From 80ff784bf60637cd348ae665fc907f7b1e527dd8 Mon Sep 17 00:00:00 2001
From: zhuguifei <zhuguifei@zhuguifeideiMac.local>
Date: 星期一, 02 三月 2026 08:43:10 +0800
Subject: [PATCH] 首次提交
---
ruoyi-plus-soybean/src/hooks/common/echarts.ts | 230
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowInstanceBizExtDTO.java | 45
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/bo/RollerTimeDataBo.java | 127
ruoyi-plus-soybean/src/store/modules/theme/shared.ts | 269
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java | 30
ruoyi-plus-soybean/src/utils/storage.ts | 9
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPattern.java | 48
ruoyi-plus-soybean/src/router/routes/builtin.ts | 31
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysOperlogController.java | 77
ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.module.css.d.ts | 19
ruoyi-plus-soybean/src/assets/svg-icon/menu/radio.svg | 1
ruoyi-plus-soybean/src/assets/svg-icon/empty-data.svg | 1
ruoyi-plus-soybean/package.json | 127
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssBo.java | 54
ruoyi-plus-soybean/packages/color/package.json | 16
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysTenant.java | 103
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysPostVo.java | 91
ruoyi-plus-soybean/src/styles/css/nprogress.css | 83
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysNotice.java | 51
ruoyi-plus-soybean/src/views/demo/demo/modules/demo-search.vue | 80
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/IRollerTimeDataService.java | 78
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/pom.xml | 36
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseProperties.java | 21
RuoYi-Vue-Plus/script/sql/update/update_5.1.1-5.1.2.sql | 5
ruoyi-plus-soybean/src/components/custom/svg-icon.vue | 54
ruoyi-plus-soybean/src/assets/svg-icon/menu/date-range.svg | 1
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/TenantListVo.java | 31
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/package-info.java | 1
ruoyi-plus-soybean/packages/scripts/src/commands/cleanup.ts | 5
ruoyi-plus-soybean/src/locales/langs/en-us.ts | 1279
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml | 10
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysPostMapper.java | 75
ruoyi-plus-soybean/src/assets/svg-icon/menu/row.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowCopyDTO.java | 30
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssConfigController.java | 106
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java | 38
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/pom.xml | 32
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserStatus.java | 30
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/.flattened-pom.xml | 46
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysOssConfigMapper.java | 16
ruoyi-plus-soybean/src/assets/svg-icon/menu/theme.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSensitiveServiceImpl.java | 47
ruoyi-plus-soybean/src/service/api/system/notice.ts | 36
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/mapper/HoisterTimeDataMapper.java | 15
ruoyi-plus-soybean/packages/alova/src/constant.ts | 2
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysSocialService.java | 53
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java | 123
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcutAdvisor.java | 33
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSocialServiceImpl.java | 112
ruoyi-plus-soybean/packages/axios/package.json | 21
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/UserInfoVo.java | 30
RuoYi-Vue-Plus/script/sql/update/oracle/update_5.0-5.1.sql | 151
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/WebSocketConfig.java | 63
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCopyBo.java | 30
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java | 242
ruoyi-plus-soybean/src/assets/svg-icon/menu/user.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/gitea/AuthGiteaSource.java | 50
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enums/AccessPolicyType.java | 56
ruoyi-plus-soybean/src/assets/svg-icon/menu/monitor.svg | 2
ruoyi-plus-soybean/src/service/api/system/tenant-package.ts | 52
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java | 119
ruoyi-plus-soybean/src/assets/svg-icon/menu/online.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PermissionService.java | 28
ruoyi-plus-soybean/packages/scripts/tsconfig.json | 20
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/pom.xml | 46
ruoyi-plus-soybean/.editorconfig | 11
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestTreeMapper.java | 21
ruoyi-plus-soybean/src/assets/svg-icon/menu/lock.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java | 40
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java | 142
RuoYi-Vue-Plus/script/sql/postgres/postgres_ry_job.sql | 871
RuoYi-Vue-Plus/script/sql/update/postgres/update_5.0-5.1.sql | 150
ruoyi-plus-soybean/src/components/common/theme-schema-switch.vue | 56
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java | 84
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOperLogBo.java | 127
ruoyi-plus-soybean/src/assets/svg-icon/menu/system.svg | 2
ruoyi-plus-soybean/src/layouts/modules/global-search/components/search-footer.vue | 36
ruoyi-plus-soybean/src/views/system/dept/modules/dept-search.vue | 82
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java | 113
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/WebSocketController.java | 33
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/config/GenConfig.java | 73
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/aspectj/RepeatSubmitAspect.java | 146
ruoyi-plus-soybean/.drone.yml | 73
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/generator.yml | 10
ruoyi-plus-soybean/src/assets/svg-icon/menu/date.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/config/WarmFlowConfig.java | 16
RuoYi-Vue-Plus/script/sql/update/postgres/update_5.3.1-5.4.0.sql | 18
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessStatus.java | 18
ruoyi-plus-soybean/src/assets/svg-icon/cast.svg | 1
ruoyi-plus-soybean/src/service/api/system/role.ts | 98
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestDemoServiceImpl.java | 118
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/HoisterTimeData.java | 222
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwSpelService.java | 88
RuoYi-Vue-Plus/script/sql/update/postgres/update_5.5.0-5.5.1.sql | 25
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelEnumFormat.java | 30
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestDemoImportVo.java | 53
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java | 40
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/config/RateLimiterConfig.java | 20
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOssExt.java | 75
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java | 251
ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/horizontal-menu.vue | 29
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/controller/GenController.java | 222
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDictTypeMapper.java | 14
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationBeanSerializerModifier.java | 29
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/controller/IndexController.java | 28
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/.flattened-pom.xml | 62
ruoyi-plus-soybean/packages/uno-preset/tsconfig.json | 20
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml | 7
ruoyi-plus-soybean/src/utils/common.ts | 210
ruoyi-plus-soybean/src/assets/svg-icon/menu/international.svg | 1
ruoyi-plus-soybean/src/components/custom/dict-radio.vue | 28
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java | 123
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessType.java | 58
ruoyi-plus-soybean/src/assets/svg-icon/bell.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/.DS_Store | 0
ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/vertical-menu.vue | 63
ruoyi-plus-soybean/src/views/system/oss-config/index.vue | 260
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/RepeatedlyRequestWrapper.java | 68
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusPostInitTableInfoHandler.java | 27
ruoyi-plus-soybean/src/layouts/modules/global-content/index.vue | 63
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/SystemConstants.java | 91
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestTreeServiceImpl.java | 90
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysClientVo.java | 90
ruoyi-plus-soybean/src/views/system/role/modules/role-data-scope-drawer.vue | 154
RuoYi-Vue-Plus/script/sql/update/oracle/update_5.3.1-5.4.0.sql | 18
ruoyi-plus-soybean/src/layouts/modules/global-search/components/search-result.vue | 56
RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.1.1-5.1.2.sql | 10
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTableColumn.java | 222
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 2
ruoyi-plus-soybean/src/components/custom/menu-tree-select.vue | 82
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserOnline.java | 63
ruoyi-plus-soybean/src/assets/svg-icon/menu/download.svg | 1
RuoYi-Vue-Plus/script/sql/update/oracle/update_5.5.0-5.5.1.sql | 25
ruoyi-plus-soybean/src/utils/icon.ts | 19
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskOperationBo.java | 48
ruoyi-plus-soybean/src/enum/business.ts | 4
ruoyi-plus-soybean/src/components/common/data-table.vue | 35
RuoYi-Vue-Plus/script/docker/redis/data/README.md | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java | 354
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysLogininforController.java | 93
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTaskAssigneeServiceImpl.java | 138
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/vo/RollerTimeDataVo.java | 164
ruoyi-plus-soybean/packages/scripts/src/commands/update-pkg.ts | 5
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysOperLogMapper.java | 14
ruoyi-plus-soybean/src/layouts/modules/global-header/components/theme-button.vue | 20
ruoyi-plus-soybean/src/store/modules/dict/index.ts | 42
ruoyi-plus-soybean/src/views/system/post/modules/post-search.vue | 86
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowSpel.java | 69
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDictData.java | 71
ruoyi-plus-soybean/src/service/api/system/menu.ts | 69
ruoyi-plus-soybean/src/service/api/auth.ts | 78
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml | 33
ruoyi-plus-soybean/src/components/custom/dept-tree-select.vue | 51
ruoyi-plus-soybean/src/views/_builtin/403/index.vue | 7
ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-import-drawer.vue | 148
ruoyi-plus-soybean/src/locales/dayjs.ts | 20
ruoyi-plus-soybean/src/views/system/dict/modules/dict-type-operate-drawer.vue | 132
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java | 110
ruoyi-plus-soybean/src/assets/svg-icon/menu/guide.svg | 1
ruoyi-plus-soybean/src/typings/global.d.ts | 26
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/.flattened-pom.xml | 24
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/general/modules/watermark-settings.vue | 66
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/general/index.vue | 17
ruoyi-plus-soybean/src/service/api/tool/gen.ts | 101
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/CaptchaVo.java | 25
ruoyi-plus-soybean/src/assets/svg-icon/menu/job.svg | 1
ruoyi-plus-soybean/packages/alova/package.json | 20
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/impl/RollerTimeDataServiceImpl.java | 162
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/FormatsType.java | 146
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/ConfigService.java | 100
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/ActuatorAuthFilter.java | 64
ruoyi-plus-soybean/packages/materials/src/libs/page-tab/shared.ts | 31
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDictTypeVo.java | 59
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java | 87
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/excel/多列表.xlsx | 0
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java | 261
ruoyi-plus-soybean/src/store/modules/theme/index.ts | 303
ruoyi-plus-soybean/src/assets/svg-icon/menu/dashboard.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysCache.java | 47
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysTenantMapper.xml | 7
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java | 65
ruoyi-plus-soybean/src/views/analy/output-analy/modules/roller-data-line-chart.vue | 312
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserImportVo.java | 76
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java | 334
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java | 160
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/entity/BillDto.java | 30
ruoyi-plus-soybean/packages/utils/package.json | 22
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java | 105
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java | 277
RuoYi-Vue-Plus/script/sql/update/update_5.3.0-5.3.1.sql | 7
ruoyi-plus-soybean/src/service/api/tool/index.ts | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java | 82
ruoyi-plus-soybean/src/assets/svg-icon/activity.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java | 103
ruoyi-plus-soybean/src/service/api/system/dept.ts | 51
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java | 127
ruoyi-plus-soybean/src/layouts/modules/global-header/components/message-button.vue | 152
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/preset/index.vue | 15
ruoyi-plus-soybean/build/config/time.ts | 12
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexUtils.java | 31
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapReduceAnnotation1.java | 60
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysLogininfor.java | 85
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/pom.xml | 41
RuoYi-Vue-Plus/ruoyi-extend/.flattened-pom.xml | 18
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java | 48
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableMapper.java | 51
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDeptVo.java | 109
ruoyi-plus-soybean/packages/materials/src/libs/page-tab/svg-close.vue | 18
ruoyi-plus-soybean/src/components/custom/status-switch.vue | 61
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/RoleService.java | 21
RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/application-prod.yml | 272
ruoyi-plus-soybean/public/favicon.svg | 4
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysTenantPackageService.java | 62
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwSpelController.java | 93
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java | 65
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm | 160
ruoyi-plus-soybean/src/views/monitor/logininfor/modules/login-infor-view-drawer.vue | 71
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/LoginType.java | 44
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/exception/TenantException.java | 20
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java | 146
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserInfoVo.java | 40
ruoyi-plus-soybean/tsconfig.json | 26
ruoyi-plus-soybean/src/views/analy/hoister/modules/hoister-data-search.vue | 147
ruoyi-plus-soybean/src/assets/svg-icon/login-background.svg | 1
ruoyi-plus-soybean/packages/alova/src/mock.ts | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssConfigServiceImpl.java | 177
ruoyi-plus-soybean/src/typings/elegant-router.d.ts | 319
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/base/BaseException.java | 74
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java | 85
ruoyi-plus-soybean/src/service/request/index.ts | 225
ruoyi-plus-soybean/src/utils/websocket.ts | 60
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DeptDTO.java | 36
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java | 39
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/resources/spy.properties | 20
ruoyi-plus-soybean/src/enum/index.ts | 9
ruoyi-plus-soybean/src/styles/scss/loading.scss | 105
ruoyi-plus-soybean/src/views/system/tenant-package/index.vue | 237
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
ruoyi-plus-soybean/src/views/system/client/modules/client-operate-drawer.vue | 221
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestTreeVo.java | 64
ruoyi-plus-soybean/src/assets/svg-icon/menu/star.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java | 328
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ObjectUtils.java | 60
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java | 87
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/WaveAndCircleCaptcha.java | 197
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java | 73
ruoyi-plus-soybean/packages/materials/src/index.ts | 6
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelResult.java | 26
RuoYi-Vue-Plus/.DS_Store | 0
RuoYi-Vue-Plus/ruoyi-common/.flattened-pom.xml | 41
ruoyi-plus-soybean/src/styles/css/transition.css | 82
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java | 29
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/.flattened-pom.xml | 84
ruoyi-plus-soybean/src/hooks/business/dict.ts | 85
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/spel-extension.json | 7
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java | 308
ruoyi-plus-soybean/src/plugins/app.ts | 108
ruoyi-plus-soybean/src/views/_builtin/social-callback/index.vue | 125
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/.flattened-pom.xml | 32
ruoyi-plus-soybean/src/typings/api/route.d.ts | 19
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/modules/theme-schema.vue | 76
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/pom.xml | 52
ruoyi-plus-soybean/src/views/home/index.vue | 43
ruoyi-plus-soybean/src/views/system/tenant/modules/tenant-operate-drawer.vue | 302
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserDTO.java | 73
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMenu.java | 191
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java | 384
ruoyi-plus-soybean/src/assets/svg-icon/menu/email.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/sql/SqlUtil.java | 56
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwSpelServiceImpl.java | 190
RuoYi-Vue-Plus/script/bin/ry.sh | 86
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserRole.java | 29
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java | 75
ruoyi-plus-soybean/src/views/analy/hoister/modules/hoister-data-operate-drawer.vue | 285
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java | 63
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
ruoyi-plus-soybean/src/utils/service.ts | 75
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowInvalidBo.java | 31
ruoyi-plus-soybean/src/styles/css/global.css | 16
RuoYi-Vue-Plus/ruoyi-modules/.DS_Store | 0
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskAssigneeService.java | 24
ruoyi-plus-soybean/src/theme/preset/dark.json | 92
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ValidatorUtils.java | 35
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java | 172
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysSocialMapper.xml | 7
ruoyi-plus-soybean/src/service/api/system/social.ts | 29
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysConfigController.java | 142
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysClientBo.java | 80
ruoyi-plus-soybean/src/assets/svg-icon/menu/color.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/SmsLoginBody.java | 29
ruoyi-plus-soybean/src/views/demo/tree/index.vue | 233
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/NetUtils.java | 84
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/listener/UserActionListener.java | 163
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/.flattened-pom.xml | 32
ruoyi-plus-soybean/src/components/common/icon-tooltip.vue | 42
ruoyi-plus-soybean/src/hooks/common/icon.ts | 10
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/XssFilter.java | 59
ruoyi-plus-soybean/src/assets/svg-icon/at-sign.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantPackageController.java | 143
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysLogininforVo.java | 106
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowCategory.java | 67
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/excel/多sheet列表.xlsx | 0
ruoyi-plus-soybean/src/views/system/tenant-package/modules/tenant-package-search.vue | 95
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java | 87
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/exception/OssException.java | 19
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/readMe.md | 499
ruoyi-plus-soybean/src/assets/svg-icon/menu/peoples.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java | 289
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/.DS_Store | 0
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java | 202
ruoyi-plus-soybean/.gitattributes | 13
ruoyi-plus-soybean/docs/template/modules/operate-drawer.vue.vm | 257
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserType.java | 39
ruoyi-plus-soybean/packages/materials/src/libs/simple-scrollbar/index.vue | 18
ruoyi-plus-soybean/packages/alova/src/type.ts | 52
ruoyi-plus-soybean/src/plugins/index.ts | 5
ruoyi-plus-soybean/src/assets/svg-icon/menu/textarea.svg | 1
ruoyi-plus-soybean/docs/sql/sys_dict_data.sql | 59
ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-operate-drawer.vue | 507
ruoyi-plus-soybean/src/assets/svg-icon/menu/shopping.svg | 1
ruoyi-plus-soybean/src/theme/preset/compact.json | 41
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestDemoMapper.xml | 11
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java | 124
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowSpelVo.java | 79
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/TaskAssigneeService.java | 45
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java | 27
ruoyi-plus-soybean/src/assets/svg-icon/menu/form.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/ExportDemoVo.java | 122
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/pom.xml | 41
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java | 55
RuoYi-Vue-Plus/script/docker/nginx/conf/nginx.conf | 156
ruoyi-plus-soybean/src/components/custom/menu-tree.vue | 222
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/IHoisterTimeDataService.java | 69
ruoyi-plus-soybean/eslint.config.js | 24
ruoyi-plus-soybean/src/components/custom/user-select.vue | 52
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestDemoBo.java | 67
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java | 219
ruoyi-plus-soybean/src/assets/svg-icon/menu/gitee.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/pom.xml | 30
ruoyi-plus-soybean/src/router/elegant/transform.ts | 228
ruoyi-plus-soybean/packages/scripts/src/config/index.ts | 39
ruoyi-plus-soybean/src/service/api/system/post.ts | 52
ruoyi-plus-soybean/src/views/home/modules/line-chart.vue | 152
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java | 43
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java | 112
ruoyi-plus-soybean/src/hooks/common/router.ts | 116
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/properties/OssProperties.java | 63
ruoyi-plus-soybean/src/views/system/config/modules/config-search.vue | 122
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/modules/theme-radius.vue | 22
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java | 25
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOperLogService.java | 61
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantSaTokenDao.java | 158
ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-preview-drawer.vue | 240
ruoyi-plus-soybean/src/views/home/modules/header-banner.vue | 71
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java | 81
ruoyi-plus-soybean/src/components/custom/better-scroll.vue | 53
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java | 378
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/sql.vm | 19
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysSocialMapper.java | 14
ruoyi-plus-soybean/src/views/system/user/modules/user-search.vue | 122
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/enums/LimitType.java | 24
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/Swagger3DemoController.java | 31
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java | 225
RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/application-dev.yml | 276
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowDefinitionVo.java | 104
ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.module.css | 63
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java | 225
ruoyi-plus-soybean/src/router/routes/index.ts | 156
RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/application.yml | 263
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application.yml | 39
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDictDataMapper.java | 29
ruoyi-plus-soybean/public/streamsaver/mitm.html | 179
ruoyi-plus-soybean/src/views/system/role/modules/role-operate-drawer.vue | 198
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/XcxLoginUser.java | 27
ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/shared.ts | 68
ruoyi-plus-soybean/src/assets/svg-icon/menu/github.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/modules/search.vue.vm | 130
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDeptBo.java | 81
ruoyi-plus-soybean/packages/scripts/src/commands/router.ts | 90
ruoyi-plus-soybean/build/plugins/unocss.ts | 32
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/pom.xml | 33
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/EmailLoginBody.java | 31
ruoyi-plus-soybean/src/hooks/business/auth.ts | 46
RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.3.0-5.3.1.sql | 21
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/handler/DataWriteHandler.java | 123
ruoyi-plus-soybean/packages/utils/src/storage.ts | 77
ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/top-hybrid-sidebar-first.vue | 64
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/RouterVo.java | 62
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamRequest.java | 113
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/components/setting-item.vue | 24
ruoyi-plus-soybean/src/components/advanced/table-header-operation.vue | 97
ruoyi-plus-soybean/packages/color/src/shared/name.ts | 49
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java | 43
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java | 273
RuoYi-Vue-Plus/script/leave/leave2.json | 187
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/.flattened-pom.xml | 25
ruoyi-plus-soybean/src/layouts/modules/global-tab/context-menu.vue | 146
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantEntity.java | 21
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestTreeController.java | 107
ruoyi-plus-soybean/packages/scripts/src/locales/index.ts | 82
ruoyi-plus-soybean/src/components/advanced/table-sider-layout.vue | 109
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/BigNumberSerializer.java | 42
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
ruoyi-plus-soybean/src/assets/svg-icon/menu/time.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java | 52
ruoyi-plus-soybean/src/assets/svg-icon/menu/clipboard.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java | 70
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCopyVo.java | 36
ruoyi-plus-soybean/build/config/proxy.ts | 56
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java | 132
ruoyi-plus-soybean/src/utils/sse.ts | 58
RuoYi-Vue-Plus/ruoyi-admin/pom.xml | 159
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysPost.java | 61
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/IGenTableService.java | 141
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestDemoController.java | 147
ruoyi-plus-soybean/src/utils/jsencrypt.ts | 21
ruoyi-plus-soybean/src/views/analy/hoister/index.vue | 413
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/api.ts.vm | 63
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/types.ts.vm | 61
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml | 7
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java | 426
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java | 772
ruoyi-plus-soybean/pnpm-workspace.yaml | 2
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssConfigVo.java | 97
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/validate/JsonPattern.java | 33
ruoyi-plus-soybean/src/hooks/common/table.ts | 477
ruoyi-plus-soybean/src/assets/svg-icon/menu/tool.svg | 1
ruoyi-plus-soybean/packages/uno-preset/package.json | 12
ruoyi-plus-soybean/src/views/_builtin/user-center/modules/user-avatar.vue | 211
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestClassJobExecutor.java | 19
RuoYi-Vue-Plus/ruoyi-admin/.DS_Store | 0
ruoyi-plus-soybean/src/service/api/monitor/oper-log.ts | 26
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/.flattened-pom.xml | 28
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/banner.txt | 11
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/FeedmatchTimeData.java | 154
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDictTypeBo.java | 50
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml | 127
ruoyi-plus-soybean/src/assets/svg-icon/menu/server.svg | 1
ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.module.css | 121
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/config/IdempotentConfig.java | 21
ruoyi-plus-soybean/src/layouts/modules/global-menu/components/first-level-menu.vue | 109
ruoyi-plus-soybean/src/layouts/modules/global-tab/index.vue | 233
ruoyi-plus-soybean/src/service/api/monitor/login-infor.ts | 34
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/properties/SecurityProperties.java | 21
RuoYi-Vue-Plus/script/sql/update/oracle/update_5.5.1-5.5.2.sql | 4
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils2.java | 343
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java | 160
ruoyi-plus-soybean/src/service/request/shared.ts | 63
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/.flattened-pom.xml | 73
ruoyi-plus-soybean/packages/color/src/palette/recommend.ts | 152
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/CacheConfig.java | 45
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
ruoyi-plus-soybean/src/views/monitor/cache/index.vue | 690
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/vo/HoisterTimeDataVo.java | 301
ruoyi-plus-soybean/packages/axios/src/index.ts | 179
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml | 51
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysLogininforServiceImpl.java | 182
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/sider-settings.vue | 60
RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.1.2-5.2.0.sql | 29
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDynamicOptions.java | 23
ruoyi-plus-soybean/src/assets/svg-icon/menu/slider.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/MetaVo.java | 78
ruoyi-plus-soybean/packages/axios/src/constant.ts | 8
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DeptService.java | 46
ruoyi-plus-soybean/src/views/system/tenant/modules/tenant-search.vue | 80
RuoYi-Vue-Plus/script/leave/leave6.json | 368
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwDefinitionController.java | 195
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java | 30
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java | 29
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/.DS_Store | 0
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java | 20
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/dto/SseMessageDto.java | 29
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictTypeController.java | 131
ruoyi-plus-soybean/src/views/system/user/modules/user-password-drawer.vue | 115
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/bo/PackerTimeDataBo.java | 149
RuoYi-Vue-Plus/script/sql/ry_job.sql | 535
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java | 61
ruoyi-plus-soybean/packages/color/src/types/index.ts | 58
ruoyi-plus-soybean/src/assets/svg-icon/menu/password.svg | 1
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/logback-plus.xml | 34
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/TaskAssigneeDTO.java | 101
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictTypeDTO.java | 41
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 6
RuoYi-Vue-Plus/ruoyi-modules/pom.xml | 28
ruoyi-plus-soybean/src/views/analy/packer/index.vue | 344
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java | 243
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java | 34
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelResult.java | 73
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/.flattened-pom.xml | 24
ruoyi-plus-soybean/packages/hooks/src/use-loading.ts | 16
ruoyi-plus-soybean/src/assets/svg-icon/menu/exit-fullscreen.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityInitializer.java | 35
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/handler/SaTokenExceptionHandler.java | 52
ruoyi-plus-soybean/packages/scripts/src/commands/git-commit.ts | 84
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/.flattened-pom.xml | 50
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/I18nLocaleResolver.java | 31
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/TestLeaveMapper.java | 15
ruoyi-plus-soybean/src/styles/scss/scrollbar.scss | 28
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java | 65
RuoYi-Vue-Plus/script/docker/redis/conf/redis.conf | 28
ruoyi-plus-soybean/src/assets/svg-icon/menu/tree-table.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysRoleVo.java | 100
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysClientMapper.java | 15
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/.flattened-pom.xml | 41
ruoyi-plus-soybean/packages/hooks/src/use-svg-icon-render.ts | 50
RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/banner.txt | 8
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysTenantVo.java | 115
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/index-tree.vue.vm | 232
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java | 94
ruoyi-plus-soybean/src/views/_builtin/login/modules/reset-pwd.vue | 105
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/listener/WebSocketTopicListener.java | 50
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserProfileBo.java | 53
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestI18nController.java | 71
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictDataDTO.java | 41
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/constant/GenConstants.java | 186
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/ITestDemoService.java | 71
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/modules/theme-color.vue | 83
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/TestDemo.java | 68
ruoyi-plus-soybean/src/service/api/analy/packer-data.ts | 35
RuoYi-Vue-Plus/script/sql/sqlserver/sqlserver_ry_vue_5.X.sql | 3641 +
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java | 23
RuoYi-Vue-Plus/script/docker/database.yml | 59
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/.flattened-pom.xml | 20
ruoyi-plus-soybean/docs/README.md | 11
ruoyi-plus-soybean/src/assets/svg-icon/menu/caret-back.svg | 1
ruoyi-plus-soybean/src/typings/api/system.api.d.ts | 830
ruoyi-plus-soybean/packages/color/tsconfig.json | 20
ruoyi-plus-soybean/src/store/modules/app/index.ts | 166
ruoyi-plus-soybean/src/layouts/modules/global-breadcrumb/index.vue | 47
ruoyi-plus-soybean/src/assets/svg-icon/menu/company.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoMapper.java | 64
ruoyi-plus-soybean/src/views/tool/gen/index.vue | 317
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/handler/PlusWebSocketHandler.java | 123
ruoyi-plus-soybean/.env | 63
ruoyi-plus-soybean/src/views/system/oss/index.vue | 360
ruoyi-plus-soybean/src/components/common/full-screen.vue | 22
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/BusinessStatusEnum.java | 215
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/typings/api.d.ts.vm | 51
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml | 7
ruoyi-plus-soybean/.env.prod | 23
ruoyi-plus-soybean/src/views/_builtin/login/modules/bind-wechat.vue | 11
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java | 17
ruoyi-plus-soybean/src/assets/svg-icon/menu/question.svg | 1
ruoyi-plus-soybean/src/service/api/analy/roller-data.ts | 46
ruoyi-plus-soybean/src/views/system/user/index.vue | 442
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/package-info.java | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java | 34
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/OperLogEvent.java | 115
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/pom.xml | 73
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/java/org/dromara/snailjob/SnailJobServerApplication.java | 19
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/.flattened-pom.xml | 53
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/config/SaTokenConfig.java | 54
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwSpelMapper.java | 15
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java | 30
ruoyi-plus-soybean/src/constants/common.ts | 15
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java | 88
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/listener/SseTopicListener.java | 48
ruoyi-plus-soybean/src/typings/api/api.d.ts | 88
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DesensitizedUtils.java | 87
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserPost.java | 29
ruoyi-plus-soybean/src/components/custom/dict-select.vue | 37
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamSource.java | 51
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssUploadVo.java | 28
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java | 111
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java | 18
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelOptionsProvider.java | 19
ruoyi-plus-soybean/src/locales/naive.ts | 12
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java | 134
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml | 7
ruoyi-plus-soybean/src/assets/svg-icon/menu/link.svg | 1
ruoyi-plus-soybean/src/views/system/tenant/index.vue | 302
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogininforEvent.java | 52
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestStaticShardingJob.java | 37
ruoyi-plus-soybean/src/views/analy/packer/modules/packer-data-search.vue | 147
ruoyi-plus-soybean/src/constants/business.ts | 138
ruoyi-plus-soybean/src/router/elegant/routes.ts | 437
ruoyi-plus-soybean/src/service/api/monitor/index.ts | 4
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserPasswordBo.java | 29
ruoyi-plus-soybean/src/assets/svg-icon/service-error.svg | 1
RuoYi-Vue-Plus/script/sql/oracle/oracle_ry_vue_5.X.sql | 1407
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java | 68
ruoyi-plus-soybean/src/views/system/dict/index.vue | 519
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/general/modules/global-settings.vue | 39
ruoyi-plus-soybean/src/typings/common.d.ts | 46
ruoyi-plus-soybean/docs/template/typings/api.d.ts.vm | 51
ruoyi-plus-soybean/src/hooks/business/download.ts | 166
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/TransTimeData.java | 159
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/.flattened-pom.xml | 40
ruoyi-plus-soybean/packages/scripts/src/commands/changelog.ts | 10
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDictFormat.java | 32
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermissionVo.java | 43
ruoyi-plus-soybean/src/assets/svg-icon/menu/size.svg | 1
ruoyi-plus-soybean/src/hooks/common/form.ts | 112
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestTreeBo.java | 54
RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.1.0-5.1.1.sql | 19
ruoyi-plus-soybean/src/assets/svg-icon/copy.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/pom.xml | 66
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 2
RuoYi-Vue-Plus/script/sql/oracle/oracle_ry_workflow.sql | 528
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java | 149
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/package-info.md | 3
ruoyi-plus-soybean/src/assets/svg-icon/menu/qq.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java | 73
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/BoxTimeData.java | 59
ruoyi-plus-soybean/src/views/system/oss-config/modules/oss-config-operate-drawer.vue | 226
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/utils/WebSocketUtils.java | 127
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java | 93
RuoYi-Vue-Plus/script/sql/update/postgres/update_5.5.1-5.5.2.sql | 6
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/pom.xml | 25
ruoyi-plus-soybean/packages/utils/src/index.ts | 4
ruoyi-plus-soybean/src/service/api/demo/tree.ts | 36
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/bo.java.vm | 50
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysTenantService.java | 92
ruoyi-plus-soybean/src/assets/svg-icon/not-found.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java | 155
ruoyi-plus-soybean/packages/scripts/src/commands/release.ts | 12
ruoyi-plus-soybean/src/components/custom/soybean-avatar.vue | 13
ruoyi-plus-soybean/src/utils/agent.ts | 7
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisLockController.java | 64
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm | 499
ruoyi-plus-soybean/src/assets/svg-icon/menu/list.svg | 1
ruoyi-plus-soybean/src/router/index.ts | 30
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java | 150
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexValidator.java | 105
RuoYi-Vue-Plus/script/sql/update/update_5.1.0-5.1.1.sql | 3
ruoyi-plus-soybean/src/components/advanced/table-column-setting.vue | 54
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java | 96
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java | 291
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDictDataBo.java | 80
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java | 50
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java | 479
ruoyi-plus-soybean/src/typings/api/monitor.api.d.ts | 170
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/AlgorithmType.java | 48
ruoyi-plus-soybean/docs/template/api/api.ts.vm | 41
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java | 79
ruoyi-plus-soybean/src/typings/vite-env.d.ts | 128
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/service.java.vm | 72
ruoyi-plus-soybean/src/layouts/modules/global-header/components/user-avatar.vue | 142
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/DelayedQueueController.java | 97
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java | 123
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceType.java | 39
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
ruoyi-plus-soybean/packages/scripts/src/types/index.ts | 31
RuoYi-Vue-Plus/script/sql/update/update_5.1.2-5.2.0.sql | 5
ruoyi-plus-soybean/src/App.vue | 59
RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/DemoUnitTest.java | 70
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestBatchController.java | 90
ruoyi-plus-soybean/packages/hooks/src/index.ts | 9
ruoyi-plus-soybean/src/constants/app.ts | 79
ruoyi-plus-soybean/src/layouts/modules/global-sider/index.vue | 36
ruoyi-plus-soybean/src/views/system/client/modules/client-search.vue | 95
RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.4.1-5.5.0.sql | 294
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/table-settings.vue | 44
ruoyi-plus-soybean/src/views/demo/demo/modules/demo-operate-drawer.vue | 140
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTable.java | 196
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java | 270
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssConfigService.java | 64
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java | 114
ruoyi-plus-soybean/packages/utils/src/nanoid.ts | 3
ruoyi-plus-soybean/src/service/api/demo/demo.ts | 36
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/.flattened-pom.xml | 24
ruoyi-plus-soybean/src/assets/svg-icon/menu/tab.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
ruoyi-plus-soybean/src/assets/svg-icon/menu/message.svg | 1
ruoyi-plus-soybean/src/components/common/reload-button.vue | 21
ruoyi-plus-soybean/src/assets/svg-icon/menu/validCode.svg | 1
ruoyi-plus-soybean/.env.dev | 26
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/pom.xml | 34
ruoyi-plus-soybean/src/components/custom/post-select.vue | 63
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/dto/WebSocketMessageDto.java | 29
ruoyi-plus-soybean/index.html | 14
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java | 313
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/XssHttpServletRequestWrapper.java | 134
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java | 33
ruoyi-plus-soybean/packages/utils/tsconfig.json | 20
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOperLogVo.java | 144
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/CellMerge.java | 29
RuoYi-Vue-Plus/script/sql/update/update_5.4.1-5.5.0.sql | 67
ruoyi-plus-soybean/packages/materials/src/libs/page-tab/chrome-tab.vue | 58
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/properties/MailProperties.java | 75
ruoyi-plus-soybean/src/assets/svg-icon/menu/money.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/WriteOutSubscriber.java | 15
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/core/dao/PlusSmsDao.java | 72
ruoyi-plus-soybean/src/assets/svg-icon/menu/workflow.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java | 213
ruoyi-plus-soybean/src/assets/svg-icon/menu/wechat.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java | 80
ruoyi-plus-soybean/src/views/analy/roller/modules/roller-data-search.vue | 147
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceBizExtMapper.xml | 7
ruoyi-plus-soybean/src/assets/svg-icon/menu/404.svg | 1
ruoyi-plus-soybean/src/views/_builtin/login/modules/pwd-login.vue | 258
RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties | 62
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwSpelMapper.xml | 7
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/XssProperties.java | 28
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java | 86
RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties | 62
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java | 54
RuoYi-Vue-Plus/pom.xml | 506
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java | 440
ruoyi-plus-soybean/src/components/custom/oss-upload.vue | 69
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantBo.java | 115
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java | 564
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictDataServiceImpl.java | 159
ruoyi-plus-soybean/packages/axios/src/type.ts | 130
ruoyi-plus-soybean/src/assets/svg-icon/menu/fullscreen.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java | 614
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/TranslationType.java | 23
ruoyi-plus-soybean/.gitignore | 35
RuoYi-Vue-Plus/ruoyi-admin/.flattened-pom.xml | 98
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/RegisterBody.java | 37
ruoyi-plus-soybean/src/main.ts | 31
RuoYi-Vue-Plus/script/sql/ry_workflow.sql | 319
ruoyi-plus-soybean/README.md | 391
ruoyi-plus-soybean/build/plugins/html.ts | 13
ruoyi-plus-soybean/src/service/api/monitor/online.ts | 42
ruoyi-plus-soybean/src/views/system/notice/index.vue | 200
ruoyi-plus-soybean/src/assets/svg-icon/menu/example.svg | 1
ruoyi-plus-soybean/src/typings/api/demo.api.d.ts | 83
ruoyi-plus-soybean/packages/scripts/package.json | 28
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/MakeupTimeData.java | 84
ruoyi-plus-soybean/src/assets/svg-icon/menu/drag.svg | 1
ruoyi-plus-soybean/src/views/system/oss/modules/oss-search.vue | 102
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java | 41
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java | 20
RuoYi-Vue-Plus/script/sql/oracle/oracle_ry_job.sql | 940
RuoYi-Vue-Plus/script/sql/ry_vue_5.X.sql | 976
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java | 83
ruoyi-plus-soybean/src/views/system/config/index.vue | 240
ruoyi-plus-soybean/src/views/_builtin/user-center/index.vue | 244
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java | 49
ruoyi-plus-soybean/src/typings/api/auth.d.ts | 110
ruoyi-plus-soybean/src/locales/langs/zh-cn.ts | 1271
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaExpireException.java | 18
ruoyi-plus-soybean/packages/scripts/src/shared/index.ts | 7
RuoYi-Vue-Plus/LICENSE | 20
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java | 68
RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/logback-plus.xml | 129
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java | 102
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseAutoConfiguration.java | 36
ruoyi-plus-soybean/.env.test | 23
ruoyi-plus-soybean/build/config/index.ts | 2
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java | 53
ruoyi-plus-soybean/src/assets/svg-icon/menu/eye-open.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/HttpStatus.java | 93
RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/i18n/messages.properties | 62
ruoyi-plus-soybean/src/views/monitor/logininfor/modules/login-infor-search.vue | 101
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/properties/RedissonProperties.java | 135
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java | 176
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java | 80
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml | 7
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/table-props.vue | 44
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/KeyPrefixHandler.java | 50
ruoyi-plus-soybean/docs/java/VelocityUtils.java | 389
ruoyi-plus-soybean/src/typings/storage.d.ts | 52
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MapstructUtils.java | 93
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java | 469
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/controller.java.vm | 115
ruoyi-plus-soybean/src/views/home/modules/project-news.vue | 40
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/.flattened-pom.xml | 24
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/RollerTimeDataMapper.xml | 69
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowHisTaskVo.java | 260
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/IEncryptor.java | 35
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysConfig.java | 51
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictDataService.java | 82
ruoyi-plus-soybean/src/assets/svg-icon/menu/my-copy.svg | 1
ruoyi-plus-soybean/src/views/_builtin/user-center/modules/social-card.vue | 114
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java | 351
ruoyi-plus-soybean/src/store/modules/auth/shared.ts | 12
RuoYi-Vue-Plus/script/sql/postgres/postgres_ry_vue_5.X.sql | 1398
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java | 69
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysConfigVo.java | 72
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java | 124
ruoyi-plus-soybean/src/assets/svg-icon/avatar.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java | 231
RuoYi-Vue-Plus/script/sql/update/update_5.5.0-5.5.1.sql | 21
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/preset/modules/theme-preset.vue | 156
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/rule/SpelRuleComponent.java | 38
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceMapper.java | 27
ruoyi-plus-soybean/src/plugins/nprogress.ts | 9
ruoyi-plus-soybean/src/views/home/modules/card-data.vue | 115
ruoyi-plus-soybean/src/styles/scss/custom.scss | 5
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/PostDTO.java | 46
ruoyi-plus-soybean/packages/hooks/src/use-count-down.ts | 68
RuoYi-Vue-Plus/script/sql/update/update_5.3.1-5.4.0.sql | 21
ruoyi-plus-soybean/src/views/analy/roller/modules/roller-data-operate-drawer.vue | 209
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/SmsController.java | 82
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/package-info.md | 3
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRoleDept.java | 29
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java | 70
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/RepeatableFilter.java | 40
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java | 131
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Base64Encryptor.java | 48
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/ProfileUserVo.java | 90
ruoyi-plus-soybean/packages/materials/src/libs/simple-scrollbar/index.ts | 3
ruoyi-plus-soybean/src/plugins/assets.ts | 3
ruoyi-plus-soybean/src/views/monitor/operlog/index.vue | 225
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/.flattened-pom.xml | 42
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCategoryBo.java | 47
RuoYi-Vue-Plus/ruoyi-common/pom.xml | 46
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java | 230
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/properties/WebSocketProperties.java | 26
ruoyi-plus-soybean/src/layouts/base-layout/index.vue | 170
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceMapper.xml | 39
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysClientService.java | 66
ruoyi-plus-soybean/src/components/custom/role-select.vue | 52
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginVo.java | 54
ruoyi-plus-soybean/packages/color/src/palette/index.ts | 45
ruoyi-plus-soybean/src/assets/svg-icon/no-icon.svg | 1
ruoyi-plus-soybean/src/layouts/modules/global-header/index.vue | 68
ruoyi-plus-soybean/src/typings/router.d.ts | 72
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java | 246
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserOnlineDTO.java | 72
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/.flattened-pom.xml | 20
ruoyi-plus-soybean/src/theme/preset/default.json | 100
ruoyi-plus-soybean/src/service/api/system/index.ts | 8
ruoyi-plus-soybean/src/views/analy/output-analy/modules/roller-data-operate-drawer.vue | 213
RuoYi-Vue-Plus/ruoyi-extend/.DS_Store | 0
ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-search.vue | 103
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java | 32
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml | 51
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/.DS_Store | 0
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/Xss.java | 26
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantPackageBo.java | 59
ruoyi-plus-soybean/src/views/system/user/modules/user-import-modal.vue | 162
ruoyi-plus-soybean/src/components/custom/umo-doc-editor.vue | 104
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml | 7
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginTenantVo.java | 25
RuoYi-Vue-Plus/ruoyi-extend/pom.xml | 19
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeEnum.java | 140
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java | 41
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml | 7
ruoyi-plus-soybean/src/service/api/system/config.ts | 69
ruoyi-plus-soybean/packages/axios/src/shared.ts | 79
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/OssDTO.java | 46
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 4
ruoyi-plus-soybean/packages/color/src/constant/name.ts | 1579
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java | 217
ruoyi-plus-soybean/src/router/elegant/imports.ts | 51
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/dicts/DictPattern.java | 40
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/AlipayBillTask.java | 42
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPostService.java | 136
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/RsaEncryptor.java | 62
ruoyi-plus-soybean/src/views/about/index.vue | 110
ruoyi-plus-soybean/src/router/guard/progress.ts | 11
ruoyi-plus-soybean/packages/color/src/palette/antd.ts | 176
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/TaskAssigneeBody.java | 56
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/RegexPatternPoolFactory.java | 52
ruoyi-plus-soybean/src/theme/vars.ts | 35
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysConfigService.java | 93
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeType.java | 49
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/controller/RollerTimeDataController.java | 120
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java | 134
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/sqlserver/sql.vm | 19
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDictDataVo.java | 88
ruoyi-plus-soybean/docs/template/index-tree.vue.vm | 232
ruoyi-plus-soybean/build/plugins/router.ts | 44
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/banner.txt | 8
ruoyi-plus-soybean/src/hooks/common/loading.ts | 25
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/api/api.ts.vm | 41
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysLogininforMapper.java | 14
RuoYi-Vue-Plus/script/bin/ry.bat | 68
ruoyi-plus-soybean/src/components/custom/count-to.vue | 88
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java | 226
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/package-info.java | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwCategoryMapper.java | 49
ruoyi-plus-soybean/src/assets/svg-icon/menu/edit.svg | 1
ruoyi-plus-soybean/src/constants/reg.ts | 25
ruoyi-plus-soybean/packages/materials/src/types/index.ts | 291
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysClient.java | 77
ruoyi-plus-soybean/src/service/api/system/tenant.ts | 81
ruoyi-plus-soybean/src/assets/svg-icon/menu/phone.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestDemoVo.java | 119
ruoyi-plus-soybean/src/assets/svg-icon/expectation.svg | 1
ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.vue | 77
ruoyi-plus-soybean/src/router/guard/title.ts | 13
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessDeleteEvent.java | 34
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/domain.java.vm | 60
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailSendController.java | 69
RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.0-5.1.sql | 409
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantController.java | 211
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowSpelBo.java | 60
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/me/zhyd/oauth/request/AuthDingTalkV2Request.java | 109
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/pom.xml | 108
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/mapper/PackerTimeDataMapper.java | 15
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMapper.java | 105
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserPostMapper.java | 13
ruoyi-plus-soybean/src/typings/naive-ui.d.ts | 24
ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.ts | 3
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowVariableBo.java | 39
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowInstanceBo.java | 55
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwCategoryMapper.xml | 7
ruoyi-plus-soybean/src/service/api/system/dict-data.ts | 36
ruoyi-plus-soybean/packages/alova/src/index.ts | 77
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/config-operation.vue | 58
ruoyi-plus-soybean/src/views/system/menu/modules/menu-cascade-delete-modal.vue | 118
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/content-settings.vue | 64
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/EncryptField.java | 44
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysRoleBo.java | 94
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java | 178
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/SseException.java | 62
ruoyi-plus-soybean/src/components/custom/file-upload.vue | 212
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java | 412
RuoYi-Vue-Plus/script/sql/update/oracle/update_5.3.0-5.3.1.sql | 7
ruoyi-plus-soybean/src/router/guard/route.ts | 192
ruoyi-plus-soybean/src/assets/svg-icon/menu/my-task.svg | 1
ruoyi-plus-soybean/src/plugins/iconify.ts | 10
ruoyi-plus-soybean/src/service/api/system/client.ts | 45
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelRequired.java | 22
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/pom.xml | 108
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/layout-mode.vue | 84
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java | 58
ruoyi-plus-soybean/src/assets/svg-icon/menu/button.svg | 10
ruoyi-plus-soybean/src/service/api/system/dict.ts | 59
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java | 119
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java | 48
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowInstanceVo.java | 149
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/PackerTimeData.java | 147
ruoyi-plus-soybean/src/views/system/dept/modules/dept-operate-drawer.vue | 248
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java | 297
RuoYi-Vue-Plus/ruoyi-modules/.flattened-pom.xml | 23
ruoyi-plus-soybean/src/service/request/type.ts | 7
ruoyi-plus-soybean/docs/template/modules/search.vue.vm | 130
ruoyi-plus-soybean/build/plugins/devtools.ts | 9
ruoyi-plus-soybean/src/views/system/post/modules/post-operate-drawer.vue | 169
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/package-info.md | 3
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java | 67
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java | 63
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveService.java | 18
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/SummaryBillTask.java | 45
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml | 42
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/CopySettingEnum.java | 20
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/RoleDTO.java | 42
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/.flattened-pom.xml | 36
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMenuBo.java | 113
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapJobAnnotation.java | 53
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/pom.xml | 26
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/I18nConfig.java | 22
RuoYi-Vue-Plus/script/sql/update/postgres/update_5.1.1-5.1.2.sql | 5
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisPubSubController.java | 47
ruoyi-plus-soybean/src/layouts/blank-layout/index.vue | 13
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPermissionServiceImpl.java | 62
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssMapper.xml | 5
ruoyi-plus-soybean/src/views/monitor/operlog/modules/oper-log-view-drawer.vue | 73
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysNoticeMapper.java | 14
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/resources/common-satoken.yml | 13
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/pom.xml | 46
ruoyi-plus-soybean/src/views/system/tenant-package/modules/tenant-package-operate-drawer.vue | 174
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTerminationBo.java | 31
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCategoryService.java | 95
ruoyi-plus-soybean/src/layouts/modules/global-search/index.vue | 18
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java | 92
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUser.java | 115
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginUser.java | 148
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/index.vue | 31
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/pom.xml | 30
ruoyi-plus-soybean/packages/materials/src/libs/page-tab/slider-tab.vue | 53
RuoYi-Vue-Plus/script/docker/docker-compose.yml | 157
RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/ip2region_v4.xdb | 0
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/properties/TenantProperties.java | 27
RuoYi-Vue-Plus/script/sql/update/postgres/update_5.3.0-5.3.1.sql | 7
ruoyi-plus-soybean/packages/color/src/shared/colord.ts | 93
RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/ParamUnitTest.java | 72
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java | 217
ruoyi-plus-soybean/packages/hooks/src/use-boolean.ts | 31
ruoyi-plus-soybean/src/assets/svg-icon/menu/select.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/package-info.java | 1
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/IAuthStrategy.java | 46
ruoyi-plus-soybean/src/store/modules/route/index.ts | 424
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java | 127
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/config/MyBatisDataSourceMonitor.java | 105
ruoyi-plus-soybean/docs/sql/sys_menu.sql | 43
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java | 102
ruoyi-plus-soybean/src/service/api/monitor/cache.ts | 8
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysOssMapper.java | 13
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowInstanceBizExt.java | 59
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/NodeExtVo.java | 45
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/listener/ExportDemoListener.java | 68
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java | 149
ruoyi-plus-soybean/src/assets/svg-icon/menu/documentation.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/DromaraApplication.java | 23
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/EditGroup.java | 9
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPermissionService.java | 28
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysNoticeService.java | 73
ruoyi-plus-soybean/src/assets/svg-icon/menu/zip.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/PasswordLoginBody.java | 32
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/bo/HoisterTimeDataBo.java | 256
RuoYi-Vue-Plus/README.md | 189
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java | 122
ruoyi-plus-soybean/src/components/common/menu-toggler.vue | 53
ruoyi-plus-soybean/src/assets/svg-icon/menu/checkbox.svg | 1
ruoyi-plus-soybean/src/layouts/modules/global-search/components/search-modal.vue | 123
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java | 52
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java | 113
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/handler/AllUrlHandler.java | 39
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/pom.xml | 34
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/TenantConstants.java | 35
ruoyi-plus-soybean/src/views/monitor/online/index.vue | 172
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelNotation.java | 20
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/.flattened-pom.xml | 28
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelBigNumberConvert.java | 52
ruoyi-plus-soybean/docs/template/index.vue.vm | 198
ruoyi-plus-soybean/src/assets/svg-icon/menu/education.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java | 104
ruoyi-plus-soybean/src/components/custom/tenant-select.vue | 103
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/QueryGroup.java | 9
ruoyi-plus-soybean/src/views/system/post/index.vue | 358
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml | 7
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/index.vue | 19
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/PlusTenantLineHandler.java | 56
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java | 29
ruoyi-plus-soybean/src/assets/svg-icon/menu/dict.svg | 1
ruoyi-plus-soybean/src/service/api/system/user.ts | 136
ruoyi-plus-soybean/packages/materials/package.json | 19
ruoyi-plus-soybean/src/assets/svg-icon/menu/swagger.svg | 1
ruoyi-plus-soybean/src/components/common/exception-base.vue | 43
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java | 272
ruoyi-plus-soybean/src/views/system/dept/index.vue | 218
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java | 34
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/SnailJobConfig.java | 37
ruoyi-plus-soybean/src/views/system/role/index.vue | 293
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java | 109
ruoyi-plus-soybean/src/views/_builtin/404/index.vue | 7
ruoyi-plus-soybean/src/components/custom/dept-tree.vue | 154
ruoyi-plus-soybean/src/store/modules/route/shared.ts | 335
ruoyi-plus-soybean/src/store/modules/tab/shared.ts | 263
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java | 41
RuoYi-Vue-Plus/script/leave/leave3.json | 211
ruoyi-plus-soybean/src/locales/locale.ts | 9
ruoyi-plus-soybean/src/assets/svg-icon/menu/build.svg | 1
ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/vertical-mix-menu.vue | 127
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/SecurityConfig.java | 29
ruoyi-plus-soybean/public/streamsaver/sw.js | 132
ruoyi-plus-soybean/src/assets/svg-icon/menu/bug.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCategoryVo.java | 69
ruoyi-plus-soybean/src/assets/svg-icon/logo.svg | 4
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysMenuVo.java | 116
ruoyi-plus-soybean/packages/materials/src/libs/page-tab/button-tab.vue | 53
ruoyi-plus-soybean/src/views/demo/demo/index.vue | 225
ruoyi-plus-soybean/src/views/system/role/modules/role-auth-user-drawer.vue | 275
ruoyi-plus-soybean/packages/utils/src/klona.ts | 3
ruoyi-plus-soybean/src/assets/svg-icon/network-error.svg | 1
ruoyi-plus-soybean/src/components/custom/button-icon.vue | 91
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java | 142
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/properties/SpringDocProperties.java | 94
ruoyi-plus-soybean/src/assets/svg-icon/menu/code.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/interceptor/PlusWebSocketInterceptor.java | 75
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfig.java | 86
ruoyi-plus-soybean/src/views/system/oss-config/modules/oss-config-search.vue | 92
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysSocialController.java | 38
ruoyi-plus-soybean/src/views/analy/roller/index.vue | 304
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/BaseController.java | 40
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/pom.xml | 46
ruoyi-plus-soybean/src/views/system/config/modules/config-operate-drawer.vue | 145
ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-db-search.vue | 76
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java | 21
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml | 7
ruoyi-plus-soybean/src/service/api/system/oss.ts | 40
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java | 107
ruoyi-plus-soybean/src/views/home/modules/pie-chart.vue | 109
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysNoticeBo.java | 61
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/pom.xml | 54
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java | 34
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PostService.java | 21
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml | 42
RuoYi-Vue-Plus/开发日志.md | 119
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 3
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java | 39
ruoyi-plus-soybean/src/components/custom/boolean-tag.vue | 24
ruoyi-plus-soybean/src/views/_builtin/user-center/modules/online-table.vue | 125
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java | 35
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/.flattened-pom.xml | 84
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMenuService.java | 172
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java | 131
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java | 31
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java | 304
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/pom.xml | 57
RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.3.1-5.4.0.sql | 63
ruoyi-plus-soybean/src/typings/api/tool.api.d.ts | 190
ruoyi-plus-soybean/packages/hooks/tsconfig.json | 20
ruoyi-plus-soybean/src/theme/preset/azir.json | 53
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/.flattened-pom.xml | 43
ruoyi-plus-soybean/build/plugins/unplugin.ts | 47
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java | 28
ruoyi-plus-soybean/src/components/common/lang-switch.vue | 61
ruoyi-plus-soybean/src/assets/svg-icon/menu/logininfor.svg | 1
ruoyi-plus-soybean/src/locales/index.ts | 26
ruoyi-plus-soybean/src/views/system/dict/modules/dict-data-search.vue | 77
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/pom.xml | 27
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestTreeMapper.xml | 7
ruoyi-plus-soybean/packages/scripts/bin.ts | 3
ruoyi-plus-soybean/src/assets/svg-icon/menu/category.svg | 1
ruoyi-plus-soybean/src/assets/svg-icon/menu/switch.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java | 89
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysTenantPackageVo.java | 66
RuoYi-Vue-Plus/script/leave/leave5.json | 211
ruoyi-plus-soybean/src/views/system/client/index.vue | 275
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java | 108
ruoyi-plus-soybean/src/assets/svg-icon/menu/nested.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 5
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysNoticeVo.java | 73
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysConfigBo.java | 59
ruoyi-plus-soybean/src/assets/svg-icon/menu/input.svg | 1
ruoyi-plus-soybean/src/utils/copy.ts | 26
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysLogininforBo.java | 87
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java | 581
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDept.java | 92
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssConfigMapper.xml | 7
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java | 217
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AbstractEncryptor.java | 18
ruoyi-plus-soybean/src/assets/svg-icon/custom-icon.svg | 1
ruoyi-plus-soybean/packages/alova/tsconfig.json | 20
ruoyi-plus-soybean/src/hooks/business/captcha.ts | 71
ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.vue | 236
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwTaskMapper.java | 47
ruoyi-plus-soybean/src/assets/svg-icon/menu/tree.svg | 1
ruoyi-plus-soybean/src/components/common/app-provider.vue | 56
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysTenantPackage.java | 60
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysClientMapper.xml | 7
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/gitea/AuthGiteaRequest.java | 92
ruoyi-plus-soybean/src/assets/svg-icon/wind.svg | 1
ruoyi-plus-soybean/src/views/system/oss/modules/oss-upload-modal.vue | 67
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java | 71
ruoyi-plus-soybean/src/styles/scss/global.scss | 2
ruoyi-plus-soybean/src/typings/api/analy.packer-data.api.d.ts | 131
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/SpringUtils.java | 67
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 1
ruoyi-plus-soybean/src/assets/svg-icon/menu/skill.svg | 1
ruoyi-plus-soybean/src/layouts/modules/global-logo/index.vue | 27
RuoYi-Vue-Plus/ruoyi-common/.DS_Store | 0
ruoyi-plus-soybean/packages/axios/src/options.ts | 60
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestEncryptController.java | 55
ruoyi-plus-soybean/src/typings/union-key.d.ts | 165
ruoyi-plus-soybean/src/components/common/system-logo.vue | 9
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysPostBo.java | 75
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml | 7
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/EncodeType.java | 26
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/ConditionalOnEnable.java | 29
ruoyi-plus-soybean/src/utils/crypto.ts | 70
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/.flattened-pom.xml | 32
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java | 35
ruoyi-plus-soybean/pnpm-lock.yaml | 10758 ++++
RuoYi-Vue-Plus/script/sql/update/postgres/update_5.4.1-5.5.0.sql | 99
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisRateLimiterController.java | 64
ruoyi-plus-soybean/src/assets/svg-icon/menu/people.svg | 1
ruoyi-plus-soybean/src/views/system/menu/modules/menu-operate-drawer.vue | 511
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOss.java | 55
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml | 51
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/CaptchaProperties.java | 31
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/RollerTimeData.java | 126
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/utils/IdGeneratorUtil.java | 129
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/vo/PackerTimeDataVo.java | 179
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java | 61
RuoYi-Vue-Plus/script/sql/update/update_5.0-5.1.sql | 101
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java | 77
ruoyi-plus-soybean/src/views/_builtin/500/index.vue | 7
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java | 70
ruoyi-plus-soybean/src/assets/svg-icon/maxkey.svg | 3
ruoyi-plus-soybean/src/assets/svg-icon/menu/model.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisCacheController.java | 92
ruoyi-plus-soybean/src/assets/svg-icon/menu/icon.svg | 1
ruoyi-plus-soybean/src/assets/svg-icon/no-permission.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/.flattened-pom.xml | 24
ruoyi-plus-soybean/src/assets/svg-icon/menu/component.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/annotation/RateLimiter.java | 47
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/controller/PackerTimeDataController.java | 106
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/impl/PackerTimeDataServiceImpl.java | 157
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/PriorityDemo.java | 24
ruoyi-plus-soybean/src/components/custom/look-forward.vue | 20
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java | 111
ruoyi-plus-soybean/src/assets/svg-icon/menu/redis.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java | 18
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm | 15
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOssConfig.java | 89
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/mapper/RollerTimeDataMapper.java | 28
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/YmlPropertySourceFactory.java | 31
ruoyi-plus-soybean/src/views/demo/tree/modules/tree-search.vue | 77
ruoyi-plus-soybean/src/views/system/notice/modules/notice-operate-drawer.vue | 145
ruoyi-plus-soybean/uno.config.ts | 30
RuoYi-Vue-Plus/script/sql/update/oracle/update_5.1.2-5.2.0.sql | 9
ruoyi-plus-soybean/src/assets/svg-icon/menu/process-definition.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/CompleteTaskDTO.java | 77
ruoyi-plus-soybean/packages/color/src/constant/palette.ts | 356
RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.5.1-5.5.2.sql | 22
ruoyi-plus-soybean/src/assets/svg-icon/menu/chart.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/ITestTreeService.java | 52
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysTenantPackageMapper.xml | 7
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/TestTree.java | 65
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml | 7
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwDefinitionService.java | 78
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/RegexConstants.java | 59
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/PackerTimeDataMapper.xml | 6
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/postgres/sql.vm | 20
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MessageUtils.java | 33
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestSensitiveController.java | 76
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/pom.xml | 89
ruoyi-plus-soybean/src/store/modules/auth/index.ts | 199
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java | 284
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/components/layout-mode-card.vue | 101
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java | 115
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/MonitorAdminApplication.java | 21
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/footer-settings.vue | 61
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java | 235
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/validate/JsonType.java | 30
ruoyi-plus-soybean/packages/color/src/constant/index.ts | 2
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/.flattened-pom.xml | 72
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java | 37
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOperLog.java | 115
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm4Encryptor.java | 55
ruoyi-plus-soybean/src/views/_builtin/login/modules/code-login.vue | 87
ruoyi-plus-soybean/packages/hooks/src/use-request.ts | 82
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysConfigMapper.java | 14
ruoyi-plus-soybean/packages/color/src/index.ts | 7
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/controller/HoisterTimeDataController.java | 106
ruoyi-plus-soybean/src/assets/svg-icon/menu/number.svg | 1
ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.ts | 5
ruoyi-plus-soybean/src/store/modules/notice/index.ts | 50
ruoyi-plus-soybean/src/components/common/pin-toggler.vue | 26
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/OperatorType.java | 23
ruoyi-plus-soybean/src/assets/svg-icon/chrome.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/OssService.java | 29
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/Dockerfile | 22
ruoyi-plus-soybean/src/assets/svg-icon/menu/rate.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDeptService.java | 137
ruoyi-plus-soybean/src/assets/svg-icon/menu/cascader.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/TestDemoEncrypt.java | 29
ruoyi-plus-soybean/src/store/modules/tab/index.ts | 395
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/oracle/sql.vm | 19
ruoyi-plus-soybean/src/components/advanced/table-row-check-alert.vue | 29
ruoyi-plus-soybean/build/plugins/index.ts | 24
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDeptMapper.java | 143
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelListener.java | 14
RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/TagUnitTest.java | 54
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java | 87
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRoleMenu.java | 29
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java | 127
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/UserException.java | 20
ruoyi-plus-soybean/src/plugins/dayjs.ts | 9
ruoyi-plus-soybean/src/views/analy/output-analy/index.vue | 269
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/SysUserImportListener.java | 127
ruoyi-plus-soybean/packages/materials/src/libs/page-tab/chrome-tab-bg.vue | 31
RuoYi-Vue-Plus/script/sql/sqlserver/sqlserver_ry_workflow.sql | 1677
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm | 459
ruoyi-plus-soybean/src/assets/svg-icon/banner.svg | 1
ruoyi-plus-soybean/src/theme/settings.ts | 104
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/VariablesEnum.java | 20
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysRoleService.java | 236
ruoyi-plus-soybean/src/assets/svg-icon/menu/waiting.svg | 1
ruoyi-plus-soybean/src/typings/api/analy.hoister-data.api.d.ts | 155
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/GenUtils.java | 219
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java | 151
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/CaffeineCacheDecorator.java | 97
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java | 469
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/pom.xml | 84
ruoyi-plus-soybean/src/components/custom/json-preview.vue | 52
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysTenantMapper.java | 14
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/XssValidator.java | 21
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java | 72
ruoyi-plus-soybean/src/typings/api/analy.roller-data.api.d.ts | 123
ruoyi-plus-soybean/packages/alova/src/client.ts | 1
ruoyi-plus-soybean/src/views/monitor/online/modules/online-search.vue | 74
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/annotation/Log.java | 48
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java | 268
ruoyi-plus-soybean/src/assets/svg-icon/menu/language.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysLogininforService.java | 53
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java | 141
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/IPackerTimeDataService.java | 69
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTaskBo.java | 69
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/validate/JsonPatternValidator.java | 51
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java | 124
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysMenuMapper.java | 147
ruoyi-plus-soybean/src/views/_builtin/login/index.vue | 107
ruoyi-plus-soybean/src/views/demo/tree/modules/tree-operate-drawer.vue | 166
ruoyi-plus-soybean/src/views/analy/packer/modules/packer-data-operate-drawer.vue | 240
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/AddGroup.java | 9
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/annotation/RepeatSubmit.java | 29
ruoyi-plus-soybean/src/store/plugins/index.ts | 22
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableColumnMapper.java | 15
ruoyi-plus-soybean/src/assets/svg-icon/menu/log.svg | 1
ruoyi-plus-soybean/src/service/api/demo/index.ts | 2
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestBroadcastJob.java | 37
ruoyi-plus-soybean/src/layouts/modules/global-menu/context/index.ts | 215
ruoyi-plus-soybean/src/assets/svg-icon/menu/finish.svg | 1
ruoyi-plus-soybean/src/components/common/dark-mode-container.vue | 17
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/runner/SystemApplicationRunner.java | 28
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysTenantPackageMapper.java | 14
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/XcxLoginBody.java | 28
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java | 104
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/header-settings.vue | 47
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java | 40
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/.DS_Store | 0
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/modules/operate-drawer.vue.vm | 257
ruoyi-plus-soybean/src/views/system/role/modules/role-search.vue | 109
ruoyi-plus-soybean/LICENSE | 21
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils.java | 389
ruoyi-plus-soybean/src/assets/svg-icon/menu/table.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPatternValidator.java | 37
ruoyi-plus-soybean/src/views/_builtin/iframe-page/[url].vue | 15
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/holder/WebSocketSessionHolder.java | 74
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestAnnoJobExecutor.java | 25
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/QueueUtils.java | 239
RuoYi-Vue-Plus/script/leave/leave1.json | 129
ruoyi-plus-soybean/src/assets/svg-icon/menu/caret-forward.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/package-info.md | 3
ruoyi-plus-soybean/src/views/_builtin/login/modules/register.vue | 194
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-bom/pom.xml | 185
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/TestLeaveMapper.xml | 7
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssConfigBo.java | 109
RuoYi-Vue-Plus/script/sql/update/postgres/update_5.1.2-5.2.0.sql | 9
ruoyi-plus-soybean/src/layouts/modules/global-menu/index.vue | 40
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/pom.xml | 99
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessReturnDTO.java | 30
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java | 89
ruoyi-plus-soybean/packages/axios/tsconfig.json | 20
ruoyi-plus-soybean/packages/hooks/package.json | 16
ruoyi-plus-soybean/src/views/system/notice/modules/notice-search.vue | 74
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java | 231
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/SecurityConfig.java | 54
ruoyi-plus-soybean/src/components/custom/dict-tag.vue | 62
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDataScopeService.java | 26
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm2Encryptor.java | 61
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java | 192
ruoyi-plus-soybean/src/assets/svg-icon/menu/excel.svg | 1
ruoyi-plus-soybean/src/typings/app.d.ts | 1130
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/CaptchaConfig.java | 16
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssVo.java | 77
RuoYi-Vue-Plus/script/sql/update/oracle/update_5.1.0-5.1.1.sql | 5
ruoyi-plus-soybean/src/views/system/menu/index.vue | 581
ruoyi-plus-soybean/src/plugins/loading.ts | 34
ruoyi-plus-soybean/src/theme/preset/soybean.json | 91
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java | 168
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/impl/HoisterTimeDataServiceImpl.java | 138
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java | 172
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictTypeService.java | 101
ruoyi-plus-soybean/packages/hooks/src/use-context.ts | 96
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/notifier/CustomNotifier.java | 55
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/SocialLoginBody.java | 35
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java | 575
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/BoundedQueueController.java | 92
ruoyi-plus-soybean/src/views/system/dict/modules/dict-data-operate-drawer.vue | 206
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/.flattened-pom.xml | 35
ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.module.css.d.ts | 18
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/pom.xml | 105
ruoyi-plus-soybean/src/typings/components.d.ts | 312
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserExportVo.java | 96
ruoyi-plus-soybean/packages/scripts/src/index.ts | 109
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContext.java | 41
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/me/zhyd/oauth/request/AbstractAuthWeChatEnterpriseRequest.java | 154
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/constant/WebSocketConstants.java | 29
ruoyi-plus-soybean/src/assets/svg-icon/menu/search.svg | 1
ruoyi-plus-soybean/src/utils/icon-tag-format.ts | 62
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/excel/单列表.xlsx | 0
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java | 24
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleDeptMapper.java | 13
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/index.vue.vm | 198
ruoyi-plus-soybean/src/assets/svg-icon/menu/pdf.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/.flattened-pom.xml | 80
RuoYi-Vue-Plus/script/sql/update/oracle/update_5.4.1-5.5.0.sql | 97
ruoyi-plus-soybean/packages/alova/src/fetch.ts | 2
ruoyi-plus-soybean/src/assets/svg-icon/menu/druid.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysSocialVo.java | 144
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowUrgeTaskBo.java | 38
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java | 168
ruoyi-plus-soybean/src/assets/svg-icon/menu/redis-list.svg | 2
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMenuMapper.java | 26
ruoyi-plus-soybean/src/components/custom/form-tip.vue | 26
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/PriorityQueueController.java | 91
ruoyi-plus-soybean/src/assets/svg-icon/menu/post.svg | 1
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaException.java | 18
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/dicts/DictPatternValidator.java | 55
RuoYi-Vue-Plus/script/sql/update/update_5.5.1-5.5.2.sql | 3
ruoyi-plus-soybean/packages/utils/src/crypto.ts | 27
ruoyi-plus-soybean/src/service/api/index.ts | 2
ruoyi-plus-soybean/packages/color/src/shared/index.ts | 2
ruoyi-plus-soybean/src/service/api/route.ts | 7
ruoyi-plus-soybean/src/views/home/modules/creativity-banner.vue | 17
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java | 155
ruoyi-plus-soybean/packages/uno-preset/src/index.ts | 55
ruoyi-plus-soybean/src/assets/imgs/soybean.jpg | 0
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java | 567
ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/vertical-hybrid-header-first.vue | 173
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/R.java | 110
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceBizExtMapper.java | 61
ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/top-hybrid-header-first.vue | 89
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/package-info.java | 1
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/tab-settings.vue | 62
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/logback-plus.xml | 93
ruoyi-plus-soybean/src/service/api/analy/hoister-data.ts | 35
ruoyi-plus-soybean/src/assets/svg-icon/menu/eye.svg | 1
RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.5.0-5.5.1.sql | 99
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/.flattened-pom.xml | 28
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/vo.java.vm | 66
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelWriterWrapper.java | 127
RuoYi-Vue-Plus/.flattened-pom.xml | 419
ruoyi-plus-soybean/src/service/api/system/oss-config.ts | 45
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDictType.java | 41
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoEncryptMapper.java | 13
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/pom.xml | 55
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/RedisExceptionHandler.java | 30
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java | 261
ruoyi-plus-soybean/packages/materials/tsconfig.json | 20
ruoyi-plus-soybean/src/assets/svg-icon/menu/upload.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml | 7
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCancelBo.java | 31
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/Dockerfile | 23
ruoyi-plus-soybean/src/components/custom/wave-bg.vue | 524
RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/AssertUnitTest.java | 45
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRole.java | 79
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssService.java | 80
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/manager/TenantSpringCacheManager.java | 41
ruoyi-plus-soybean/src/styles/css/reset.css | 385
ruoyi-plus-soybean/src/router/guard/index.ts | 15
ruoyi-plus-soybean/src/store/index.ts | 12
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/handler/SmsExceptionHandler.java | 30
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/.flattened-pom.xml | 20
ruoyi-plus-soybean/src/assets/svg-icon/heart.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/CacheController.java | 65
RuoYi-Vue-Plus/script/sql/update/postgres/update_5.1.0-5.1.1.sql | 5
ruoyi-plus-soybean/CHANGELOG.md | 501
ruoyi-plus-soybean/vite.config.ts | 51
ruoyi-plus-soybean/src/views/monitor/logininfor/index.vue | 279
ruoyi-plus-soybean/.npmrc | 4
ruoyi-plus-soybean/src/views/monitor/operlog/modules/oper-log-search.vue | 112
ruoyi-plus-soybean/src/components/custom/dict-checkbox.vue | 28
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/.flattened-pom.xml | 59
RuoYi-Vue-Plus/script/sql/sqlserver/sqlserver_ry_job.sql | 2891 +
ruoyi-plus-soybean/src/layouts/modules/theme-drawer/index.vue | 66
ruoyi-plus-soybean/packages/hooks/src/use-table.ts | 131
ruoyi-plus-soybean/packages/scripts/src/commands/index.ts | 6
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/pom.xml | 37
ruoyi-plus-soybean/src/assets/svg-icon/menu/time-range.svg | 1
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java | 859
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/package-info.java | 1
RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/pom.xml | 58
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm | 6
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/WechatBillTask.java | 43
ruoyi-plus-soybean/src/views/analy/output-analy/modules/roller-data-search.vue | 147
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysSocialBo.java | 142
RuoYi-Vue-Plus/script/leave/leave4.json | 154
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/pom.xml | 34
ruoyi-plus-soybean/src/views/system/user/modules/user-operate-drawer.vue | 248
RuoYi-Vue-Plus/script/sql/update/oracle/update_5.1.1-5.1.2.sql | 6
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserRoleMapper.java | 28
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java | 253
RuoYi-Vue-Plus/ruoyi-admin/Dockerfile | 31
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java | 187
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/DromaraServletInitializer.java | 18
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AesEncryptor.java | 55
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysSocial.java | 136
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml | 7
ruoyi-plus-soybean/src/assets/svg-icon/topiam.svg | 29
RuoYi-Vue-Plus/script/sql/postgres/postgres_ry_workflow.sql | 507
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/reflect/ReflectUtils.java | 56
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml | 7
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/pom.xml | 32
ruoyi-plus-soybean/src/layouts/modules/global-footer/index.vue | 13
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfig.java | 37
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/interceptor/PlusWebInvokeTimeInterceptor.java | 127
1,464 files changed, 151,207 insertions(+), 0 deletions(-)
diff --git a/RuoYi-Vue-Plus/.DS_Store b/RuoYi-Vue-Plus/.DS_Store
new file mode 100755
index 0000000..83527bb
--- /dev/null
+++ b/RuoYi-Vue-Plus/.DS_Store
Binary files differ
diff --git a/RuoYi-Vue-Plus/.flattened-pom.xml b/RuoYi-Vue-Plus/.flattened-pom.xml
new file mode 100644
index 0000000..3816295
--- /dev/null
+++ b/RuoYi-Vue-Plus/.flattened-pom.xml
@@ -0,0 +1,419 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-vue-plus</artifactId>
+ <version>5.5.3</version>
+ <packaging>pom</packaging>
+ <name>RuoYi-Vue-Plus</name>
+ <description>Dromara RuoYi-Vue-Plus澶氱鎴风鐞嗙郴缁�</description>
+ <url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
+ <modules>
+ <module>ruoyi-admin</module>
+ <module>ruoyi-common</module>
+ <module>ruoyi-extend</module>
+ <module>ruoyi-modules</module>
+ </modules>
+ <properties>
+ <spring-boot-admin.version>3.5.6</spring-boot-admin.version>
+ <dynamic-ds.version>4.3.1</dynamic-ds.version>
+ <aws.sdk.version>2.28.22</aws.sdk.version>
+ <hutool.version>5.8.43</hutool.version>
+ <spring-boot.version>3.5.10</spring-boot.version>
+ <p6spy.version>3.9.1</p6spy.version>
+ <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
+ <snailjob.version>1.9.0</snailjob.version>
+ <springdoc.version>2.8.15</springdoc.version>
+ <mapstruct-plus.version>1.5.0</mapstruct-plus.version>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <fastjson.version>1.2.83</fastjson.version>
+ <skipTests>true</skipTests>
+ <anyline.version>8.7.3-20251210</anyline.version>
+ <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
+ <lock4j.version>2.2.7</lock4j.version>
+ <warm-flow.version>1.8.4</warm-flow.version>
+ <sms4j.version>3.3.5</sms4j.version>
+ <mybatis.version>3.5.19</mybatis.version>
+ <maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
+ <java.version>17</java.version>
+ <bouncycastle.version>1.80</bouncycastle.version>
+ <maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
+ <fastexcel.version>1.3.0</fastexcel.version>
+ <mybatis-plus.version>3.5.16</mybatis-plus.version>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ <revision>5.5.3</revision>
+ <lombok.version>1.18.42</lombok.version>
+ <maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
+ <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
+ <ip2region.version>3.3.2</ip2region.version>
+ <satoken.version>1.44.0</satoken.version>
+ <redisson.version>3.52.0</redisson.version>
+ <justauth.version>1.16.7</justauth.version>
+ <maven-war-plugin.version>3.4.0</maven-war-plugin.version>
+ <velocity.version>2.3</velocity.version>
+ </properties>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-dependencies</artifactId>
+ <version>${spring-boot.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-bom</artifactId>
+ <version>${hutool.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-bom</artifactId>
+ <version>5.5.3</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
+ <version>${springdoc.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.therapi</groupId>
+ <artifactId>therapi-runtime-javadoc</artifactId>
+ <version>${therapi-javadoc.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>${lombok.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>cn.idev.excel</groupId>
+ <artifactId>fastexcel</artifactId>
+ <version>${fastexcel.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-core</artifactId>
+ <version>${velocity.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-spring-boot3-starter</artifactId>
+ <version>${satoken.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-jwt</artifactId>
+ <version>${satoken.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-all</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-core</artifactId>
+ <version>${satoken.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
+ <version>${dynamic-ds.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.mybatis</groupId>
+ <artifactId>mybatis</artifactId>
+ <version>${mybatis.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+ <version>${mybatis-plus.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-jsqlparser</artifactId>
+ <version>${mybatis-plus.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-annotation</artifactId>
+ <version>${mybatis-plus.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>p6spy</groupId>
+ <artifactId>p6spy</artifactId>
+ <version>${p6spy.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>s3</artifactId>
+ <version>${aws.sdk.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>s3-transfer-manager</artifactId>
+ <version>${aws.sdk.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>netty-nio-client</artifactId>
+ <version>${aws.sdk.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara.sms4j</groupId>
+ <artifactId>sms4j-spring-boot-starter</artifactId>
+ <version>${sms4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-server</artifactId>
+ <version>${spring-boot-admin.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-client</artifactId>
+ <version>${spring-boot-admin.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.redisson</groupId>
+ <artifactId>redisson-spring-boot-starter</artifactId>
+ <version>${redisson.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
+ <version>${lock4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.aizuda</groupId>
+ <artifactId>snail-job-client-starter</artifactId>
+ <version>${snailjob.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.aizuda</groupId>
+ <artifactId>snail-job-client-job-core</artifactId>
+ <version>${snailjob.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15to18</artifactId>
+ <version>${bouncycastle.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.github.linpeilie</groupId>
+ <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
+ <version>${mapstruct-plus.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara.warm</groupId>
+ <artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
+ <version>${warm-flow.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara.warm</groupId>
+ <artifactId>warm-flow-plugin-ui-sb-web</artifactId>
+ <version>${warm-flow.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>me.zhyd.oauth</groupId>
+ <artifactId>JustAuth</artifactId>
+ <version>${justauth.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.lionsoul</groupId>
+ <artifactId>ip2region</artifactId>
+ <version>${ip2region.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>fastjson</artifactId>
+ <version>${fastjson.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-system</artifactId>
+ <version>5.5.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-job</artifactId>
+ <version>5.5.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-generator</artifactId>
+ <version>5.5.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-demo</artifactId>
+ <version>5.5.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-qa</artifactId>
+ <version>5.5.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-workflow</artifactId>
+ <version>5.5.3</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ <repositories>
+ <repository>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ <id>public</id>
+ <name>huawei nexus</name>
+ <url>https://mirrors.huaweicloud.com/repository/maven/</url>
+ </repository>
+ </repositories>
+ <pluginRepositories>
+ <pluginRepository>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <id>public</id>
+ <name>huawei nexus</name>
+ <url>https://mirrors.huaweicloud.com/repository/maven/</url>
+ </pluginRepository>
+ </pluginRepositories>
+ <build>
+ <resources>
+ <resource>
+ <filtering>false</filtering>
+ <directory>src/main/resources</directory>
+ </resource>
+ <resource>
+ <filtering>true</filtering>
+ <directory>src/main/resources</directory>
+ <includes>
+ <include>application*</include>
+ <include>bootstrap*</include>
+ <include>banner*</include>
+ </includes>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>${maven-compiler-plugin.version}</version>
+ <configuration>
+ <source>${java.version}</source>
+ <target>${java.version}</target>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ <annotationProcessorPaths>
+ <path>
+ <groupId>com.github.therapi</groupId>
+ <artifactId>therapi-runtime-javadoc-scribe</artifactId>
+ <version>${therapi-javadoc.version}</version>
+ </path>
+ <path>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>${lombok.version}</version>
+ </path>
+ <path>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-configuration-processor</artifactId>
+ <version>${spring-boot.version}</version>
+ </path>
+ <path>
+ <groupId>io.github.linpeilie</groupId>
+ <artifactId>mapstruct-plus-processor</artifactId>
+ <version>${mapstruct-plus.version}</version>
+ </path>
+ <path>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok-mapstruct-binding</artifactId>
+ <version>${mapstruct-plus.lombok.version}</version>
+ </path>
+ </annotationProcessorPaths>
+ <compilerArgs>
+ <arg>-parameters</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${maven-surefire-plugin.version}</version>
+ <configuration>
+ <argLine>-Dfile.encoding=UTF-8</argLine>
+ <groups>${profiles.active}</groups>
+ <excludedGroups>exclude</excludedGroups>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>flatten-maven-plugin</artifactId>
+ <version>${flatten-maven-plugin.version}</version>
+ <executions>
+ <execution>
+ <id>flatten</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>flatten</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>flatten.clean</id>
+ <phase>clean</phase>
+ <goals>
+ <goal>clean</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <updatePomFile>true</updatePomFile>
+ <flattenMode>resolveCiFriendliesOnly</flattenMode>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <profiles>
+ <profile>
+ <id>local</id>
+ <properties>
+ <monitor.username>ruoyi</monitor.username>
+ <profiles.active>local</profiles.active>
+ <monitor.password>123456</monitor.password>
+ <logging.level>info</logging.level>
+ </properties>
+ </profile>
+ <profile>
+ <id>dev</id>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ <properties>
+ <monitor.username>ruoyi</monitor.username>
+ <profiles.active>dev</profiles.active>
+ <monitor.password>123456</monitor.password>
+ <logging.level>info</logging.level>
+ </properties>
+ </profile>
+ <profile>
+ <id>prod</id>
+ <properties>
+ <monitor.username>ruoyi</monitor.username>
+ <profiles.active>prod</profiles.active>
+ <monitor.password>123456</monitor.password>
+ <logging.level>warn</logging.level>
+ </properties>
+ </profile>
+ </profiles>
+</project>
diff --git a/RuoYi-Vue-Plus/LICENSE b/RuoYi-Vue-Plus/LICENSE
new file mode 100755
index 0000000..32b3071
--- /dev/null
+++ b/RuoYi-Vue-Plus/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2019 RuoYi-Vue-Plus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/RuoYi-Vue-Plus/README.md b/RuoYi-Vue-Plus/README.md
new file mode 100755
index 0000000..acd1400
--- /dev/null
+++ b/RuoYi-Vue-Plus/README.md
@@ -0,0 +1,189 @@
+<img src="https://foruda.gitee.com/images/1679673773341074847/178e8451_1766278.png" width="50%" height="50%">
+<div style="height: 10px; clear: both;"></div>
+
+- - -
+## 骞冲彴绠�浠�
+
+[](https://gitee.com/dromara/RuoYi-Vue-Plus)
+[](https://github.com/dromara/RuoYi-Vue-Plus)
+[](https://gitcode.com/dromara/RuoYi-Vue-Plus)
+[](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE)
+<br>
+[](https://gitee.com/dromara/RuoYi-Vue-Plus)
+[]()
+[]()
+[]()
+
+> Dromara RuoYi-Vue-Plus 鏄噸鍐� RuoYi-Vue 閽堝 `鍒嗗竷寮忛泦缇や笌澶氱鎴穈 鍦烘櫙鍏ㄦ柟浣嶅崌绾�(涓嶅吋瀹瑰師妗嗘灦)
+
+> 椤圭洰浠g爜銆佹枃妗� 鍧囧紑婧愬厤璐瑰彲鍟嗙敤 閬靛惊寮�婧愬崗璁湪椤圭洰涓繚鐣欏紑婧愬崗璁枃浠跺嵆鍙�<br>
+娲诲埌鑰佸啓鍒拌�� 涓哄叴瓒h�屽紑婧� 涓哄涔犺�屽紑婧� 涓鸿澶у鐪熸鍙互瀛﹀埌鎶�鏈�屽紑婧�
+
+> 绯荤粺婕旂ず: [浼犻�侀棬](https://plus-doc.dromara.org/#/common/demo_system)
+
+> 瀹樻柟鍓嶇椤圭洰鍦板潃: [gitee](https://gitee.com/JavaLionLi/plus-ui) - [github](https://github.com/JavaLionLi/plus-ui) - [gitcode](https://gitcode.com/dromara/plus-ui)<br>
+> 鎴愬憳鍓嶇椤圭洰鍦板潃: 鍩轰簬vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)<br>
+> 鎴愬憳鍓嶇椤圭洰鍦板潃: 鍩轰簬soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)<br>
+> 鎴愬憳椤圭洰鍦板潃: 鍒犻櫎澶氱鎴蜂笌宸ヤ綔娴� [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)<br>
+
+> 鏂囨。鍦板潃: [plus-doc](https://plus-doc.dromara.org) 鍥藉唴鍔犻��: [plus-doc.top](https://plus-doc.top)
+
+## 璧炲姪鍟�
+
+MaxKey 涓氱晫棰嗗厛鍗曠偣鐧诲綍浜у搧 - https://gitee.com/dromara/MaxKey <br>
+CCFlow 椹拌仒浣庝唬鐮�-娴佺▼-琛ㄥ崟 - https://gitee.com/opencc/RuoYi-JFlow <br>
+鏁拌埖绉戞妧 杞欢瀹氬埗寮�鍙慉PP灏忕▼搴忕瓑 - https://www.shuduokeji.com/ <br>
+寮曡繄淇℃伅 杞欢寮�鍙戝钩鍙� - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
+Mall4J 楂樿川閲廕ava鍟嗗煄绯荤粺 - https://www.mall4j.com/cn/?statId=11 <br>
+aizuda flowlong 宸ヤ綔娴� - https://gitee.com/aizuda/flowlong <br>
+Ruoyi-Plus-Uniapp - https://ruoyi.plus <br>
+Topiam IAM/IDaaS韬唤绠$悊骞冲彴 - https://www.topiam.cn/ <br>
+
+[濡備綍鎴愪负璧炲姪鍟� 鍔犵兢鑱旂郴浣滆�呰璋� 姣忔棩PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group)
+
+# 鏈鏋朵笌RuoYi鐨勫姛鑳藉樊寮�
+
+| 鍔熻兘 | 鏈鏋� | RuoYi |
+|-------------|-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
+| 鍓嶇椤圭洰 | 閲囩敤 Vue3 + TS + ElementPlus 閲嶅啓 | 鍩轰簬Vue2/Vue3 + JS |
+| 鍚庣椤圭洰缁撴瀯 | 閲囩敤鎻掍欢鍖� + 鎵╁睍鍖呭舰寮� 缁撴瀯瑙h�� 鏄撲簬鎵╁睍 | 妯″潡鐩镐簰娉ㄥ叆鑰﹀悎涓ラ噸闅句互鎵╁睍 |
+| 鍚庣浠g爜椋庢牸 | 涓ユ牸閬靛畧Alibaba瑙勮寖涓庨」鐩粺涓�閰嶇疆鐨勪唬鐮佹牸寮忓寲 | 浠g爜涔﹀啓涓庡父瑙勭粨鏋勪笉鍚岄槄璇婚殰纰嶅ぇ |
+| Web瀹瑰櫒 | 閲囩敤 Undertow 鍩轰簬 XNIO 鐨勯珮鎬ц兘瀹瑰櫒 | 閲囩敤 Tomcat |
+| 鏉冮檺璁よ瘉 | 閲囩敤 Sa-Token銆丣wt 闈欐�佷娇鐢ㄥ姛鑳介綈鍏� 浣庤�﹀悎 楂樻墿灞� | Spring Security 閰嶇疆绻佺悙鎵╁睍鎬ф瀬宸� |
+| 鏉冮檺娉ㄨВ | 閲囩敤 Sa-Token 鏀寔娉ㄨВ 鐧诲綍鏍¢獙銆佽鑹叉牎楠屻�佹潈闄愭牎楠屻�佷簩绾ц璇佹牎楠屻�丠ttpBasic鏍¢獙銆佸拷鐣ユ牎楠�<br/>瑙掕壊涓庢潈闄愭牎楠屾敮鎸佸绉嶆潯浠� 濡� `AND` `OR` 鎴� `鏉冮檺 OR 瑙掕壊` 绛夊鏉傝〃杈惧紡 | 鍙敮鎸佹槸鍚﹀瓨鍦ㄥ尮閰� |
+| 涓夋柟閴存潈 | 閲囩敤 JustAuth 绗笁鏂圭櫥褰曠粍浠� 鏀寔寰俊銆侀拤閽夌瓑鏁板崄绉嶄笁鏂硅璇� | 鏃� |
+| 鍏崇郴鏁版嵁搴撴敮鎸� | 鍘熺敓鏀寔 MySQL銆丱racle銆丳ostgreSQL銆丼QLServer<br/>鍙悓鏃朵娇鐢ㄥ紓鏋勫垏鎹�(鏀寔鍏朵粬 mybatis-plus 鏀寔鐨勬墍鏈夋暟鎹簱 鍙渶瑕佸鍔爅dbc渚濊禆鍗冲彲浣跨敤 杈炬ⅵ閲戜粨绛夊潎鏈夋垚鍔熸渚�) | 鏀寔 Mysql銆丱racle 涓嶆敮鎸佸悓鏃朵娇鐢ㄣ�佷笉鏀寔寮傛瀯鍒囨崲 |
+| 缂撳瓨鏁版嵁搴� | 鏀寔 Redis 5-7 鏀寔澶ч儴鍒嗘柊鍔熻兘鐗规�� 濡� 鍒嗗竷寮忛檺娴併�佸垎甯冨紡闃熷垪 | Redis 绠�鍗� get set 鏀寔 |
+| Redis瀹㈡埛绔� | 閲囩敤 Redisson Redis瀹樻柟鎺ㄨ崘 鍩轰簬Netty鐨勫鎴风宸ュ叿<br/>鏀寔Redis 90%浠ヤ笂鐨勫懡浠� 搴曞眰浼樺寲瑙勯伩寰堝涓嶆纭殑鐢ㄦ硶 渚嬪: keys琚浆鎹负scan<br/>鏀寔鍗曟満銆佸摠鍏点�佸崟涓婚泦缇ゃ�佸涓婚泦缇ょ瓑妯″紡 | Lettuce + RedisTemplate 鏀寔妯″紡灏� 宸ュ叿浣跨敤绻佺悙<br/>杩炴帴姹犻噰鐢� common-pool Bug澶氱粡甯告�у嚭闂 |
+| 缂撳瓨娉ㄨВ | 閲囩敤 Spring-Cache 娉ㄨВ 瀵瑰叾鎵╁睍浜嗗疄鐜版敮鎸佷簡鏇村鍔熻兘<br/>渚嬪 杩囨湡鏃堕棿 鏈�澶х┖闂叉椂闂� 缁勬渶澶ч暱搴︾瓑 鍙渶涓�涓敞瑙e嵆鍙畬鎴愭暟鎹嚜鍔ㄧ紦瀛� | 闇�鎵嬪姩缂栧啓Redis浠g爜閫昏緫 |
+| ORM妗嗘灦 | 閲囩敤 Mybatis-Plus 鍩轰簬瀵硅薄鍑犱箮涓嶇敤鍐橲QL鍏╦ava鎿嶄綔 鍔熻兘寮哄ぇ鎻掍欢浼楀<br/>渚嬪澶氱鎴锋彃浠� 鍒嗛〉鎻掍欢 涔愯閿佹彃浠剁瓑绛� | 閲囩敤 Mybatis 鍩轰簬XML闇�瑕佹墜鍐橲QL |
+| SQL鐩戞帶 | 閲囩敤 p6spy 鍙緭鍑哄畬鏁碨QL涓庢墽琛屾椂闂寸洃鎺� | log杈撳嚭 闇�鎵嬪姩鎷兼帴sql涓庡弬鏁版棤娉曞揩閫熸煡鐪嬭皟璇曢棶棰� |
+| 鏁版嵁鍒嗛〉 | 閲囩敤 Mybatis-Plus 鍒嗛〉鎻掍欢<br/>妗嗘灦瀵瑰叾杩涜浜嗘墿灞� 瀵硅薄鍖栧垎椤靛璞� 鏀寔澶氱鏂瑰紡浼犲弬 鏀寔鍓嶇澶氭帓搴� 澶嶆潅鎺掑簭 | 閲囩敤 PageHelper 浠呮敮鎸佸崟鏌ヨ鍒嗛〉 鍙傛暟鍙兘浠巔aram浼� 鍙兘鍗曟帓搴� 鍔熻兘鎵╁睍鎬у樊 浣撻獙涓嶅ソ |
+| 鏁版嵁鏉冮檺 | 閲囩敤 Mybatis-Plus 鎻掍欢 鑷鍒嗘瀽鎷兼帴SQL 鏃犳劅寮忚繃婊�<br/>鍙渶涓篗apper璁剧疆濂芥敞瑙f潯浠� 鏀寔澶氱鑷畾涔� 涓嶉檺浜庨儴闂ㄨ鑹� | 閲囩敤 娉ㄨВ+aop 瀹炵幇 鍩轰簬閮ㄩ棬瑙掕壊 鐢熸垚鐨剆ql鍏煎鎬у樊 涓嶆敮鎸佸叾浠栦笟鍔℃墿灞�<br/>鐢熸垚sql鍚庨渶鎵嬪姩鎷兼帴鍒板叿浣撲笟鍔ql涓� 瀵逛簬澶氫釜Mapper鏌ヨ涓嶈捣浣滅敤 |
+| 鏁版嵁鑴辨晱 | 閲囩敤 娉ㄨВ + jackson 搴忓垪鍖栨湡闂磋劚鏁� 鏀寔涓嶅悓妯″潡涓嶅悓鐨勮劚鏁忔潯浠�<br/>鏀寔澶氱绛栫暐 濡傝韩浠借瘉銆佹墜鏈哄彿銆佸湴鍧�銆侀偖绠便�侀摱琛屽崱绛� 鍙嚜琛屾墿灞� | 鏃� |
+| 鏁版嵁鍔犺В瀵� | 閲囩敤 娉ㄨВ + mybatis 鎷︽埅鍣� 瀵瑰瓨鍙栨暟鎹湡闂磋嚜鍔ㄥ姞瑙e瘑<br/>鏀寔澶氱绛栫暐 濡侭ASE64銆丄ES銆丷SA銆丼M2銆丼M4绛� | 鏃� |
+| 鎺ュ彛浼犺緭鍔犲瘑 | 閲囩敤 鍔ㄦ�� AES + RSA 鍔犲瘑璇锋眰 body 姣忎竴娆¤姹傜閽ラ兘涓嶅悓澶у箙搴﹂檷浣庡彲鐮磋В鎬� | 鏃� |
+| 鏁版嵁缈昏瘧 | 閲囩敤 娉ㄨВ + jackson 搴忓垪鍖栨湡闂村姩鎬佷慨鏀规暟鎹� 鏁版嵁杩涜缈昏瘧<br/>鏀寔澶氱妯″紡: `鏄犲皠缈昏瘧` `鐩存帴缈昏瘧` `鍏朵粬鎵╁睍鏉′欢缈昏瘧` 鎺ュ彛鍖栦袱姝ュ嵆鍙畬鎴愯嚜瀹氫箟鎵╁睍 鍐呯疆澶氱缈昏瘧瀹炵幇 | 鏃� |
+| 澶氭暟鎹簮妗嗘灦 | 閲囩敤 dynamic-datasource 鏀寔甯傞潰澶ч儴鍒嗘暟鎹簱<br/>閫氳繃yml閰嶇疆鍗冲彲鍔ㄦ�佺鐞嗗紓鏋勪笉鍚岀绫荤殑鏁版嵁搴� 涔熷彲閫氳繃鍓嶇椤甸潰娣诲姞鏁版嵁婧�<br/>鏀寔spel琛ㄨ揪寮忎粠璇锋眰澶村弬鏁扮瓑鏉′欢鍒囨崲鏁版嵁婧� | 鍩轰簬 druid 鎵嬪姩缂栧啓浠g爜閰嶇疆鏁版嵁婧� 閰嶇疆绻佺悙 鏀寔鎬у樊 |
+| 澶氭暟鎹簮浜嬪姟 | 閲囩敤 dynamic-datasource 鏀寔澶氭暟鎹簮涓嶅悓绉嶇被鐨勬暟鎹簱浜嬪姟鍥炴粴 | 涓嶆敮鎸� |
+| 鏁版嵁搴撹繛鎺ユ睜 | 閲囩敤 HikariCP Spring瀹樻柟鍐呯疆杩炴帴姹� 閰嶇疆绠�鍗� 浠ユ�ц兘涓庣ǔ瀹氭�ч椈鍚嶅ぉ涓� | 閲囩敤 druid bug浼楀 绀惧尯缁存姢宸� 娲昏穬搴︿綆 閰嶇疆浼楀绻佺悙鎬ц兘涓�鑸� |
+| 鏁版嵁搴撲富閿� | 閲囩敤 闆姳ID 鍩轰簬鏃堕棿鎴崇殑 鏈夊簭澧為暱 鍞竴ID 鍐嶄篃涓嶇敤涓哄垎搴撳垎琛� 鏁版嵁鍚堝苟涓婚敭鍐茬獊閲嶅鑰屽彂鎰� | 閲囩敤 鏁版嵁搴撹嚜澧濱D 鏀寔鏁版嵁閲忔湁闄� 涓嶆敮鎸佸鏁版嵁婧愪富閿敮涓� |
+| WebSocket鍗忚 | 鍩轰簬 Spring 灏佽鐨� WebSocket 鍗忚 鎵╁睍浜員oken閴存潈涓庡垎甯冨紡浼氳瘽鍚屾 涓嶅啀鍙槸鍩轰簬鍗曟満鐨勫簾鐗� | 鏃� |
+| SSE鎺ㄩ�� | 閲囩敤 Spring SSE 瀹炵幇 鎵╁睍浜員oken閴存潈涓庡垎甯冨紡浼氳瘽鍚屾 | 鏃� |
+| 搴忓垪鍖� | 閲囩敤 Jackson Spring瀹樻柟鍐呯疆搴忓垪鍖� 闈犺氨!!! | 閲囩敤 fastjson bugjson 杩滆繎闂诲悕 |
+| 鍒嗗竷寮忓箓绛� | 鍙傝�冪編鍥TIS闃查噸绯荤粺绠�鍖栧疄鐜�(缁嗚妭鍙湅鏂囨。) | 鎵嬪姩缂栧啓娉ㄨВ鍩轰簬aop瀹炵幇 |
+| 鍒嗗竷寮忛攣 | 閲囩敤 Lock4j 搴曞眰鍩轰簬 Redisson | 鏃� |
+| 鍒嗗竷寮忎换鍔¤皟搴� | 閲囩敤 SnailJob 澶╃敓鏀寔鍒嗗竷寮� 缁熶竴鐨勭鐞嗕腑蹇� 鏀寔澶氱鏁版嵁搴� 鏀寔鍒嗙墖閲嶈瘯DAG浠诲姟娴佺瓑 | 閲囩敤 Quartz 鍩轰簬鏁版嵁搴撻攣鎬ц兘宸� 闆嗙兢闇�瑕佸仛寰堝閰嶇疆涓庢敼閫� |
+| 鏂囦欢瀛樺偍 | 閲囩敤 Minio 鍒嗗竷寮忔枃浠跺瓨鍌� 澶╃敓鏀寔澶氭満銆佸纭洏銆佸鍒嗙墖銆佸鍓湰瀛樺偍<br/>鏀寔鏉冮檺绠$悊 瀹夊叏鍙潬 鏂囦欢鍙姞瀵嗗瓨鍌� | 閲囩敤 鏈満鏂囦欢瀛樺偍 鏂囦欢瑁告紡 鏄撲涪澶辨硠婕� 涓嶆敮鎸侀泦缇ゆ湁鍗曠偣鏁堝簲 |
+| 浜戝瓨鍌� | 閲囩敤 AWS S3 鍗忚瀹㈡埛绔� 鏀寔 涓冪墰銆侀樋閲屻�佽吘璁� 绛変竴鍒囨敮鎸丼3鍗忚鐨勫巶瀹� | 涓嶆敮鎸� |
+| 鐭俊 | 閲囩敤 sms4j 鐭俊铻嶅悎鍖� 鏀寔鏁板崄绉嶇煭淇″巶瀹� 鍙渶鍦▂ml閰嶇疆濂藉巶瀹跺瘑閽ュ嵆鍙娇鐢� 鍙鍘傚鍏辩敤 | 涓嶆敮鎸� |
+| 閭欢 | 閲囩敤 mail-api 閫氱敤鍗忚鏀寔澶ч儴鍒嗛偖浠跺巶鍟� | 涓嶆敮鎸� |
+| 鎺ュ彛鏂囨。 | 閲囩敤 SpringDoc銆乯avadoc 鏃犳敞瑙i浂鍏ヤ镜鍩轰簬java娉ㄩ噴<br/>鍙渶鎶婃敞閲婂啓濂� 鏃犻渶鍐嶅啓涓�澶у爢鐨勬枃妗f敞瑙d簡 | 閲囩敤 Springfox 宸插仠姝㈢淮鎶� 闇�瑕佺紪鍐欏ぇ閲忕殑娉ㄨВ鏉ユ敮鎸佹枃妗g敓鎴� |
+| 鏍¢獙妗嗘灦 | 閲囩敤 Validation 鏀寔娉ㄨВ涓庡伐鍏风被鏍¢獙 娉ㄨВ鏀寔鍥介檯鍖� | 浠呮敮鎸佹敞瑙� 涓旀敞瑙d笉鏀寔鍥介檯鍖� |
+| Excel妗嗘灦 | 閲囩敤 FastExcel(鍘烝libaba EasyExcel) 鍩轰簬鎻掍欢鍖�<br/>妗嗘灦瀵瑰叾澧炲姞浜嗗緢澶氬姛鑳� 渚嬪 鑷姩鍚堝苟鐩稿悓鍐呭 鑷姩鎺掑垪甯冨眬 瀛楀吀缈昏瘧绛� | 鍩轰簬 POI 鎵嬪啓瀹炵幇 鍔熻兘鏈夐檺 澶嶆潅 鎵╁睍鎬у樊 |
+| 宸ヤ綔娴佹敮鎸� | 鏀寔鍚勭澶嶆潅瀹℃壒 杞姙 濮旀淳 鍔犲噺绛� 浼氱 鎴栫 绁ㄧ 绛夊姛鑳� | 鏃� |
+| 宸ュ叿绫绘鏋� | 閲囩敤 Hutool銆丩ombok 涓婄櫨绉嶅伐鍏疯鐩�90%鐨勪娇鐢ㄩ渶姹� 鍩轰簬娉ㄨВ鑷姩鐢熸垚 get set 绛夌畝鍖栨鏋跺ぇ閲忎唬鐮� | 鎵嬪啓宸ュ叿绋冲畾鎬у樊鏄撳嚭闂 宸ュ叿鏁伴噺鏈夐檺 浠g爜鑷冭偪闇�鑷繁鎵嬪啓 get set 绛� |
+| 鐩戞帶妗嗘灦 | 閲囩敤 SpringBoot-Admin 鍩轰簬SpringBoot瀹樻柟 actuator 鎺㈤拡鏈哄埗<br/>瀹炴椂鐩戞帶鏈嶅姟鐘舵�� 妗嗘灦杩樹负鍏舵墿灞曚簡鍦ㄧ嚎鏃ュ織鏌ョ湅鐩戞帶 | 鏃� |
+| 閾捐矾杩借釜 | 閲囩敤 Apache SkyWalking 杩樺湪涓鸿姹備笉鐭ラ亾鍘诲摢浜� 鍒板摢鍑轰簡闂鑰岀儲鎭煎悧<br/>鐢ㄤ簡瀹冨嵆鍙疄鏃舵煡鐪嬭姹傜粡杩囩殑姣忎竴澶勬瘡涓�涓妭鐐� | 鏃� |
+| 浠g爜鐢熸垚鍣� | 鍙渶璁捐濂借〃缁撴瀯 涓�閿敓鎴愭墍鏈塩rud浠g爜涓庨〉闈�<br/>闄嶄綆80%鐨勫紑鍙戦噺 鎶婄簿鍔涢兘鎶曞叆鍒颁笟鍔¤璁′笂<br/>妗嗘灦涓哄叾閫傞厤MP銆丼pringDoc瑙勮寖鍖栦唬鐮� 鍚屾椂鏀寔鍔ㄦ�佸鏁版嵁婧愪唬鐮佺敓鎴� | 浠g爜鐢熸垚鍘熺敓缁撴瀯 鍙敮鎸佸崟鏁版嵁婧愮敓鎴� |
+| 閮ㄧ讲鏂瑰紡 | 鏀寔 Docker 缂栨帓 涓�閿惌寤烘墍鏈夌幆澧� 璁╁紑鍙戜汉鍛樹粠姝や笉鍐嶄负鎼缓鐜鑰岀儲鎭� | 鍘熺敓jar閮ㄧ讲 鍏朵粬鐜闇�鎵嬪姩涓嬭浇瀹夎 鑷鎼缓 |
+| 椤圭洰璺緞淇敼 | 鎻愪緵璇︾粏鐨勪慨鏀规柟妗堟枃妗� 骞朵负鍏跺仛浜嗕竴浜涙敼鍔� 闈炲父绠�鍗曞嵆鍙慨鏀规垚鑷繁鎯宠鐨� | 闇�瑕佸仛寰堝鏀归�� 鏂囨。璇存槑鏈夐檺 |
+| 鍥介檯鍖� | 鍩轰簬璇锋眰澶村姩鎬佽繑鍥炰笉鍚岃绉嶇殑鏂囨湰鍐呭 寮�鍙戦毦搴︿綆 鏈夊搴旂殑宸ュ叿绫� 鏀寔澶ч儴鍒嗘敞瑙e唴瀹瑰浗闄呭寲 | 鍙彁渚涘熀纭�鍔熻兘 鍏朵粬闇�鑷缂栧啓鎵╁睍 |
+| 浠g爜鍗曚緥娴嬭瘯 | 鎻愪緵鍗曚緥娴嬭瘯 浣跨敤鏂瑰紡缂栧啓鏂规硶涓巑aven澶氱幆澧冨崟娴嬫彃浠� | 鍙彁渚涘熀纭�鍔熻兘 鍏朵粬闇�鑷缂栧啓鎵╁睍 |
+| Demo妗堜緥 | 鎻愪緵妗嗘灦鍔熻兘鐨勫疄闄呬娇鐢ㄦ渚� 鍗曠嫭涓�涓ā鍧楁彁渚涗簡寰堝寰堝叏 | 鏃� |
+
+
+## 鏈鏋朵笌RuoYi鐨勪笟鍔″樊寮�
+
+| 涓氬姟 | 鍔熻兘璇存槑 | 鏈鏋� | RuoYi |
+|--------|----------------------------------------------------------------------|-----|------------------|
+| 绉熸埛绠$悊 | 绯荤粺鍐呯鎴风殑绠$悊 濡�:绉熸埛濂楅銆佽繃鏈熸椂闂淬�佺敤鎴锋暟閲忋�佷紒涓氫俊鎭瓑 | 鏀寔 | 鏃� |
+| 绉熸埛濂楅绠$悊 | 绯荤粺鍐呯鎴锋墍鑳戒娇鐢ㄧ殑濂楅绠$悊 濡�:濂楅鍐呮墍鍖呭惈鐨勮彍鍗曠瓑 | 鏀寔 | 鏃� |
+| 瀹㈡埛绔鐞� | 绯荤粺鍐呭鎺ョ殑鎵�鏈夊鎴风绠$悊 濡�: pc绔�佸皬绋嬪簭绔瓑<br>鏀寔鍔ㄦ�佹巿鏉冪櫥褰曟柟寮� 濡�: 鐭俊鐧诲綍銆佸瘑鐮佺櫥褰曠瓑 鏀寔鍔ㄦ�佹帶鍒秚oken鏃舵晥 | 鏀寔 | 鏃� |
+| 鐢ㄦ埛绠$悊 | 鐢ㄦ埛鐨勭鐞嗛厤缃� 濡�:鏂板鐢ㄦ埛銆佸垎閰嶇敤鎴锋墍灞為儴闂ㄣ�佽鑹层�佸矖浣嶇瓑 | 鏀寔 | 鏀寔 |
+| 閮ㄩ棬绠$悊 | 閰嶇疆绯荤粺缁勭粐鏈烘瀯锛堝叕鍙搞�侀儴闂ㄣ�佸皬缁勶級 鏍戠粨鏋勫睍鐜版敮鎸佹暟鎹潈闄� | 鏀寔 | 鏀寔 |
+| 宀椾綅绠$悊 | 閰嶇疆绯荤粺鐢ㄦ埛鎵�灞炴媴浠昏亴鍔� | 鏀寔 | 鏀寔 |
+| 鑿滃崟绠$悊 | 閰嶇疆绯荤粺鑿滃崟銆佹搷浣滄潈闄愩�佹寜閽潈闄愭爣璇嗙瓑 | 鏀寔 | 鏀寔 |
+| 瑙掕壊绠$悊 | 瑙掕壊鑿滃崟鏉冮檺鍒嗛厤銆佽缃鑹叉寜鏈烘瀯杩涜鏁版嵁鑼冨洿鏉冮檺鍒掑垎 | 鏀寔 | 鏀寔 |
+| 瀛楀吀绠$悊 | 瀵圭郴缁熶腑缁忓父浣跨敤鐨勪竴浜涜緝涓哄浐瀹氱殑鏁版嵁杩涜缁存姢 | 鏀寔 | 鏀寔 |
+| 鍙傛暟绠$悊 | 瀵圭郴缁熷姩鎬侀厤缃父鐢ㄥ弬鏁� | 鏀寔 | 鏀寔 |
+| 閫氱煡鍏憡 | 绯荤粺閫氱煡鍏憡淇℃伅鍙戝竷缁存姢 | 鏀寔 | 鏀寔 |
+| 鎿嶄綔鏃ュ織 | 绯荤粺姝e父鎿嶄綔鏃ュ織璁板綍鍜屾煡璇� 绯荤粺寮傚父淇℃伅鏃ュ織璁板綍鍜屾煡璇� | 鏀寔 | 鏀寔 |
+| 鐧诲綍鏃ュ織 | 绯荤粺鐧诲綍鏃ュ織璁板綍鏌ヨ鍖呭惈鐧诲綍寮傚父 | 鏀寔 | 鏀寔 |
+| 鏂囦欢绠$悊 | 绯荤粺鏂囦欢灞曠ず銆佷笂浼犮�佷笅杞姐�佸垹闄ょ瓑绠$悊 | 鏀寔 | 鏃� |
+| 鏂囦欢閰嶇疆绠$悊 | 绯荤粺鏂囦欢涓婁紶銆佷笅杞芥墍闇�瑕佺殑閰嶇疆淇℃伅鍔ㄦ�佹坊鍔犮�佷慨鏀广�佸垹闄ょ瓑绠$悊 | 鏀寔 | 鏃� |
+| 鍦ㄧ嚎鐢ㄦ埛绠$悊 | 宸茬櫥褰曠郴缁熺殑鍦ㄧ嚎鐢ㄦ埛淇℃伅鐩戞帶涓庡己鍒惰涪鍑烘搷浣� | 鏀寔 | 鏀寔 |
+| 瀹氭椂浠诲姟 | 杩愯鎶ヨ〃銆佷换鍔$鐞�(娣诲姞銆佷慨鏀广�佸垹闄�)銆佹棩蹇楃鐞嗐�佹墽琛屽櫒绠$悊绛� | 鏀寔 | 浠呮敮鎸佷换鍔′笌鏃ュ織绠$悊 |
+| 浠g爜鐢熸垚 | 澶氭暟鎹簮鍓嶅悗绔唬鐮佺殑鐢熸垚锛坖ava銆乭tml銆亁ml銆乻ql锛夋敮鎸丆RUD涓嬭浇 | 鏀寔 | 浠呮敮鎸佸崟鏁版嵁婧� |
+| 绯荤粺鎺ュ彛 | 鏍规嵁涓氬姟浠g爜鑷姩鐢熸垚鐩稿叧鐨刟pi鎺ュ彛鏂囨。 | 鏀寔 | 鏀寔 |
+| 鏈嶅姟鐩戞帶 | 鐩戣闆嗙兢绯荤粺CPU銆佸唴瀛樸�佺鐩樸�佸爢鏍堛�佸湪绾挎棩蹇椼�丼pring鐩稿叧閰嶇疆绛� | 鏀寔 | 浠呮敮鎸佸崟鏈篊PU銆佸唴瀛樸�佺鐩樼洃鎺� |
+| 缂撳瓨鐩戞帶 | 瀵圭郴缁熺殑缂撳瓨淇℃伅鏌ヨ锛屽懡浠ょ粺璁$瓑銆� | 鏀寔 | 鏀寔 |
+| 浣跨敤妗堜緥 | 绯荤粺鐨勪竴浜涘姛鑳芥渚� | 鏀寔 | 涓嶆敮鎸� |
+
+## 鍙傝�冩枃妗�
+
+浣跨敤妗嗘灦鍓嶈浠旂粏闃呰鏂囨。閲嶇偣娉ㄦ剰浜嬮」
+<br>
+>[鍒濆鍖栭」鐩� 蹇呯湅](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init)
+>>[https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init)
+>
+>[涓撴爮涓庤棰� 鍏ラ棬蹇呯湅](https://plus-doc.dromara.org/#/common/column)
+>>[https://plus-doc.dromara.org/#/common/column](https://plus-doc.dromara.org/#/common/column)
+>
+>[閮ㄧ讲椤圭洰 蹇呯湅](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy)
+>>[https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy)
+>
+>[濡備綍鍔犵兢](https://plus-doc.dromara.org/#/common/add_group)
+>>[https://plus-doc.dromara.org/#/common/add_group](https://plus-doc.dromara.org/#/common/add_group)
+>
+>[鍙傝�冩枃妗� Wiki](https://plus-doc.dromara.org)
+>>[https://plus-doc.dromara.org](https://plus-doc.dromara.org)
+
+## 杞欢鏋舵瀯鍥�
+
+
+
+## 鎹愮尞浣滆��
+浣滆�呬负鍏艰亴鍋氬紑婧�,骞虫椂杩橀渶瑕佸伐浣�,濡傛灉甯埌浜嗘偍鍙互璇蜂綔鑰呭悆涓洅楗�
+<img src="https://foruda.gitee.com/images/1678975784848381069/d8661ed9_1766278.png" width="300px" height="450px" />
+<img src="https://foruda.gitee.com/images/1678975801230205215/6f96229d_1766278.png" width="300px" height="450px" />
+
+## 婕旂ず鍥句緥
+
+| | |
+|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+|  |  |
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/RuoYi-Vue-Plus/pom.xml b/RuoYi-Vue-Plus/pom.xml
new file mode 100755
index 0000000..e269afd
--- /dev/null
+++ b/RuoYi-Vue-Plus/pom.xml
@@ -0,0 +1,506 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-vue-plus</artifactId>
+ <version>${revision}</version>
+
+ <name>RuoYi-Vue-Plus</name>
+ <url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
+ <description>Dromara RuoYi-Vue-Plus澶氱鎴风鐞嗙郴缁�</description>
+
+ <properties>
+ <revision>5.5.3</revision>
+ <spring-boot.version>3.5.10</spring-boot.version>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ <java.version>17</java.version>
+ <mybatis.version>3.5.19</mybatis.version>
+ <springdoc.version>2.8.15</springdoc.version>
+ <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
+ <fastexcel.version>1.3.0</fastexcel.version>
+ <velocity.version>2.3</velocity.version>
+ <satoken.version>1.44.0</satoken.version>
+ <mybatis-plus.version>3.5.16</mybatis-plus.version>
+ <p6spy.version>3.9.1</p6spy.version>
+ <hutool.version>5.8.43</hutool.version>
+ <spring-boot-admin.version>3.5.6</spring-boot-admin.version>
+ <redisson.version>3.52.0</redisson.version>
+ <lock4j.version>2.2.7</lock4j.version>
+ <dynamic-ds.version>4.3.1</dynamic-ds.version>
+ <snailjob.version>1.9.0</snailjob.version>
+ <mapstruct-plus.version>1.5.0</mapstruct-plus.version>
+ <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
+ <lombok.version>1.18.42</lombok.version>
+ <bouncycastle.version>1.80</bouncycastle.version>
+ <justauth.version>1.16.7</justauth.version>
+ <!-- 绂荤嚎IP鍦板潃瀹氫綅搴� -->
+ <ip2region.version>3.3.2</ip2region.version>
+ <!-- OSS 閰嶇疆 -->
+ <aws.sdk.version>2.28.22</aws.sdk.version>
+ <!-- SMS 閰嶇疆 -->
+ <sms4j.version>3.3.5</sms4j.version>
+ <!-- 闄愬埗妗嗘灦涓殑fastjson鐗堟湰 -->
+ <fastjson.version>1.2.83</fastjson.version>
+ <!-- 闈㈠悜杩愯鏃剁殑D-ORM渚濊禆 -->
+ <anyline.version>8.7.3-20251210</anyline.version>
+ <!-- 宸ヤ綔娴侀厤缃� -->
+ <warm-flow.version>1.8.4</warm-flow.version>
+
+ <!-- 鎻掍欢鐗堟湰 -->
+ <maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
+ <maven-war-plugin.version>3.4.0</maven-war-plugin.version>
+ <maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
+ <maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
+ <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
+ <!-- 鎵撳寘榛樿璺宠繃娴嬭瘯 -->
+ <skipTests>true</skipTests>
+ </properties>
+
+ <profiles>
+ <profile>
+ <id>local</id>
+ <properties>
+ <!-- 鐜鏍囪瘑锛岄渶瑕佷笌閰嶇疆鏂囦欢鐨勫悕绉扮浉瀵瑰簲 -->
+ <profiles.active>local</profiles.active>
+ <logging.level>info</logging.level>
+ <monitor.username>ruoyi</monitor.username>
+ <monitor.password>123456</monitor.password>
+ </properties>
+ </profile>
+ <profile>
+ <id>dev</id>
+ <properties>
+ <!-- 鐜鏍囪瘑锛岄渶瑕佷笌閰嶇疆鏂囦欢鐨勫悕绉扮浉瀵瑰簲 -->
+ <profiles.active>dev</profiles.active>
+ <logging.level>info</logging.level>
+ <monitor.username>ruoyi</monitor.username>
+ <monitor.password>123456</monitor.password>
+ </properties>
+ <activation>
+ <!-- 榛樿鐜 -->
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ </profile>
+ <profile>
+ <id>prod</id>
+ <properties>
+ <profiles.active>prod</profiles.active>
+ <logging.level>warn</logging.level>
+ <monitor.username>ruoyi</monitor.username>
+ <monitor.password>123456</monitor.password>
+ </properties>
+ </profile>
+ </profiles>
+
+ <!-- 渚濊禆澹版槑 -->
+ <dependencyManagement>
+ <dependencies>
+
+ <!-- SpringBoot鐨勪緷璧栭厤缃�-->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-dependencies</artifactId>
+ <version>${spring-boot.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+
+ <!-- hutool 鐨勪緷璧栭厤缃�-->
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-bom</artifactId>
+ <version>${hutool.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+
+ <!-- common 鐨勪緷璧栭厤缃�-->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-bom</artifactId>
+ <version>${revision}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
+ <version>${springdoc.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.github.therapi</groupId>
+ <artifactId>therapi-runtime-javadoc</artifactId>
+ <version>${therapi-javadoc.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>${lombok.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.idev.excel</groupId>
+ <artifactId>fastexcel</artifactId>
+ <version>${fastexcel.version}</version>
+ </dependency>
+
+ <!-- velocity浠g爜鐢熸垚浣跨敤妯℃澘 -->
+ <dependency>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-core</artifactId>
+ <version>${velocity.version}</version>
+ </dependency>
+
+ <!-- Sa-Token 鏉冮檺璁よ瘉, 鍦ㄧ嚎鏂囨。锛歨ttp://sa-token.dev33.cn/ -->
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-spring-boot3-starter</artifactId>
+ <version>${satoken.version}</version>
+ </dependency>
+ <!-- Sa-Token 鏁村悎 jwt -->
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-jwt</artifactId>
+ <version>${satoken.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-all</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-core</artifactId>
+ <version>${satoken.version}</version>
+ </dependency>
+
+ <!-- dynamic-datasource 澶氭暟鎹簮-->
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
+ <version>${dynamic-ds.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mybatis</groupId>
+ <artifactId>mybatis</artifactId>
+ <version>${mybatis.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+ <version>${mybatis-plus.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-jsqlparser</artifactId>
+ <version>${mybatis-plus.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-annotation</artifactId>
+ <version>${mybatis-plus.version}</version>
+ </dependency>
+
+ <!-- sql鎬ц兘鍒嗘瀽鎻掍欢 -->
+ <dependency>
+ <groupId>p6spy</groupId>
+ <artifactId>p6spy</artifactId>
+ <version>${p6spy.version}</version>
+ </dependency>
+
+ <!-- AWS SDK for Java 2.x -->
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>s3</artifactId>
+ <version>${aws.sdk.version}</version>
+ </dependency>
+ <!-- 瀹㈡埛绔殑鎬ц兘澧炲己浼犺緭绠$悊鍣� -->
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>s3-transfer-manager</artifactId>
+ <version>${aws.sdk.version}</version>
+ </dependency>
+ <!-- 閫傜敤浜� Netty 鐨勫鎴风 -->
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>netty-nio-client</artifactId>
+ <version>${aws.sdk.version}</version>
+ </dependency>
+ <!--鐭俊sms4j-->
+ <dependency>
+ <groupId>org.dromara.sms4j</groupId>
+ <artifactId>sms4j-spring-boot-starter</artifactId>
+ <version>${sms4j.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-server</artifactId>
+ <version>${spring-boot-admin.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-client</artifactId>
+ <version>${spring-boot-admin.version}</version>
+ </dependency>
+
+ <!--redisson-->
+ <dependency>
+ <groupId>org.redisson</groupId>
+ <artifactId>redisson-spring-boot-starter</artifactId>
+ <version>${redisson.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
+ <version>${lock4j.version}</version>
+ </dependency>
+
+ <!-- SnailJob Client -->
+ <dependency>
+ <groupId>com.aizuda</groupId>
+ <artifactId>snail-job-client-starter</artifactId>
+ <version>${snailjob.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.aizuda</groupId>
+ <artifactId>snail-job-client-job-core</artifactId>
+ <version>${snailjob.version}</version>
+ </dependency>
+
+ <!-- 鍔犲瘑鍖呭紩鍏� -->
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15to18</artifactId>
+ <version>${bouncycastle.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.github.linpeilie</groupId>
+ <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
+ <version>${mapstruct-plus.version}</version>
+ </dependency>
+
+ <!-- Warm-Flow鍥戒骇宸ヤ綔娴佸紩鎿�, 鍦ㄧ嚎鏂囨。锛歨ttp://warm-flow.cn/ -->
+ <dependency>
+ <groupId>org.dromara.warm</groupId>
+ <artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
+ <version>${warm-flow.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara.warm</groupId>
+ <artifactId>warm-flow-plugin-ui-sb-web</artifactId>
+ <version>${warm-flow.version}</version>
+ </dependency>
+
+ <!-- JustAuth 鐨勪緷璧栭厤缃�-->
+ <dependency>
+ <groupId>me.zhyd.oauth</groupId>
+ <artifactId>JustAuth</artifactId>
+ <version>${justauth.version}</version>
+ </dependency>
+
+ <!-- 绂荤嚎IP鍦板潃瀹氫綅搴� ip2region -->
+ <dependency>
+ <groupId>org.lionsoul</groupId>
+ <artifactId>ip2region</artifactId>
+ <version>${ip2region.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>fastjson</artifactId>
+ <version>${fastjson.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-system</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-job</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-generator</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-demo</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-qa</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 宸ヤ綔娴佹ā鍧� -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-workflow</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ </dependencies>
+ </dependencyManagement>
+
+ <modules>
+ <module>ruoyi-admin</module>
+ <module>ruoyi-common</module>
+ <module>ruoyi-extend</module>
+ <module>ruoyi-modules</module>
+ </modules>
+ <packaging>pom</packaging>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>${maven-compiler-plugin.version}</version>
+ <configuration>
+ <source>${java.version}</source>
+ <target>${java.version}</target>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ <annotationProcessorPaths>
+ <path>
+ <groupId>com.github.therapi</groupId>
+ <artifactId>therapi-runtime-javadoc-scribe</artifactId>
+ <version>${therapi-javadoc.version}</version>
+ </path>
+ <path>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>${lombok.version}</version>
+ </path>
+ <path>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-configuration-processor</artifactId>
+ <version>${spring-boot.version}</version>
+ </path>
+ <path>
+ <groupId>io.github.linpeilie</groupId>
+ <artifactId>mapstruct-plus-processor</artifactId>
+ <version>${mapstruct-plus.version}</version>
+ </path>
+ <path>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok-mapstruct-binding</artifactId>
+ <version>${mapstruct-plus.lombok.version}</version>
+ </path>
+ </annotationProcessorPaths>
+ <compilerArgs>
+ <arg>-parameters</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ <!-- 鍗曞厓娴嬭瘯浣跨敤 -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${maven-surefire-plugin.version}</version>
+ <configuration>
+ <argLine>-Dfile.encoding=UTF-8</argLine>
+ <!-- 鏍规嵁鎵撳寘鐜鎵ц瀵瑰簲鐨凘Tag娴嬭瘯鏂规硶 -->
+ <groups>${profiles.active}</groups>
+ <!-- 鎺掗櫎鏍囩 -->
+ <excludedGroups>exclude</excludedGroups>
+ </configuration>
+ </plugin>
+ <!-- 缁熶竴鐗堟湰鍙风鐞� -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>flatten-maven-plugin</artifactId>
+ <version>${flatten-maven-plugin.version}</version>
+ <configuration>
+ <updatePomFile>true</updatePomFile>
+ <flattenMode>resolveCiFriendliesOnly</flattenMode>
+ </configuration>
+ <executions>
+ <execution>
+ <id>flatten</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>flatten</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>flatten.clean</id>
+ <phase>clean</phase>
+ <goals>
+ <goal>clean</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <!-- 鍏抽棴杩囨护 -->
+ <filtering>false</filtering>
+ </resource>
+ <resource>
+ <directory>src/main/resources</directory>
+ <!-- 寮曞叆鎵�鏈� 鍖归厤鏂囦欢杩涜杩囨护 -->
+ <includes>
+ <include>application*</include>
+ <include>bootstrap*</include>
+ <include>banner*</include>
+ </includes>
+ <!-- 鍚敤杩囨护 鍗宠璧勬簮涓殑鍙橀噺灏嗕細琚繃婊ゅ櫒涓殑鍊兼浛鎹� -->
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </build>
+
+ <repositories>
+ <repository>
+ <id>public</id>
+ <name>huawei nexus</name>
+ <url>https://mirrors.huaweicloud.com/repository/maven/</url>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ </repository>
+ </repositories>
+
+ <pluginRepositories>
+ <pluginRepository>
+ <id>public</id>
+ <name>huawei nexus</name>
+ <url>https://mirrors.huaweicloud.com/repository/maven/</url>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </pluginRepository>
+ </pluginRepositories>
+
+</project>
+
+
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/.DS_Store b/RuoYi-Vue-Plus/ruoyi-admin/.DS_Store
new file mode 100755
index 0000000..47a7058
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/.DS_Store
Binary files differ
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-admin/.flattened-pom.xml
new file mode 100644
index 0000000..bd636d6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/.flattened-pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-vue-plus</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-admin</artifactId>
+ <version>5.5.3</version>
+ <description>web鏈嶅姟鍏ュ彛</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.postgresql</groupId>
+ <artifactId>postgresql</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-social</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-ratelimiter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mail</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-system</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-job</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-generator</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-demo</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-qa</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-workflow</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <finalName>${project.artifactId}</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring-boot.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>${maven-jar-plugin.version}</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-war-plugin</artifactId>
+ <version>${maven-war-plugin.version}</version>
+ <configuration>
+ <failOnMissingWebXml>false</failOnMissingWebXml>
+ <warName>${project.artifactId}</warName>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/Dockerfile b/RuoYi-Vue-Plus/ruoyi-admin/Dockerfile
new file mode 100755
index 0000000..0394ccb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/Dockerfile
@@ -0,0 +1,31 @@
+# 璐濆皵瀹為獙瀹� Spring 瀹樻柟鎺ㄨ崘闀滃儚 JDK涓嬭浇鍦板潃 https://bell-sw.com/pages/downloads/
+FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
+#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
+#FROM findepi/graalvm:java17-native
+
+LABEL maintainer="Lion Li"
+
+RUN mkdir -p /ruoyi/server/logs \
+ /ruoyi/server/temp \
+ /ruoyi/skywalking/agent
+
+WORKDIR /ruoyi/server
+
+ENV SERVER_PORT=8080 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
+
+EXPOSE ${SERVER_PORT}
+# 鏆撮湶 snail job 瀹㈡埛绔鍙� 鐢ㄤ簬瀹氭椂浠诲姟璋冨害涓績閫氫俊
+EXPOSE ${SNAIL_PORT}
+
+ADD ./target/ruoyi-admin.jar ./app.jar
+
+SHELL ["/bin/bash", "-c"]
+
+ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
+ -Dsnail-job.port=${SNAIL_PORT} \
+ # 搴旂敤鍚嶇О 濡傛灉鎯冲尯鍒嗛泦缇よ妭鐐圭洃鎺� 鏀规垚涓嶅悓鐨勫悕绉板嵆鍙�
+ #-Dskywalking.agent.service_name=ruoyi-server \
+ #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
+ -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
+ -jar app.jar
+
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/pom.xml b/RuoYi-Vue-Plus/ruoyi-admin/pom.xml
new file mode 100755
index 0000000..25600a1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/pom.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>ruoyi-vue-plus</artifactId>
+ <groupId>org.dromara</groupId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>jar</packaging>
+ <artifactId>ruoyi-admin</artifactId>
+
+ <description>
+ web鏈嶅姟鍏ュ彛
+ </description>
+
+ <dependencies>
+
+ <!-- Mysql椹卞姩鍖� -->
+<!-- <dependency>-->
+<!-- <groupId>com.mysql</groupId>-->
+<!-- <artifactId>mysql-connector-j</artifactId>-->
+<!-- </dependency>-->
+
+<!-- <!– mp鏀寔鐨勬暟鎹簱鍧囨敮鎸� 鍙渶瑕佸鍔犲搴旂殑jdbc渚濊禆鍗冲彲 –>-->
+<!-- <!– Oracle –>-->
+<!-- <dependency>-->
+<!-- <groupId>com.oracle.database.jdbc</groupId>-->
+<!-- <artifactId>ojdbc8</artifactId>-->
+<!-- </dependency>-->
+<!-- <!– 鍏煎oracle浣庣増鏈� –>-->
+<!-- <dependency>-->
+<!-- <groupId>com.oracle.database.nls</groupId>-->
+<!-- <artifactId>orai18n</artifactId>-->
+<!-- </dependency>-->
+ <!-- PostgreSql -->
+ <dependency>
+ <groupId>org.postgresql</groupId>
+ <artifactId>postgresql</artifactId>
+ </dependency>
+<!-- <!– SqlServer –>-->
+<!-- <dependency>-->
+<!-- <groupId>com.microsoft.sqlserver</groupId>-->
+<!-- <artifactId>mssql-jdbc</artifactId>-->
+<!-- </dependency>-->
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-social</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-ratelimiter</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mail</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-system</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-job</artifactId>
+ </dependency>
+
+ <!-- 浠g爜鐢熸垚-->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-generator</artifactId>
+ </dependency>
+
+ <!-- demo妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-demo</artifactId>
+ </dependency>
+
+ <!-- 璐ㄩ噺鍒嗘瀽妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-qa</artifactId>
+ </dependency>
+
+ <!-- 宸ヤ綔娴佹ā鍧� -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-workflow</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-client</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- skywalking 鏁村悎 logback -->
+<!-- <dependency>-->
+<!-- <groupId>org.apache.skywalking</groupId>-->
+<!-- <artifactId>apm-toolkit-logback-1.x</artifactId>-->
+<!-- <version>${涓庝綘鐨刟gent鎺㈤拡鐗堟湰淇濇寔涓�鑷磢</version>-->
+<!-- </dependency>-->
+<!-- <dependency>-->
+<!-- <groupId>org.apache.skywalking</groupId>-->
+<!-- <artifactId>apm-toolkit-trace</artifactId>-->
+<!-- <version>${涓庝綘鐨刟gent鎺㈤拡鐗堟湰淇濇寔涓�鑷磢</version>-->
+<!-- </dependency>-->
+
+ </dependencies>
+
+ <build>
+ <finalName>${project.artifactId}</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring-boot.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>${maven-jar-plugin.version}</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-war-plugin</artifactId>
+ <version>${maven-war-plugin.version}</version>
+ <configuration>
+ <failOnMissingWebXml>false</failOnMissingWebXml>
+ <warName>${project.artifactId}</warName>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/DromaraApplication.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/DromaraApplication.java
new file mode 100755
index 0000000..8ef33fe
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/DromaraApplication.java
@@ -0,0 +1,23 @@
+package org.dromara;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
+
+/**
+ * 鍚姩绋嬪簭
+ *
+ * @author Lion Li
+ */
+
+@SpringBootApplication
+public class DromaraApplication {
+
+ public static void main(String[] args) {
+ SpringApplication application = new SpringApplication(DromaraApplication.class);
+ application.setApplicationStartup(new BufferingApplicationStartup(2048));
+ application.run(args);
+ System.out.println("(鈾モ棤鈥库棤)锞夛緸 RuoYi-Vue-Plus鍚姩鎴愬姛 醿�(麓凇`醿�)锞�");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/DromaraServletInitializer.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/DromaraServletInitializer.java
new file mode 100755
index 0000000..066a683
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/DromaraServletInitializer.java
@@ -0,0 +1,18 @@
+package org.dromara;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+/**
+ * web瀹瑰櫒涓繘琛岄儴缃�
+ *
+ * @author Lion Li
+ */
+public class DromaraServletInitializer extends SpringBootServletInitializer {
+
+ @Override
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+ return application.sources(DromaraApplication.class);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
new file mode 100755
index 0000000..a865c5c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
@@ -0,0 +1,243 @@
+package org.dromara.web.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthRequest;
+import me.zhyd.oauth.utils.AuthStateUtils;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.domain.model.LoginBody;
+import org.dromara.common.core.domain.model.RegisterBody;
+import org.dromara.common.core.domain.model.SocialLoginBody;
+import org.dromara.common.core.utils.*;
+import org.dromara.common.encrypt.annotation.ApiEncrypt;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.ratelimiter.annotation.RateLimiter;
+import org.dromara.common.ratelimiter.enums.LimitType;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
+import org.dromara.common.social.config.properties.SocialProperties;
+import org.dromara.common.social.utils.SocialUtils;
+import org.dromara.common.sse.dto.SseMessageDto;
+import org.dromara.common.sse.utils.SseMessageUtils;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.domain.bo.SysTenantBo;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.system.domain.vo.SysTenantVo;
+import org.dromara.system.service.ISysClientService;
+import org.dromara.system.service.ISysConfigService;
+import org.dromara.system.service.ISysSocialService;
+import org.dromara.system.service.ISysTenantService;
+import org.dromara.web.domain.vo.LoginTenantVo;
+import org.dromara.web.domain.vo.LoginVo;
+import org.dromara.web.domain.vo.TenantListVo;
+import org.dromara.web.service.IAuthStrategy;
+import org.dromara.web.service.SysLoginService;
+import org.dromara.web.service.SysRegisterService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 璁よ瘉
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@SaIgnore
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+ private final SocialProperties socialProperties;
+ private final SysLoginService loginService;
+ private final SysRegisterService registerService;
+ private final ISysConfigService configService;
+ private final ISysTenantService tenantService;
+ private final ISysSocialService socialUserService;
+ private final ISysClientService clientService;
+ private final ScheduledExecutorService scheduledExecutorService;
+
+
+ /**
+ * 鐧诲綍鏂规硶
+ *
+ * @param body 鐧诲綍淇℃伅
+ * @return 缁撴灉
+ */
+ @ApiEncrypt
+ @PostMapping("/login")
+ public R<LoginVo> login(@RequestBody String body) {
+ LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class);
+ ValidatorUtils.validate(loginBody);
+ // 鎺堟潈绫诲瀷鍜屽鎴风id
+ String clientId = loginBody.getClientId();
+ String grantType = loginBody.getGrantType();
+ SysClientVo client = clientService.queryByClientId(clientId);
+ // 鏌ヨ涓嶅埌 client 鎴� client 鍐呬笉鍖呭惈 grantType
+ if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
+ log.info("瀹㈡埛绔痠d: {} 璁よ瘉绫诲瀷锛歿} 寮傚父!.", clientId, grantType);
+ return R.fail(MessageUtils.message("auth.grant.type.error"));
+ } else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
+ return R.fail(MessageUtils.message("auth.grant.type.blocked"));
+ }
+ // 鏍¢獙绉熸埛
+ loginService.checkTenant(loginBody.getTenantId());
+ // 鐧诲綍
+ LoginVo loginVo = IAuthStrategy.login(body, client, grantType);
+
+ Long userId = LoginHelper.getUserId();
+ scheduledExecutorService.schedule(() -> {
+ SseMessageDto dto = new SseMessageDto();
+ dto.setMessage(DateUtils.getTodayHour(new Date()) + "濂斤紝娆㈣繋鐧诲綍 璐ㄩ噺鍒嗘瀽绠$悊绯荤粺");
+ dto.setUserIds(List.of(userId));
+ SseMessageUtils.publishMessage(dto);
+ }, 5, TimeUnit.SECONDS);
+ return R.ok(loginVo);
+ }
+
+ /**
+ * 鑾峰彇璺宠浆URL
+ *
+ * @param source 鐧诲綍鏉ユ簮
+ * @return 缁撴灉
+ */
+ @GetMapping("/binding/{source}")
+ public R<String> authBinding(@PathVariable("source") String source,
+ @RequestParam String tenantId, @RequestParam String domain) {
+ SocialLoginConfigProperties obj = socialProperties.getType().get(source);
+ if (ObjectUtil.isNull(obj)) {
+ return R.fail(source + "骞冲彴璐﹀彿鏆備笉鏀寔");
+ }
+ AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
+ Map<String, String> map = new HashMap<>();
+ map.put("tenantId", tenantId);
+ map.put("domain", domain);
+ map.put("state", AuthStateUtils.createState());
+ String authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8));
+ return R.ok("鎿嶄綔鎴愬姛", authorizeUrl);
+ }
+
+ /**
+ * 鍓嶇鍥炶皟缁戝畾鎺堟潈(闇�瑕乼oken)
+ *
+ * @param loginBody 璇锋眰浣�
+ * @return 缁撴灉
+ */
+ @PostMapping("/social/callback")
+ public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) {
+ // 鏍¢獙token
+ StpUtil.checkLogin();
+ // 鑾峰彇绗笁鏂圭櫥褰曚俊鎭�
+ AuthResponse<AuthUser> response = SocialUtils.loginAuth(
+ loginBody.getSource(), loginBody.getSocialCode(),
+ loginBody.getSocialState(), socialProperties);
+ AuthUser authUserData = response.getData();
+ // 鍒ゆ柇鎺堟潈鍝嶅簲鏄惁鎴愬姛
+ if (!response.ok()) {
+ return R.fail(response.getMsg());
+ }
+ loginService.socialRegister(authUserData);
+ return R.ok();
+ }
+
+
+ /**
+ * 鍙栨秷鎺堟潈(闇�瑕乼oken)
+ *
+ * @param socialId socialId
+ */
+ @DeleteMapping(value = "/unlock/{socialId}")
+ public R<Void> unlockSocial(@PathVariable Long socialId) {
+ // 鏍¢獙token
+ StpUtil.checkLogin();
+ Boolean rows = socialUserService.deleteWithValidById(socialId);
+ return rows ? R.ok() : R.fail("鍙栨秷鎺堟潈澶辫触");
+ }
+
+
+ /**
+ * 閫�鍑虹櫥褰�
+ */
+ @PostMapping("/logout")
+ public R<Void> logout() {
+ loginService.logout();
+ return R.ok("閫�鍑烘垚鍔�");
+ }
+
+ /**
+ * 鐢ㄦ埛娉ㄥ唽
+ */
+ @ApiEncrypt
+ @PostMapping("/register")
+ public R<Void> register(@Validated @RequestBody RegisterBody user) {
+ if (!configService.selectRegisterEnabled(user.getTenantId())) {
+ return R.fail("褰撳墠绯荤粺娌℃湁寮�鍚敞鍐屽姛鑳斤紒");
+ }
+ registerService.register(user);
+ return R.ok();
+ }
+
+ /**
+ * 鐧诲綍椤甸潰绉熸埛涓嬫媺妗�
+ *
+ * @return 绉熸埛鍒楄〃
+ */
+ @RateLimiter(time = 60, count = 20, limitType = LimitType.IP)
+ @GetMapping("/tenant/list")
+ public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
+ // 杩斿洖瀵硅薄
+ LoginTenantVo result = new LoginTenantVo();
+ boolean enable = TenantHelper.isEnable();
+ result.setTenantEnabled(enable);
+ // 濡傛灉鏈紑鍚鎴疯繖鐩存帴杩斿洖
+ if (!enable) {
+ return R.ok(result);
+ }
+
+ List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
+ List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
+ try {
+ // 濡傛灉鍙秴绠¤繑鍥炴墍鏈夌鎴�
+ if (LoginHelper.isSuperAdmin()) {
+ result.setVoList(voList);
+ return R.ok(result);
+ }
+ } catch (NotLoginException ignored) {
+ }
+
+ // 鑾峰彇鍩熷悕
+ String host;
+ String referer = request.getHeader("referer");
+ if (StringUtils.isNotBlank(referer)) {
+ // 杩欓噷浠巖eferer涓彇鍊兼槸涓轰簡鏈湴浣跨敤hosts娣诲姞铏氭嫙鍩熷悕锛屾柟渚挎湰鍦扮幆澧冭皟璇�
+ host = referer.split("//")[1].split("/")[0];
+ } else {
+ host = new URL(request.getRequestURL().toString()).getHost();
+ }
+ // 鏍规嵁鍩熷悕杩涜绛涢��
+ List<TenantListVo> list = StreamUtils.filter(voList, vo ->
+ StringUtils.equalsIgnoreCase(vo.getDomain(), host));
+ result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
+ return R.ok(result);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java
new file mode 100755
index 0000000..2586add
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java
@@ -0,0 +1,160 @@
+package org.dromara.web.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.captcha.generator.MathGenerator;
+import cn.hutool.captcha.generator.RandomGenerator;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.RandomUtil;
+import jakarta.validation.constraints.NotBlank;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mail.config.properties.MailProperties;
+import org.dromara.common.mail.utils.MailUtils;
+import org.dromara.common.ratelimiter.annotation.RateLimiter;
+import org.dromara.common.ratelimiter.enums.LimitType;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.web.core.WaveAndCircleCaptcha;
+import org.dromara.common.web.config.properties.CaptchaProperties;
+import org.dromara.sms4j.api.SmsBlend;
+import org.dromara.sms4j.api.entity.SmsResponse;
+import org.dromara.sms4j.core.factory.SmsFactory;
+import org.dromara.web.domain.vo.CaptchaVo;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.awt.*;
+import java.time.Duration;
+import java.util.LinkedHashMap;
+
+/**
+ * 楠岃瘉鐮佹搷浣滃鐞�
+ *
+ * @author Lion Li
+ */
+@SaIgnore
+@Slf4j
+@Validated
+@RequiredArgsConstructor
+@RestController
+public class CaptchaController {
+
+ private final CaptchaProperties captchaProperties;
+ private final MailProperties mailProperties;
+
+ /**
+ * 鐭俊楠岃瘉鐮�
+ *
+ * @param phonenumber 鐢ㄦ埛鎵嬫満鍙�
+ */
+ @RateLimiter(key = "#phonenumber", time = 60, count = 1)
+ @GetMapping("/resource/sms/code")
+ public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
+ String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
+ String code = RandomUtil.randomNumbers(4);
+ RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+ // 楠岃瘉鐮佹ā鏉縤d 鑷澶勭悊 (鏌ユ暟鎹簱鎴栧啓姝诲潎鍙�)
+ String templateId = "";
+ LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
+ map.put("code", code);
+ SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
+ SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
+ if (!smsResponse.isSuccess()) {
+ log.error("楠岃瘉鐮佺煭淇″彂閫佸紓甯� => {}", smsResponse);
+ return R.fail(smsResponse.getData().toString());
+ }
+ return R.ok();
+ }
+
+ /**
+ * 閭楠岃瘉鐮�
+ *
+ * @param email 閭
+ */
+ @GetMapping("/resource/email/code")
+ public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
+ if (!mailProperties.getEnabled()) {
+ return R.fail("褰撳墠绯荤粺娌℃湁寮�鍚偖绠卞姛鑳斤紒");
+ }
+ SpringUtils.getAopProxy(this).emailCodeImpl(email);
+ return R.ok();
+ }
+
+ /**
+ * 閭楠岃瘉鐮�
+ * 鐙珛鏂规硶閬垮厤楠岃瘉鐮佸叧闂箣鍚庝粛鐒惰蛋闄愭祦
+ */
+ @RateLimiter(key = "#email", time = 60, count = 1)
+ public void emailCodeImpl(String email) {
+ String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
+ String code = RandomUtil.randomNumbers(4);
+ RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+ try {
+ MailUtils.sendText(email, "鐧诲綍楠岃瘉鐮�", "鎮ㄦ湰娆¢獙璇佺爜涓猴細" + code + "锛屾湁鏁堟�т负" + Constants.CAPTCHA_EXPIRATION + "鍒嗛挓锛岃灏藉揩濉啓銆�");
+ } catch (Exception e) {
+ log.error("楠岃瘉鐮佺煭淇″彂閫佸紓甯� => {}", e.getMessage());
+ throw new ServiceException(e.getMessage());
+ }
+ }
+
+ /**
+ * 鐢熸垚楠岃瘉鐮�
+ */
+ @GetMapping("/auth/code")
+ public R<CaptchaVo> getCode() {
+ boolean captchaEnabled = captchaProperties.getEnable();
+ if (!captchaEnabled) {
+ CaptchaVo captchaVo = new CaptchaVo();
+ captchaVo.setCaptchaEnabled(false);
+ return R.ok(captchaVo);
+ }
+ return R.ok(SpringUtils.getAopProxy(this).getCodeImpl());
+ }
+
+ /**
+ * 鐢熸垚楠岃瘉鐮�
+ * 鐙珛鏂规硶閬垮厤楠岃瘉鐮佸叧闂箣鍚庝粛鐒惰蛋闄愭祦
+ */
+ @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
+ public CaptchaVo getCodeImpl() {
+ // 淇濆瓨楠岃瘉鐮佷俊鎭�
+ String uuid = IdUtil.simpleUUID();
+ String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
+ // 鐢熸垚楠岃瘉鐮�
+ String captchaType = captchaProperties.getType();
+ CodeGenerator codeGenerator;
+ if ("math".equals(captchaType)) {
+ codeGenerator = new MathGenerator(captchaProperties.getNumberLength(), false);
+ } else {
+ codeGenerator = new RandomGenerator(captchaProperties.getCharLength());
+ }
+ WaveAndCircleCaptcha captcha = new WaveAndCircleCaptcha(160, 60);
+ // captcha.setBackground(Color.WHITE); // 涓嶈缃氨鏄�忔槑搴�
+ captcha.setFont(new Font("Arial", Font.BOLD, 45));
+ captcha.setGenerator(codeGenerator);
+ captcha.createCode();
+ // 濡傛灉鏄暟瀛﹂獙璇佺爜锛屼娇鐢⊿pEL琛ㄨ揪寮忓鐞嗛獙璇佺爜缁撴灉
+ String code = captcha.getCode();
+ if ("math".equals(captchaType)) {
+ ExpressionParser parser = new SpelExpressionParser();
+ Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
+ code = exp.getValue(String.class);
+ }
+ RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+ CaptchaVo captchaVo = new CaptchaVo();
+ captchaVo.setUuid(uuid);
+ captchaVo.setImg(captcha.getImageBase64());
+ return captchaVo;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/controller/IndexController.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/controller/IndexController.java
new file mode 100755
index 0000000..cdcfed6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/controller/IndexController.java
@@ -0,0 +1,28 @@
+package org.dromara.web.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 棣栭〉
+ *
+ * @author Lion Li
+ */
+@SaIgnore
+@RequiredArgsConstructor
+@RestController
+public class IndexController {
+
+ /**
+ * 璁块棶棣栭〉锛屾彁绀鸿
+ */
+ @GetMapping("/")
+ public String index() {
+ return StringUtils.format("娆㈣繋浣跨敤{}鍚庡彴绠$悊妗嗘灦锛岃閫氳繃鍓嶇鍦板潃璁块棶銆�", SpringUtils.getApplicationName());
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/CaptchaVo.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/CaptchaVo.java
new file mode 100755
index 0000000..664df1e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/CaptchaVo.java
@@ -0,0 +1,25 @@
+package org.dromara.web.domain.vo;
+
+import lombok.Data;
+
+/**
+ * 楠岃瘉鐮佷俊鎭�
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class CaptchaVo {
+
+ /**
+ * 鏄惁寮�鍚獙璇佺爜
+ */
+ private Boolean captchaEnabled = true;
+
+ private String uuid;
+
+ /**
+ * 楠岃瘉鐮佸浘鐗�
+ */
+ private String img;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginTenantVo.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginTenantVo.java
new file mode 100755
index 0000000..0a83ace
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginTenantVo.java
@@ -0,0 +1,25 @@
+package org.dromara.web.domain.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 鐧诲綍绉熸埛瀵硅薄
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class LoginTenantVo {
+
+ /**
+ * 绉熸埛寮�鍏�
+ */
+ private Boolean tenantEnabled;
+
+ /**
+ * 绉熸埛瀵硅薄鍒楄〃
+ */
+ private List<TenantListVo> voList;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginVo.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginVo.java
new file mode 100755
index 0000000..834afe5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginVo.java
@@ -0,0 +1,54 @@
+package org.dromara.web.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * 鐧诲綍楠岃瘉淇℃伅
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class LoginVo {
+
+ /**
+ * 鎺堟潈浠ょ墝
+ */
+ @JsonProperty("access_token")
+ private String accessToken;
+
+ /**
+ * 鍒锋柊浠ょ墝
+ */
+ @JsonProperty("refresh_token")
+ private String refreshToken;
+
+ /**
+ * 鎺堟潈浠ょ墝 access_token 鐨勬湁鏁堟湡
+ */
+ @JsonProperty("expire_in")
+ private Long expireIn;
+
+ /**
+ * 鍒锋柊浠ょ墝 refresh_token 鐨勬湁鏁堟湡
+ */
+ @JsonProperty("refresh_expire_in")
+ private Long refreshExpireIn;
+
+ /**
+ * 搴旂敤id
+ */
+ @JsonProperty("client_id")
+ private String clientId;
+
+ /**
+ * 浠ょ墝鏉冮檺
+ */
+ private String scope;
+
+ /**
+ * 鐢ㄦ埛 openid
+ */
+ private String openid;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/TenantListVo.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/TenantListVo.java
new file mode 100755
index 0000000..db9c271
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/TenantListVo.java
@@ -0,0 +1,31 @@
+package org.dromara.web.domain.vo;
+
+import org.dromara.system.domain.vo.SysTenantVo;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+/**
+ * 绉熸埛鍒楄〃
+ *
+ * @author Lion Li
+ */
+@Data
+@AutoMapper(target = SysTenantVo.class)
+public class TenantListVo {
+
+ /**
+ * 绉熸埛缂栧彿
+ */
+ private String tenantId;
+
+ /**
+ * 浼佷笟鍚嶇О
+ */
+ private String companyName;
+
+ /**
+ * 鍩熷悕
+ */
+ private String domain;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/listener/UserActionListener.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/listener/UserActionListener.java
new file mode 100755
index 0000000..6c8accb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/listener/UserActionListener.java
@@ -0,0 +1,163 @@
+package org.dromara.web.listener;
+
+import cn.dev33.satoken.listener.SaTokenListener;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.http.useragent.UserAgent;
+import cn.hutool.http.useragent.UserAgentUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.CacheConstants;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.domain.dto.UserOnlineDTO;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.ip.AddressUtils;
+import org.dromara.common.log.event.LogininforEvent;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.web.service.SysLoginService;
+import org.springframework.stereotype.Component;
+
+import java.time.Duration;
+
+/**
+ * 鐢ㄦ埛琛屼负 渚﹀惉鍣ㄧ殑瀹炵幇
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Component
+@Slf4j
+public class UserActionListener implements SaTokenListener {
+
+ private final SysLoginService loginService;
+
+ /**
+ * 姣忔鐧诲綍鏃惰Е鍙�
+ */
+ @Override
+ public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
+ UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
+ String ip = ServletUtils.getClientIP();
+ UserOnlineDTO dto = new UserOnlineDTO();
+ dto.setIpaddr(ip);
+ dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
+ dto.setBrowser(userAgent.getBrowser().getName());
+ dto.setOs(userAgent.getOs().getName());
+ dto.setLoginTime(System.currentTimeMillis());
+ dto.setTokenId(tokenValue);
+ String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
+ String tenantId = (String) loginParameter.getExtra(LoginHelper.TENANT_KEY);
+ dto.setUserName(username);
+ dto.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY));
+ dto.setDeviceType(loginParameter.getDeviceType());
+ dto.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY));
+ TenantHelper.dynamic(tenantId, () -> {
+ if(loginParameter.getTimeout() == -1) {
+ RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
+ } else {
+ RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginParameter.getTimeout()));
+ }
+ });
+ // 璁板綍鐧诲綍鏃ュ織
+ LogininforEvent logininforEvent = new LogininforEvent();
+ logininforEvent.setTenantId(tenantId);
+ logininforEvent.setUsername(username);
+ logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
+ logininforEvent.setMessage(MessageUtils.message("user.login.success"));
+ logininforEvent.setRequest(ServletUtils.getRequest());
+ SpringUtils.context().publishEvent(logininforEvent);
+ // 鏇存柊鐧诲綍淇℃伅
+ loginService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip);
+ log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
+ }
+
+ /**
+ * 姣忔娉ㄩ攢鏃惰Е鍙�
+ */
+ @Override
+ public void doLogout(String loginType, Object loginId, String tokenValue) {
+ String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
+ TenantHelper.dynamic(tenantId, () -> {
+ RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
+ });
+ log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue);
+ }
+
+ /**
+ * 姣忔琚涪涓嬬嚎鏃惰Е鍙�
+ */
+ @Override
+ public void doKickout(String loginType, Object loginId, String tokenValue) {
+ String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
+ TenantHelper.dynamic(tenantId, () -> {
+ RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
+ });
+ log.info("user doKickout, userId:{}, token:{}", loginId, tokenValue);
+ }
+
+ /**
+ * 姣忔琚《涓嬬嚎鏃惰Е鍙�
+ */
+ @Override
+ public void doReplaced(String loginType, Object loginId, String tokenValue) {
+ String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
+ TenantHelper.dynamic(tenantId, () -> {
+ RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
+ });
+ log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue);
+ }
+
+ /**
+ * 姣忔琚皝绂佹椂瑙﹀彂
+ */
+ @Override
+ public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
+ }
+
+ /**
+ * 姣忔琚В灏佹椂瑙﹀彂
+ */
+ @Override
+ public void doUntieDisable(String loginType, Object loginId, String service) {
+ }
+
+ /**
+ * 姣忔鎵撳紑浜岀骇璁よ瘉鏃惰Е鍙�
+ */
+ @Override
+ public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {
+ }
+
+ /**
+ * 姣忔鍒涘缓Session鏃惰Е鍙�
+ */
+ @Override
+ public void doCloseSafe(String loginType, String tokenValue, String service) {
+ }
+
+ /**
+ * 姣忔鍒涘缓Session鏃惰Е鍙�
+ */
+ @Override
+ public void doCreateSession(String id) {
+ }
+
+ /**
+ * 姣忔娉ㄩ攢Session鏃惰Е鍙�
+ */
+ @Override
+ public void doLogoutSession(String id) {
+ }
+
+ /**
+ * 姣忔Token缁湡鏃惰Е鍙�
+ */
+ @Override
+ public void doRenewTimeout(String loginType, Object loginId, String tokenValue, long timeout) {
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/IAuthStrategy.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/IAuthStrategy.java
new file mode 100755
index 0000000..a75b913
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/IAuthStrategy.java
@@ -0,0 +1,46 @@
+package org.dromara.web.service;
+
+
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.system.domain.SysClient;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.web.domain.vo.LoginVo;
+
+/**
+ * 鎺堟潈绛栫暐
+ *
+ * @author Michelle.Chung
+ */
+public interface IAuthStrategy {
+
+ String BASE_NAME = "AuthStrategy";
+
+ /**
+ * 鐧诲綍
+ *
+ * @param body 鐧诲綍瀵硅薄
+ * @param client 鎺堟潈绠$悊瑙嗗浘瀵硅薄
+ * @param grantType 鎺堟潈绫诲瀷
+ * @return 鐧诲綍楠岃瘉淇℃伅
+ */
+ static LoginVo login(String body, SysClientVo client, String grantType) {
+ // 鎺堟潈绫诲瀷鍜屽鎴风id
+ String beanName = grantType + BASE_NAME;
+ if (!SpringUtils.containsBean(beanName)) {
+ throw new ServiceException("鎺堟潈绫诲瀷涓嶆纭�!");
+ }
+ IAuthStrategy instance = SpringUtils.getBean(beanName);
+ return instance.login(body, client);
+ }
+
+ /**
+ * 鐧诲綍
+ *
+ * @param body 鐧诲綍瀵硅薄
+ * @param client 鎺堟潈绠$悊瑙嗗浘瀵硅薄
+ * @return 鐧诲綍楠岃瘉淇℃伅
+ */
+ LoginVo login(String body, SysClientVo client);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java
new file mode 100755
index 0000000..41a802b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java
@@ -0,0 +1,251 @@
+package org.dromara.web.service;
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Opt;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.lock.annotation.Lock4j;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.model.AuthUser;
+import org.dromara.common.core.constant.CacheConstants;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.constant.TenantConstants;
+import org.dromara.common.core.domain.dto.PostDTO;
+import org.dromara.common.core.domain.dto.RoleDTO;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.enums.LoginType;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.exception.user.UserException;
+import org.dromara.common.core.utils.*;
+import org.dromara.common.log.event.LogininforEvent;
+import org.dromara.common.mybatis.helper.DataPermissionHelper;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.exception.TenantException;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.bo.SysSocialBo;
+import org.dromara.system.domain.vo.*;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.system.service.*;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.time.Duration;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * 鐧诲綍鏍¢獙鏂规硶
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Slf4j
+@Service
+public class SysLoginService {
+
+ @Value("${user.password.maxRetryCount}")
+ private Integer maxRetryCount;
+
+ @Value("${user.password.lockTime}")
+ private Integer lockTime;
+
+ private final ISysTenantService tenantService;
+ private final ISysPermissionService permissionService;
+ private final ISysSocialService sysSocialService;
+ private final ISysRoleService roleService;
+ private final ISysDeptService deptService;
+ private final ISysPostService postService;
+ private final SysUserMapper userMapper;
+
+
+ /**
+ * 缁戝畾绗笁鏂圭敤鎴�
+ *
+ * @param authUserData 鎺堟潈鍝嶅簲瀹炰綋
+ */
+ @Lock4j
+ public void socialRegister(AuthUser authUserData) {
+ String authId = authUserData.getSource() + authUserData.getUuid();
+ // 绗笁鏂圭敤鎴蜂俊鎭�
+ SysSocialBo bo = BeanUtil.toBean(authUserData, SysSocialBo.class);
+ BeanUtil.copyProperties(authUserData.getToken(), bo);
+ Long userId = LoginHelper.getUserId();
+ bo.setUserId(userId);
+ bo.setAuthId(authId);
+ bo.setOpenId(authUserData.getUuid());
+ bo.setUserName(authUserData.getUsername());
+ bo.setNickName(authUserData.getNickname());
+ List<SysSocialVo> checkList = sysSocialService.selectByAuthId(authId);
+ if (CollUtil.isNotEmpty(checkList)) {
+ throw new ServiceException("姝や笁鏂硅处鍙峰凡缁忚缁戝畾!");
+ }
+ // 鏌ヨ鏄惁宸茬粡缁戝畾鐢ㄦ埛
+ SysSocialBo params = new SysSocialBo();
+ params.setUserId(userId);
+ params.setSource(bo.getSource());
+ List<SysSocialVo> list = sysSocialService.queryList(params);
+ if (CollUtil.isEmpty(list)) {
+ // 娌℃湁缁戝畾鐢ㄦ埛, 鏂板鐢ㄦ埛淇℃伅
+ sysSocialService.insertByBo(bo);
+ } else {
+ // 鏇存柊鐢ㄦ埛淇℃伅
+ bo.setId(list.get(0).getId());
+ sysSocialService.updateByBo(bo);
+ // 濡傛灉瑕佺粦瀹氱殑骞冲彴璐﹀彿宸茬粡琚粦瀹氳繃浜� 鏄惁鎶涘紓甯歌嚜琛屽喅鏂�
+ // throw new ServiceException("姝ゅ钩鍙拌处鍙峰凡缁忚缁戝畾!");
+ }
+ }
+
+
+ /**
+ * 閫�鍑虹櫥褰�
+ */
+ public void logout() {
+ try {
+ LoginUser loginUser = LoginHelper.getLoginUser();
+ if (ObjectUtil.isNull(loginUser)) {
+ return;
+ }
+ if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
+ // 瓒呯骇绠$悊鍛� 鐧诲嚭娓呴櫎鍔ㄦ�佺鎴�
+ TenantHelper.clearDynamic();
+ }
+ recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
+ } catch (NotLoginException ignored) {
+ } finally {
+ try {
+ StpUtil.logout();
+ } catch (NotLoginException ignored) {
+ }
+ }
+ }
+
+ /**
+ * 璁板綍鐧诲綍淇℃伅
+ *
+ * @param tenantId 绉熸埛ID
+ * @param username 鐢ㄦ埛鍚�
+ * @param status 鐘舵��
+ * @param message 娑堟伅鍐呭
+ */
+ public void recordLogininfor(String tenantId, String username, String status, String message) {
+ LogininforEvent logininforEvent = new LogininforEvent();
+ logininforEvent.setTenantId(tenantId);
+ logininforEvent.setUsername(username);
+ logininforEvent.setStatus(status);
+ logininforEvent.setMessage(message);
+ logininforEvent.setRequest(ServletUtils.getRequest());
+ SpringUtils.context().publishEvent(logininforEvent);
+ }
+
+ /**
+ * 鏋勫缓鐧诲綍鐢ㄦ埛
+ */
+ public LoginUser buildLoginUser(SysUserVo user) {
+ LoginUser loginUser = new LoginUser();
+ Long userId = user.getUserId();
+ loginUser.setTenantId(user.getTenantId());
+ loginUser.setUserId(userId);
+ loginUser.setDeptId(user.getDeptId());
+ loginUser.setUsername(user.getUserName());
+ loginUser.setNickname(user.getNickName());
+ loginUser.setUserType(user.getUserType());
+ loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
+ loginUser.setRolePermission(permissionService.getRolePermission(userId));
+ if (ObjectUtil.isNotNull(user.getDeptId())) {
+ Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
+ loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
+ loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
+ }
+ List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
+ List<SysPostVo> posts = postService.selectPostsByUserId(userId);
+ loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
+ loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
+ return loginUser;
+ }
+
+ /**
+ * 璁板綍鐧诲綍淇℃伅
+ *
+ * @param userId 鐢ㄦ埛ID
+ */
+ public void recordLoginInfo(Long userId, String ip) {
+ SysUser sysUser = new SysUser();
+ sysUser.setUserId(userId);
+ sysUser.setLoginIp(ip);
+ sysUser.setLoginDate(DateUtils.getNowDate());
+ sysUser.setUpdateBy(userId);
+ DataPermissionHelper.ignore(() -> userMapper.updateById(sysUser));
+ }
+
+ /**
+ * 鐧诲綍鏍¢獙
+ */
+ public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
+ String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
+ String loginFail = Constants.LOGIN_FAIL;
+
+ // 鑾峰彇鐢ㄦ埛鐧诲綍閿欒娆℃暟锛岄粯璁や负0 (鍙嚜瀹氫箟闄愬埗绛栫暐 渚嬪: key + username + ip)
+ int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
+ // 閿佸畾鏃堕棿鍐呯櫥褰� 鍒欒涪鍑�
+ if (errorNumber >= maxRetryCount) {
+ recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
+ throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
+ }
+
+ if (supplier.get()) {
+ // 閿欒娆℃暟閫掑
+ errorNumber++;
+ RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
+ // 杈惧埌瑙勫畾閿欒娆℃暟 鍒欓攣瀹氱櫥褰�
+ if (errorNumber >= maxRetryCount) {
+ recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
+ throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
+ } else {
+ // 鏈揪鍒拌瀹氶敊璇鏁�
+ recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
+ throw new UserException(loginType.getRetryLimitCount(), errorNumber);
+ }
+ }
+
+ // 鐧诲綍鎴愬姛 娓呯┖閿欒娆℃暟
+ RedisUtils.deleteObject(errorKey);
+ }
+
+ /**
+ * 鏍¢獙绉熸埛
+ *
+ * @param tenantId 绉熸埛ID
+ */
+ public void checkTenant(String tenantId) {
+ if (!TenantHelper.isEnable()) {
+ return;
+ }
+ if (StringUtils.isBlank(tenantId)) {
+ throw new TenantException("tenant.number.not.blank");
+ }
+ if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
+ return;
+ }
+ SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
+ if (ObjectUtil.isNull(tenant)) {
+ log.info("鐧诲綍绉熸埛锛歿} 涓嶅瓨鍦�.", tenantId);
+ throw new TenantException("tenant.not.exists");
+ } else if (SystemConstants.DISABLE.equals(tenant.getStatus())) {
+ log.info("鐧诲綍绉熸埛锛歿} 宸茶鍋滅敤.", tenantId);
+ throw new TenantException("tenant.blocked");
+ } else if (ObjectUtil.isNotNull(tenant.getExpireTime())
+ && new Date().after(tenant.getExpireTime())) {
+ log.info("鐧诲綍绉熸埛锛歿} 宸茶秴杩囨湁鏁堟湡.", tenantId);
+ throw new TenantException("tenant.expired");
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java
new file mode 100755
index 0000000..5a3351d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java
@@ -0,0 +1,115 @@
+package org.dromara.web.service;
+
+import cn.hutool.crypto.digest.BCrypt;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.domain.model.RegisterBody;
+import org.dromara.common.core.enums.UserType;
+import org.dromara.common.core.exception.user.CaptchaException;
+import org.dromara.common.core.exception.user.CaptchaExpireException;
+import org.dromara.common.core.exception.user.UserException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.log.event.LogininforEvent;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.common.web.config.properties.CaptchaProperties;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.bo.SysUserBo;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.system.service.ISysUserService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 娉ㄥ唽鏍¢獙鏂规硶
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Service
+public class SysRegisterService {
+
+ private final ISysUserService userService;
+ private final SysUserMapper userMapper;
+ private final CaptchaProperties captchaProperties;
+
+ /**
+ * 娉ㄥ唽
+ */
+ public void register(RegisterBody registerBody) {
+ String tenantId = registerBody.getTenantId();
+ String username = registerBody.getUsername();
+ String password = registerBody.getPassword();
+ // 鏍¢獙鐢ㄦ埛绫诲瀷鏄惁瀛樺湪
+ String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
+
+ boolean captchaEnabled = captchaProperties.getEnable();
+ // 楠岃瘉鐮佸紑鍏�
+ if (captchaEnabled) {
+ validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
+ }
+ SysUserBo sysUser = new SysUserBo();
+ sysUser.setUserName(username);
+ sysUser.setNickName(username);
+ sysUser.setPassword(BCrypt.hashpw(password));
+ sysUser.setUserType(userType);
+
+ boolean exist = TenantHelper.dynamic(tenantId, () -> {
+ return userMapper.exists(new LambdaQueryWrapper<SysUser>()
+ .eq(SysUser::getUserName, sysUser.getUserName()));
+ });
+ if (exist) {
+ throw new UserException("user.register.save.error", username);
+ }
+ boolean regFlag = userService.registerUser(sysUser, tenantId);
+ if (!regFlag) {
+ throw new UserException("user.register.error");
+ }
+ recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
+ }
+
+ /**
+ * 鏍¢獙楠岃瘉鐮�
+ *
+ * @param username 鐢ㄦ埛鍚�
+ * @param code 楠岃瘉鐮�
+ * @param uuid 鍞竴鏍囪瘑
+ */
+ public void validateCaptcha(String tenantId, String username, String code, String uuid) {
+ String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
+ String captcha = RedisUtils.getCacheObject(verifyKey);
+ RedisUtils.deleteObject(verifyKey);
+ if (captcha == null) {
+ recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+ throw new CaptchaExpireException();
+ }
+ if (!StringUtils.equalsIgnoreCase(code, captcha)) {
+ recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
+ throw new CaptchaException();
+ }
+ }
+
+ /**
+ * 璁板綍鐧诲綍淇℃伅
+ *
+ * @param tenantId 绉熸埛ID
+ * @param username 鐢ㄦ埛鍚�
+ * @param status 鐘舵��
+ * @param message 娑堟伅鍐呭
+ * @return
+ */
+ private void recordLogininfor(String tenantId, String username, String status, String message) {
+ LogininforEvent logininforEvent = new LogininforEvent();
+ logininforEvent.setTenantId(tenantId);
+ logininforEvent.setUsername(username);
+ logininforEvent.setStatus(status);
+ logininforEvent.setMessage(message);
+ logininforEvent.setRequest(ServletUtils.getRequest());
+ SpringUtils.context().publishEvent(logininforEvent);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java
new file mode 100755
index 0000000..e4315dc
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java
@@ -0,0 +1,102 @@
+package org.dromara.web.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.model.EmailLoginBody;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.enums.LoginType;
+import org.dromara.common.core.exception.user.CaptchaExpireException;
+import org.dromara.common.core.exception.user.UserException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.web.domain.vo.LoginVo;
+import org.dromara.web.service.IAuthStrategy;
+import org.dromara.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 閭欢璁よ瘉绛栫暐
+ *
+ * @author Michelle.Chung
+ */
+@Slf4j
+@Service("email" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class EmailAuthStrategy implements IAuthStrategy {
+
+ private final SysLoginService loginService;
+ private final SysUserMapper userMapper;
+
+ @Override
+ public LoginVo login(String body, SysClientVo client) {
+ EmailLoginBody loginBody = JsonUtils.parseObject(body, EmailLoginBody.class);
+ ValidatorUtils.validate(loginBody);
+ String tenantId = loginBody.getTenantId();
+ String email = loginBody.getEmail();
+ String emailCode = loginBody.getEmailCode();
+ LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
+ SysUserVo user = loadUserByEmail(email);
+ loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
+ // 姝ゅ鍙牴鎹櫥褰曠敤鎴风殑鏁版嵁涓嶅悓 鑷鍒涘缓 loginUser 灞炴�т笉澶熺敤缁ф壙鎵╁睍灏辫浜�
+ return loginService.buildLoginUser(user);
+ });
+ loginUser.setClientKey(client.getClientKey());
+ loginUser.setDeviceType(client.getDeviceType());
+ SaLoginParameter model = new SaLoginParameter();
+ model.setDeviceType(client.getDeviceType());
+ // 鑷畾涔夊垎閰� 涓嶅悓鐢ㄦ埛浣撶郴 涓嶅悓 token 鎺堟潈鏃堕棿 涓嶈缃粯璁よ蛋鍏ㄥ眬 yml 閰嶇疆
+ // 渚嬪: 鍚庡彴鐢ㄦ埛30鍒嗛挓杩囨湡 app鐢ㄦ埛1澶╄繃鏈�
+ model.setTimeout(client.getTimeout());
+ model.setActiveTimeout(client.getActiveTimeout());
+ model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+ // 鐢熸垚token
+ LoginHelper.login(loginUser, model);
+
+ LoginVo loginVo = new LoginVo();
+ loginVo.setAccessToken(StpUtil.getTokenValue());
+ loginVo.setExpireIn(StpUtil.getTokenTimeout());
+ loginVo.setClientId(client.getClientId());
+ return loginVo;
+ }
+
+ /**
+ * 鏍¢獙閭楠岃瘉鐮�
+ */
+ private boolean validateEmailCode(String tenantId, String email, String emailCode) {
+ String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
+ if (StringUtils.isBlank(code)) {
+ loginService.recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+ throw new CaptchaExpireException();
+ }
+ return code.equals(emailCode);
+ }
+
+ private SysUserVo loadUserByEmail(String email) {
+ SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
+ if (ObjectUtil.isNull(user)) {
+ log.info("鐧诲綍鐢ㄦ埛锛歿} 涓嶅瓨鍦�.", email);
+ throw new UserException("user.not.exists", email);
+ } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
+ log.info("鐧诲綍鐢ㄦ埛锛歿} 宸茶鍋滅敤.", email);
+ throw new UserException("user.blocked", email);
+ }
+ return user;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java
new file mode 100755
index 0000000..abf590b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java
@@ -0,0 +1,123 @@
+package org.dromara.web.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.crypto.digest.BCrypt;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.domain.model.PasswordLoginBody;
+import org.dromara.common.core.enums.LoginType;
+import org.dromara.common.core.exception.user.CaptchaException;
+import org.dromara.common.core.exception.user.CaptchaExpireException;
+import org.dromara.common.core.exception.user.UserException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.common.web.config.properties.CaptchaProperties;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.web.domain.vo.LoginVo;
+import org.dromara.web.service.IAuthStrategy;
+import org.dromara.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 瀵嗙爜璁よ瘉绛栫暐
+ *
+ * @author Michelle.Chung
+ */
+@Slf4j
+@Service("password" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class PasswordAuthStrategy implements IAuthStrategy {
+
+ private final CaptchaProperties captchaProperties;
+ private final SysLoginService loginService;
+ private final SysUserMapper userMapper;
+
+ @Override
+ public LoginVo login(String body, SysClientVo client) {
+ PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
+ ValidatorUtils.validate(loginBody);
+ String tenantId = loginBody.getTenantId();
+ String username = loginBody.getUsername();
+ String password = loginBody.getPassword();
+ String code = loginBody.getCode();
+ String uuid = loginBody.getUuid();
+
+ boolean captchaEnabled = captchaProperties.getEnable();
+ // 楠岃瘉鐮佸紑鍏�
+ if (captchaEnabled) {
+ validateCaptcha(tenantId, username, code, uuid);
+ }
+ LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
+ SysUserVo user = loadUserByUsername(username);
+ loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
+ // 姝ゅ鍙牴鎹櫥褰曠敤鎴风殑鏁版嵁涓嶅悓 鑷鍒涘缓 loginUser
+ return loginService.buildLoginUser(user);
+ });
+ loginUser.setClientKey(client.getClientKey());
+ loginUser.setDeviceType(client.getDeviceType());
+ SaLoginParameter model = new SaLoginParameter();
+ model.setDeviceType(client.getDeviceType());
+ // 鑷畾涔夊垎閰� 涓嶅悓鐢ㄦ埛浣撶郴 涓嶅悓 token 鎺堟潈鏃堕棿 涓嶈缃粯璁よ蛋鍏ㄥ眬 yml 閰嶇疆
+ // 渚嬪: 鍚庡彴鐢ㄦ埛30鍒嗛挓杩囨湡 app鐢ㄦ埛1澶╄繃鏈�
+ model.setTimeout(client.getTimeout());
+ model.setActiveTimeout(client.getActiveTimeout());
+ model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+ // 鐢熸垚token
+ LoginHelper.login(loginUser, model);
+
+ LoginVo loginVo = new LoginVo();
+ loginVo.setAccessToken(StpUtil.getTokenValue());
+ loginVo.setExpireIn(StpUtil.getTokenTimeout());
+ loginVo.setClientId(client.getClientId());
+ return loginVo;
+ }
+
+ /**
+ * 鏍¢獙楠岃瘉鐮�
+ *
+ * @param username 鐢ㄦ埛鍚�
+ * @param code 楠岃瘉鐮�
+ * @param uuid 鍞竴鏍囪瘑
+ */
+ private void validateCaptcha(String tenantId, String username, String code, String uuid) {
+ String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
+ String captcha = RedisUtils.getCacheObject(verifyKey);
+ RedisUtils.deleteObject(verifyKey);
+ if (captcha == null) {
+ loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+ throw new CaptchaExpireException();
+ }
+ if (!StringUtils.equalsIgnoreCase(code, captcha)) {
+ loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
+ throw new CaptchaException();
+ }
+ }
+
+ private SysUserVo loadUserByUsername(String username) {
+ SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
+ if (ObjectUtil.isNull(user)) {
+ log.info("鐧诲綍鐢ㄦ埛锛歿} 涓嶅瓨鍦�.", username);
+ throw new UserException("user.not.exists", username);
+ } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
+ log.info("鐧诲綍鐢ㄦ埛锛歿} 宸茶鍋滅敤.", username);
+ throw new UserException("user.blocked", username);
+ }
+ return user;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java
new file mode 100755
index 0000000..597a601
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java
@@ -0,0 +1,102 @@
+package org.dromara.web.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.domain.model.SmsLoginBody;
+import org.dromara.common.core.enums.LoginType;
+import org.dromara.common.core.exception.user.CaptchaExpireException;
+import org.dromara.common.core.exception.user.UserException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.web.domain.vo.LoginVo;
+import org.dromara.web.service.IAuthStrategy;
+import org.dromara.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 鐭俊璁よ瘉绛栫暐
+ *
+ * @author Michelle.Chung
+ */
+@Slf4j
+@Service("sms" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class SmsAuthStrategy implements IAuthStrategy {
+
+ private final SysLoginService loginService;
+ private final SysUserMapper userMapper;
+
+ @Override
+ public LoginVo login(String body, SysClientVo client) {
+ SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
+ ValidatorUtils.validate(loginBody);
+ String tenantId = loginBody.getTenantId();
+ String phonenumber = loginBody.getPhonenumber();
+ String smsCode = loginBody.getSmsCode();
+ LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
+ SysUserVo user = loadUserByPhonenumber(phonenumber);
+ loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
+ // 姝ゅ鍙牴鎹櫥褰曠敤鎴风殑鏁版嵁涓嶅悓 鑷鍒涘缓 loginUser 灞炴�т笉澶熺敤缁ф壙鎵╁睍灏辫浜�
+ return loginService.buildLoginUser(user);
+ });
+ loginUser.setClientKey(client.getClientKey());
+ loginUser.setDeviceType(client.getDeviceType());
+ SaLoginParameter model = new SaLoginParameter();
+ model.setDeviceType(client.getDeviceType());
+ // 鑷畾涔夊垎閰� 涓嶅悓鐢ㄦ埛浣撶郴 涓嶅悓 token 鎺堟潈鏃堕棿 涓嶈缃粯璁よ蛋鍏ㄥ眬 yml 閰嶇疆
+ // 渚嬪: 鍚庡彴鐢ㄦ埛30鍒嗛挓杩囨湡 app鐢ㄦ埛1澶╄繃鏈�
+ model.setTimeout(client.getTimeout());
+ model.setActiveTimeout(client.getActiveTimeout());
+ model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+ // 鐢熸垚token
+ LoginHelper.login(loginUser, model);
+
+ LoginVo loginVo = new LoginVo();
+ loginVo.setAccessToken(StpUtil.getTokenValue());
+ loginVo.setExpireIn(StpUtil.getTokenTimeout());
+ loginVo.setClientId(client.getClientId());
+ return loginVo;
+ }
+
+ /**
+ * 鏍¢獙鐭俊楠岃瘉鐮�
+ */
+ private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
+ String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
+ if (StringUtils.isBlank(code)) {
+ loginService.recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+ throw new CaptchaExpireException();
+ }
+ return code.equals(smsCode);
+ }
+
+ private SysUserVo loadUserByPhonenumber(String phonenumber) {
+ SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
+ if (ObjectUtil.isNull(user)) {
+ log.info("鐧诲綍鐢ㄦ埛锛歿} 涓嶅瓨鍦�.", phonenumber);
+ throw new UserException("user.not.exists", phonenumber);
+ } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
+ log.info("鐧诲綍鐢ㄦ埛锛歿} 宸茶鍋滅敤.", phonenumber);
+ throw new UserException("user.blocked", phonenumber);
+ }
+ return user;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java
new file mode 100755
index 0000000..e01666e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java
@@ -0,0 +1,119 @@
+package org.dromara.web.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.domain.model.SocialLoginBody;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.exception.user.UserException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.social.config.properties.SocialProperties;
+import org.dromara.common.social.utils.SocialUtils;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.system.domain.vo.SysSocialVo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.system.service.ISysSocialService;
+import org.dromara.web.domain.vo.LoginVo;
+import org.dromara.web.service.IAuthStrategy;
+import org.dromara.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 绗笁鏂规巿鏉冪瓥鐣�
+ *
+ * @author thiszhc is 涓変笁
+ */
+@Slf4j
+@Service("social" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class SocialAuthStrategy implements IAuthStrategy {
+
+ private final SocialProperties socialProperties;
+ private final ISysSocialService sysSocialService;
+ private final SysUserMapper userMapper;
+ private final SysLoginService loginService;
+
+ /**
+ * 鐧诲綍-绗笁鏂规巿鏉冪櫥褰�
+ *
+ * @param body 鐧诲綍淇℃伅
+ * @param client 瀹㈡埛绔俊鎭�
+ */
+ @Override
+ public LoginVo login(String body, SysClientVo client) {
+ SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
+ ValidatorUtils.validate(loginBody);
+ AuthResponse<AuthUser> response = SocialUtils.loginAuth(
+ loginBody.getSource(), loginBody.getSocialCode(),
+ loginBody.getSocialState(), socialProperties);
+ if (!response.ok()) {
+ throw new ServiceException(response.getMsg());
+ }
+ AuthUser authUserData = response.getData();
+
+ List<SysSocialVo> list = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
+ if (CollUtil.isEmpty(list)) {
+ throw new ServiceException("浣犺繕娌℃湁缁戝畾绗笁鏂硅处鍙凤紝缁戝畾鍚庢墠鍙互鐧诲綍锛�");
+ }
+ SysSocialVo social;
+ if (TenantHelper.isEnable()) {
+ Optional<SysSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId()));
+ if (opt.isEmpty()) {
+ throw new ServiceException("瀵逛笉璧凤紝浣犳病鏈夋潈闄愮櫥褰曞綋鍓嶇鎴凤紒");
+ }
+ social = opt.get();
+ } else {
+ social = list.get(0);
+ }
+ LoginUser loginUser = TenantHelper.dynamic(social.getTenantId(), () -> {
+ SysUserVo user = loadUser(social.getUserId());
+ // 姝ゅ鍙牴鎹櫥褰曠敤鎴风殑鏁版嵁涓嶅悓 鑷鍒涘缓 loginUser 灞炴�т笉澶熺敤缁ф壙鎵╁睍灏辫浜�
+ return loginService.buildLoginUser(user);
+ });
+ loginUser.setClientKey(client.getClientKey());
+ loginUser.setDeviceType(client.getDeviceType());
+ SaLoginParameter model = new SaLoginParameter();
+ model.setDeviceType(client.getDeviceType());
+ // 鑷畾涔夊垎閰� 涓嶅悓鐢ㄦ埛浣撶郴 涓嶅悓 token 鎺堟潈鏃堕棿 涓嶈缃粯璁よ蛋鍏ㄥ眬 yml 閰嶇疆
+ // 渚嬪: 鍚庡彴鐢ㄦ埛30鍒嗛挓杩囨湡 app鐢ㄦ埛1澶╄繃鏈�
+ model.setTimeout(client.getTimeout());
+ model.setActiveTimeout(client.getActiveTimeout());
+ model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+ // 鐢熸垚token
+ LoginHelper.login(loginUser, model);
+
+ LoginVo loginVo = new LoginVo();
+ loginVo.setAccessToken(StpUtil.getTokenValue());
+ loginVo.setExpireIn(StpUtil.getTokenTimeout());
+ loginVo.setClientId(client.getClientId());
+ return loginVo;
+ }
+
+ private SysUserVo loadUser(Long userId) {
+ SysUserVo user = userMapper.selectVoById(userId);
+ if (ObjectUtil.isNull(user)) {
+ log.info("鐧诲綍鐢ㄦ埛锛歿} 涓嶅瓨鍦�.", "");
+ throw new UserException("user.not.exists", "");
+ } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
+ log.info("鐧诲綍鐢ㄦ埛锛歿} 宸茶鍋滅敤.", "");
+ throw new UserException("user.blocked", "");
+ }
+ return user;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java
new file mode 100755
index 0000000..f223dd8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java
@@ -0,0 +1,111 @@
+package org.dromara.web.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthRequest;
+import me.zhyd.oauth.request.AuthWechatMiniProgramRequest;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.model.XcxLoginBody;
+import org.dromara.common.core.domain.model.XcxLoginUser;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.web.domain.vo.LoginVo;
+import org.dromara.web.service.IAuthStrategy;
+import org.dromara.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 灏忕▼搴忚璇佺瓥鐣�
+ *
+ * @author Michelle.Chung
+ */
+@Slf4j
+@Service("xcx" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class XcxAuthStrategy implements IAuthStrategy {
+
+ private final SysLoginService loginService;
+
+ @Override
+ public LoginVo login(String body, SysClientVo client) {
+ XcxLoginBody loginBody = JsonUtils.parseObject(body, XcxLoginBody.class);
+ ValidatorUtils.validate(loginBody);
+ // xcxCode 涓� 灏忕▼搴忚皟鐢� wx.login 鎺堟潈鍚庤幏鍙�
+ String xcxCode = loginBody.getXcxCode();
+ // 澶氫釜灏忕▼搴忚瘑鍒娇鐢�
+ String appid = loginBody.getAppid();
+
+ // 鏍¢獙 appid + appsrcret + xcxCode 璋冪敤鐧诲綍鍑瘉鏍¢獙鎺ュ彛 鑾峰彇 session_key 涓� openid
+ AuthRequest authRequest = new AuthWechatMiniProgramRequest(AuthConfig.builder()
+ .clientId(appid).clientSecret("鑷濉啓瀵嗛挜 鍙牴鎹笉鍚宎ppid濉叆涓嶅悓瀵嗛挜")
+ .ignoreCheckRedirectUri(true).ignoreCheckState(true).build());
+ AuthCallback authCallback = new AuthCallback();
+ authCallback.setCode(xcxCode);
+ AuthResponse<AuthUser> resp = authRequest.login(authCallback);
+ String openid, unionId;
+ if (resp.ok()) {
+ AuthToken token = resp.getData().getToken();
+ openid = token.getOpenId();
+ // 寰俊灏忕▼搴忓彧鏈夊叧鑱斿埌寰俊寮�鏀惧钩鍙颁笅涔嬪悗鎵嶈兘鑾峰彇鍒� unionId锛屽洜姝nionId涓嶄竴瀹氳兘杩斿洖銆�
+ unionId = token.getUnionId();
+ } else {
+ throw new ServiceException(resp.getMsg());
+ }
+ // 妗嗘灦鐧诲綍涓嶉檺鍒朵粠浠�涔堣〃鏌ヨ 鍙鏈�缁堟瀯寤哄嚭 LoginUser 鍗冲彲
+ SysUserVo user = loadUserByOpenid(openid);
+ // 姝ゅ鍙牴鎹櫥褰曠敤鎴风殑鏁版嵁涓嶅悓 鑷鍒涘缓 loginUser 灞炴�т笉澶熺敤缁ф壙鎵╁睍灏辫浜�
+ XcxLoginUser loginUser = new XcxLoginUser();
+ loginUser.setTenantId(user.getTenantId());
+ loginUser.setUserId(user.getUserId());
+ loginUser.setUsername(user.getUserName());
+ loginUser.setNickname(user.getNickName());
+ loginUser.setUserType(user.getUserType());
+ loginUser.setClientKey(client.getClientKey());
+ loginUser.setDeviceType(client.getDeviceType());
+ loginUser.setOpenid(openid);
+
+ SaLoginParameter model = new SaLoginParameter();
+ model.setDeviceType(client.getDeviceType());
+ // 鑷畾涔夊垎閰� 涓嶅悓鐢ㄦ埛浣撶郴 涓嶅悓 token 鎺堟潈鏃堕棿 涓嶈缃粯璁よ蛋鍏ㄥ眬 yml 閰嶇疆
+ // 渚嬪: 鍚庡彴鐢ㄦ埛30鍒嗛挓杩囨湡 app鐢ㄦ埛1澶╄繃鏈�
+ model.setTimeout(client.getTimeout());
+ model.setActiveTimeout(client.getActiveTimeout());
+ model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+ // 鐢熸垚token
+ LoginHelper.login(loginUser, model);
+
+ LoginVo loginVo = new LoginVo();
+ loginVo.setAccessToken(StpUtil.getTokenValue());
+ loginVo.setExpireIn(StpUtil.getTokenTimeout());
+ loginVo.setClientId(client.getClientId());
+ loginVo.setOpenid(openid);
+ return loginVo;
+ }
+
+ private SysUserVo loadUserByOpenid(String openid) {
+ // 浣跨敤 openid 鏌ヨ缁戝畾鐢ㄦ埛 濡傛湭缁戝畾鐢ㄦ埛 鍒欐牴鎹笟鍔¤嚜琛屽鐞� 渚嬪 鍒涘缓榛樿鐢ㄦ埛
+ // todo 鑷瀹炵幇 userService.selectUserByOpenid(openid);
+ SysUserVo user = new SysUserVo();
+ if (ObjectUtil.isNull(user)) {
+ log.info("鐧诲綍鐢ㄦ埛锛歿} 涓嶅瓨鍦�.", openid);
+ // todo 鐢ㄦ埛涓嶅瓨鍦� 涓氬姟閫昏緫鑷瀹炵幇
+ } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
+ log.info("鐧诲綍鐢ㄦ埛锛歿} 宸茶鍋滅敤.", openid);
+ // todo 鐢ㄦ埛宸茶鍋滅敤 涓氬姟閫昏緫鑷瀹炵幇
+ }
+ return user;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/application-dev.yml b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/application-dev.yml
new file mode 100755
index 0000000..8e3efe6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/application-dev.yml
@@ -0,0 +1,276 @@
+--- # 鐩戞帶涓績閰嶇疆
+spring.boot.admin.client:
+ # 澧炲姞瀹㈡埛绔紑鍏�
+ enabled: false
+ url: http://localhost:9090/admin
+ instance:
+ service-host-type: IP
+ metadata:
+ username: ${spring.boot.admin.client.username}
+ userpassword: ${spring.boot.admin.client.password}
+ username: @monitor.username@
+ password: @monitor.password@
+
+--- # snail-job 閰嶇疆
+snail-job:
+ enabled: false
+ # 闇�瑕佸湪 SnailJob 鍚庡彴缁勭鐞嗗垱寤哄搴斿悕绉扮殑缁�,鐒跺悗鍒涘缓浠诲姟鐨勬椂鍊欓�夋嫨瀵瑰簲鐨勭粍,鎵嶈兘姝g‘鍒嗘淳浠诲姟
+ group: "ruoyi_group"
+ # SnailJob 鎺ュ叆楠岃瘉浠ょ墝 璇﹁ script/sql/ry_job.sql `sj_group_config` 琛�
+ token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
+ server:
+ host: 127.0.0.1
+ port: 17888
+ # 鍛藉悕绌洪棿UUID 璇﹁ script/sql/ry_job.sql `sj_namespace`琛╜unique_id`瀛楁
+ namespace: ${spring.profiles.active}
+ # 闅忎富搴旂敤绔彛婕傜Щ
+ port: 2${server.port}
+ # 瀹㈡埛绔痠p鎸囧畾
+ host:
+
+--- # 鏁版嵁婧愰厤缃�
+spring:
+ datasource:
+ type: com.zaxxer.hikari.HikariDataSource
+ # 鍔ㄦ�佹暟鎹簮鏂囨。 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
+ dynamic:
+ # 鎬ц兘鍒嗘瀽鎻掍欢(鏈夋�ц兘鎹熻�� 涓嶅缓璁敓浜х幆澧冧娇鐢�)
+ p6spy: true
+ # 璁剧疆榛樿鐨勬暟鎹簮鎴栬�呮暟鎹簮缁�,榛樿鍊煎嵆涓� master
+ primary: master
+ # 涓ユ牸妯″紡 鍖归厤涓嶅埌鏁版嵁婧愬垯鎶ラ敊
+ strict: true
+ datasource:
+ # 涓诲簱鏁版嵁婧�
+ master:
+ type: ${spring.datasource.type}
+ driverClassName: org.postgresql.Driver
+ url: jdbc:postgresql://192.168.2.26:5432/postgres?currentSchema=public&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+ username: postgres
+ password: 123456
+ # master:
+# type: ${spring.datasource.type}
+# driverClassName: com.mysql.cj.jdbc.Driver
+# # jdbc 鎵�鏈夊弬鏁伴厤缃弬鑰� https://lionli.blog.csdn.net/article/details/122018562
+# # rewriteBatchedStatements=true 鎵瑰鐞嗕紭鍖� 澶у箙鎻愬崌鎵归噺鎻掑叆鏇存柊鍒犻櫎鎬ц兘(瀵规暟鎹簱鏈夋�ц兘鎹熻�� 浣跨敤鎵归噺鎿嶄綔搴旇�冭檻鎬ц兘闂)
+# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+# username: root
+# password: 123456
+# # 浠庡簱鏁版嵁婧�
+# slave:
+# lazy: true
+# type: ${spring.datasource.type}
+# driverClassName: com.mysql.cj.jdbc.Driver
+# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+# username:
+# password:
+# oracle:
+# type: ${spring.datasource.type}
+# driverClassName: oracle.jdbc.OracleDriver
+# url: jdbc:oracle:thin:@//localhost:1521/XE
+# username: ROOT
+# password: root
+# postgres:
+# type: ${spring.datasource.type}
+# driverClassName: org.postgresql.Driver
+# url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+# username: root
+# password: root
+# sqlserver:
+# type: ${spring.datasource.type}
+# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
+# url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
+# username: SA
+# password: root
+ hikari:
+ # 鏈�澶ц繛鎺ユ睜鏁伴噺
+ maxPoolSize: 20
+ # 鏈�灏忕┖闂茬嚎绋嬫暟閲�
+ minIdle: 10
+ # 閰嶇疆鑾峰彇杩炴帴绛夊緟瓒呮椂鐨勬椂闂�
+ connectionTimeout: 30000
+ # 鏍¢獙瓒呮椂鏃堕棿
+ validationTimeout: 5000
+ # 绌洪棽杩炴帴瀛樻椿鏈�澶ф椂闂达紝榛樿10鍒嗛挓
+ idleTimeout: 600000
+ # 姝ゅ睘鎬ф帶鍒舵睜涓繛鎺ョ殑鏈�闀跨敓鍛藉懆鏈燂紝鍊�0琛ㄧず鏃犻檺鐢熷懡鍛ㄦ湡锛岄粯璁�30鍒嗛挓
+ maxLifetime: 1800000
+ # 澶氫箙妫�鏌ヤ竴娆¤繛鎺ョ殑娲绘��
+ keepaliveTime: 30000
+
+--- # redis 鍗曟満閰嶇疆(鍗曟満涓庨泦缇ゅ彧鑳藉紑鍚竴涓彟涓�涓渶瑕佹敞閲婃帀)
+spring.data:
+ redis:
+ # 鍦板潃
+ host: localhost
+ # 绔彛锛岄粯璁や负6677
+ port: 6379
+ # 鏁版嵁搴撶储寮�
+ database: 0
+ # redis 瀵嗙爜蹇呴』閰嶇疆
+# password:
+ # 杩炴帴瓒呮椂鏃堕棿
+ timeout: 10s
+ # 鏄惁寮�鍚痵sl
+ ssl.enabled: false
+
+# redisson 閰嶇疆
+redisson:
+ # redis key鍓嶇紑
+ keyPrefix:
+ # 绾跨▼姹犳暟閲�
+ threads: 4
+ # Netty绾跨▼姹犳暟閲�
+ nettyThreads: 8
+ # 鍗曡妭鐐归厤缃�
+ singleServerConfig:
+ # 瀹㈡埛绔悕绉� 涓嶈兘鐢ㄤ腑鏂�
+ clientName: RuoYi-Vue-Plus
+ # 鏈�灏忕┖闂茶繛鎺ユ暟
+ connectionMinimumIdleSize: 8
+ # 杩炴帴姹犲ぇ灏�
+ connectionPoolSize: 32
+ # 杩炴帴绌洪棽瓒呮椂锛屽崟浣嶏細姣
+ idleConnectionTimeout: 10000
+ # 鍛戒护绛夊緟瓒呮椂锛屽崟浣嶏細姣
+ timeout: 3000
+ # 鍙戝竷鍜岃闃呰繛鎺ユ睜澶у皬
+ subscriptionConnectionPoolSize: 50
+
+--- # mail 閭欢鍙戦��
+mail:
+ enabled: false
+ host: smtp.163.com
+ port: 465
+ # 鏄惁闇�瑕佺敤鎴峰悕瀵嗙爜楠岃瘉
+ auth: true
+ # 鍙戦�佹柟锛岄伒寰猂FC-822鏍囧噯
+ from: xxx@163.com
+ # 鐢ㄦ埛鍚嶏紙娉ㄦ剰锛氬鏋滀娇鐢╢oxmail閭锛屾澶剈ser涓簈q鍙凤級
+ user: xxx@163.com
+ # 瀵嗙爜锛堟敞鎰忥紝鏌愪簺閭闇�瑕佷负SMTP鏈嶅姟鍗曠嫭璁剧疆瀵嗙爜锛岃鎯呮煡鐪嬬浉鍏冲府鍔╋級
+ pass: xxxxxxxxxx
+ # 浣跨敤 STARTTLS瀹夊叏杩炴帴锛孲TARTTLS鏄绾枃鏈�氫俊鍗忚鐨勬墿灞曘��
+ starttlsEnable: true
+ # 浣跨敤SSL瀹夊叏杩炴帴
+ sslEnable: true
+ # SMTP瓒呮椂鏃堕暱锛屽崟浣嶆绉掞紝缂虹渷鍊间笉瓒呮椂
+ timeout: 0
+ # Socket杩炴帴瓒呮椂鍊硷紝鍗曚綅姣锛岀己鐪佸�间笉瓒呮椂
+ connectionTimeout: 0
+
+--- # sms 鐭俊 鏀寔 闃块噷浜� 鑵捐浜� 浜戠墖 绛夌瓑鍚勫紡鍚勬牱鐨勭煭淇℃湇鍔″晢
+# https://sms4j.com/doc3/ 宸紓閰嶇疆鏂囨。鍦板潃 鏀寔鍗曞巶鍟嗗閰嶇疆锛屽彲浠ラ厤缃涓悓鏃朵娇鐢�
+sms:
+ # 閰嶇疆婧愮被鍨嬬敤浜庢爣瀹氶厤缃潵婧�(interface,yaml)
+ config-type: yaml
+ # 鐢ㄤ簬鏍囧畾yml涓殑閰嶇疆鏄惁寮�鍚煭淇℃嫤鎴紝鎺ュ彛閰嶇疆涓嶅彈姝ら檺鍒�
+ restricted: true
+ # 鐭俊鎷︽埅闄愬埗鍗曟墜鏈哄彿姣忓垎閽熸渶澶у彂閫侊紝鍙寮�鍚簡鎷︽埅鐨勯厤缃湁鏁�
+ minute-max: 1
+ # 鐭俊鎷︽埅闄愬埗鍗曟墜鏈哄彿姣忔棩鏈�澶у彂閫侀噺锛屽彧瀵瑰紑鍚簡鎷︽埅鐨勯厤缃湁鏁�
+ account-max: 30
+ # 浠ヤ笅閰嶇疆鏉ヨ嚜浜� org.dromara.sms4j.provider.config.BaseConfig绫讳腑
+ blends:
+ # 鍞竴ID 鐢ㄤ簬鍙戦�佺煭淇″鎵惧叿浣撻厤缃� 闅忎究瀹氫箟鍒敤涓枃鍗冲彲
+ # 鍙互鍚屾椂瀛樺湪涓や釜鐩稿悓鍘傚晢 渚嬪: ali1 ali2 涓や釜涓嶅悓鐨勯樋閲岀煭淇¤处鍙� 涔熷彲鐢ㄤ簬鍖哄垎绉熸埛
+ config1:
+ # 妗嗘灦瀹氫箟鐨勫巶鍟嗗悕绉版爣璇嗭紝鏍囧畾姝ら厤缃槸鍝釜鍘傚晢锛岃缁嗚鐪嬪巶鍟嗘爣璇嗕粙缁嶉儴鍒�
+ supplier: alibaba
+ # 鏈変簺绉颁负accessKey鏈変簺绉颁箣涓篴piKey锛屼篃鏈夌О涓簊dkKey鎴栬�卆ppId銆�
+ access-key-id: 鎮ㄧ殑accessKey
+ # 绉颁负accessSecret鏈変簺绉颁箣涓篴piSecret
+ access-key-secret: 鎮ㄧ殑accessKeySecret
+ signature: 鎮ㄧ殑鐭俊绛惧悕
+ sdk-app-id: 鎮ㄧ殑sdkAppId
+ config2:
+ # 鍘傚晢鏍囪瘑锛屾爣瀹氭閰嶇疆鏄摢涓巶鍟嗭紝璇︾粏璇风湅鍘傚晢鏍囪瘑浠嬬粛閮ㄥ垎
+ supplier: tencent
+ access-key-id: 鎮ㄧ殑accessKey
+ access-key-secret: 鎮ㄧ殑accessKeySecret
+ signature: 鎮ㄧ殑鐭俊绛惧悕
+ sdk-app-id: 鎮ㄧ殑sdkAppId
+
+
+--- # 涓夋柟鎺堟潈
+justauth:
+ # 鍓嶇澶栫綉璁块棶鍦板潃
+ address: http://localhost:80
+ type:
+ maxkey:
+ # maxkey 鏈嶅姟鍣ㄥ湴鍧�
+ # 娉ㄦ剰 濡備笅鍧囬厤缃潎涓嶉渶瑕佷慨鏀� maxkey 宸茬粡鍐呯疆濂戒簡鏁版嵁
+ server-url: http://sso.maxkey.top
+ client-id: 876892492581044224
+ client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
+ redirect-uri: ${justauth.address}/social-callback?source=maxkey
+ topiam:
+ # topiam 鏈嶅姟鍣ㄥ湴鍧�
+ server-url: http://127.0.0.1:1898/api/v1/authorize/y0q************spq***********8ol
+ client-id: 449c4*********937************759
+ client-secret: ac7***********1e0************28d
+ redirect-uri: ${justauth.address}/social-callback?source=topiam
+ scopes: [openid, email, phone, profile]
+ qq:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=qq
+ union-id: false
+ weibo:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=weibo
+ gitee:
+ client-id: 91436b7940090d09c72c7daf85b959cfd5f215d67eea73acbf61b6b590751a98
+ client-secret: 02c6fcfd70342980cd8dd2f2c06c1a350645d76c754d7a264c4e125f9ba915ac
+ redirect-uri: ${justauth.address}/social-callback?source=gitee
+ dingtalk:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=dingtalk
+ baidu:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=baidu
+ csdn:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=csdn
+ coding:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=coding
+ coding-group-name: xx
+ oschina:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=oschina
+ alipay_wallet:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet
+ alipay-public-key: MIIB**************DAQAB
+ wechat_open:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=wechat_open
+ wechat_mp:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=wechat_mp
+ wechat_enterprise:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=wechat_enterprise
+ agent-id: 1000002
+ gitlab:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=gitlab
+ gitea:
+ # 鍓嶇鏀瑰姩 https://gitee.com/JavaLionLi/plus-ui/pulls/204
+ # gitea 鏈嶅姟鍣ㄥ湴鍧�
+ server-url: https://demo.gitea.com
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=gitea
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/application-prod.yml b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/application-prod.yml
new file mode 100755
index 0000000..d77ddf5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/application-prod.yml
@@ -0,0 +1,272 @@
+--- # 涓存椂鏂囦欢瀛樺偍浣嶇疆 閬垮厤涓存椂鏂囦欢琚郴缁熸竻鐞嗘姤閿�
+spring.servlet.multipart.location: /ruoyi/server/temp
+
+--- # 鐩戞帶涓績閰嶇疆
+spring.boot.admin.client:
+ # 澧炲姞瀹㈡埛绔紑鍏�
+ enabled: true
+ url: http://localhost:9090/admin
+ instance:
+ service-host-type: IP
+ metadata:
+ username: ${spring.boot.admin.client.username}
+ userpassword: ${spring.boot.admin.client.password}
+ username: @monitor.username@
+ password: @monitor.password@
+
+--- # snail-job 閰嶇疆
+snail-job:
+ enabled: true
+ # 闇�瑕佸湪 SnailJob 鍚庡彴缁勭鐞嗗垱寤哄搴斿悕绉扮殑缁�,鐒跺悗鍒涘缓浠诲姟鐨勬椂鍊欓�夋嫨瀵瑰簲鐨勭粍,鎵嶈兘姝g‘鍒嗘淳浠诲姟
+ group: "ruoyi_group"
+ # SnailJob 鎺ュ叆楠岃瘉浠ょ墝 璇﹁ script/sql/ry_job.sql `sj_group_config`琛�
+ token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
+ server:
+ host: 127.0.0.1
+ port: 17888
+ # 鍛藉悕绌洪棿UUID 璇﹁ script/sql/ry_job.sql `sj_namespace`琛╜unique_id`瀛楁
+ namespace: ${spring.profiles.active}
+ # 闅忎富搴旂敤绔彛婕傜Щ
+ port: 2${server.port}
+ # 瀹㈡埛绔痠p鎸囧畾
+ host:
+
+--- # 鏁版嵁婧愰厤缃�
+spring:
+ datasource:
+ type: com.zaxxer.hikari.HikariDataSource
+ # 鍔ㄦ�佹暟鎹簮鏂囨。 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
+ dynamic:
+ # 鎬ц兘鍒嗘瀽鎻掍欢(鏈夋�ц兘鎹熻�� 涓嶅缓璁敓浜х幆澧冧娇鐢�)
+ p6spy: false
+ # 璁剧疆榛樿鐨勬暟鎹簮鎴栬�呮暟鎹簮缁�,榛樿鍊煎嵆涓� master
+ primary: master
+ # 涓ユ牸妯″紡 鍖归厤涓嶅埌鏁版嵁婧愬垯鎶ラ敊
+ strict: true
+ datasource:
+ # 涓诲簱鏁版嵁婧�
+ master:
+ type: ${spring.datasource.type}
+ driverClassName: com.mysql.cj.jdbc.Driver
+ # jdbc 鎵�鏈夊弬鏁伴厤缃弬鑰� https://lionli.blog.csdn.net/article/details/122018562
+ # rewriteBatchedStatements=true 鎵瑰鐞嗕紭鍖� 澶у箙鎻愬崌鎵归噺鎻掑叆鏇存柊鍒犻櫎鎬ц兘(瀵规暟鎹簱鏈夋�ц兘鎹熻�� 浣跨敤鎵归噺鎿嶄綔搴旇�冭檻鎬ц兘闂)
+ url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+ username: root
+ password: root
+# # 浠庡簱鏁版嵁婧�
+# slave:
+# lazy: true
+# type: ${spring.datasource.type}
+# driverClassName: com.mysql.cj.jdbc.Driver
+# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+# username:
+# password:
+# oracle:
+# type: ${spring.datasource.type}
+# driverClassName: oracle.jdbc.OracleDriver
+# url: jdbc:oracle:thin:@//localhost:1521/XE
+# username: ROOT
+# password: root
+# postgres:
+# type: ${spring.datasource.type}
+# driverClassName: org.postgresql.Driver
+# url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+# username: root
+# password: root
+# sqlserver:
+# type: ${spring.datasource.type}
+# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
+# url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
+# username: SA
+# password: root
+ hikari:
+ # 鏈�澶ц繛鎺ユ睜鏁伴噺
+ maxPoolSize: 20
+ # 鏈�灏忕┖闂茬嚎绋嬫暟閲�
+ minIdle: 10
+ # 閰嶇疆鑾峰彇杩炴帴绛夊緟瓒呮椂鐨勬椂闂�
+ connectionTimeout: 30000
+ # 鏍¢獙瓒呮椂鏃堕棿
+ validationTimeout: 5000
+ # 绌洪棽杩炴帴瀛樻椿鏈�澶ф椂闂达紝榛樿10鍒嗛挓
+ idleTimeout: 600000
+ # 姝ゅ睘鎬ф帶鍒舵睜涓繛鎺ョ殑鏈�闀跨敓鍛藉懆鏈燂紝鍊�0琛ㄧず鏃犻檺鐢熷懡鍛ㄦ湡锛岄粯璁�30鍒嗛挓
+ maxLifetime: 1800000
+ # 澶氫箙妫�鏌ヤ竴娆¤繛鎺ョ殑娲绘��
+ keepaliveTime: 30000
+
+--- # redis 鍗曟満閰嶇疆(鍗曟満涓庨泦缇ゅ彧鑳藉紑鍚竴涓彟涓�涓渶瑕佹敞閲婃帀)
+spring.data:
+ redis:
+ # 鍦板潃
+ host: localhost
+ # 绔彛锛岄粯璁や负6379
+ port: 6379
+ # 鏁版嵁搴撶储寮�
+ database: 0
+ # redis 瀵嗙爜蹇呴』閰嶇疆
+ password: ruoyi123
+ # 杩炴帴瓒呮椂鏃堕棿
+ timeout: 10s
+ # 鏄惁寮�鍚痵sl
+ ssl.enabled: false
+
+# redisson 閰嶇疆
+redisson:
+ # redis key鍓嶇紑
+ keyPrefix:
+ # 绾跨▼姹犳暟閲�
+ threads: 16
+ # Netty绾跨▼姹犳暟閲�
+ nettyThreads: 32
+ # 鍗曡妭鐐归厤缃�
+ singleServerConfig:
+ # 瀹㈡埛绔悕绉� 涓嶈兘鐢ㄤ腑鏂�
+ clientName: RuoYi-Vue-Plus
+ # 鏈�灏忕┖闂茶繛鎺ユ暟
+ connectionMinimumIdleSize: 32
+ # 杩炴帴姹犲ぇ灏�
+ connectionPoolSize: 64
+ # 杩炴帴绌洪棽瓒呮椂锛屽崟浣嶏細姣
+ idleConnectionTimeout: 10000
+ # 鍛戒护绛夊緟瓒呮椂锛屽崟浣嶏細姣
+ timeout: 3000
+ # 鍙戝竷鍜岃闃呰繛鎺ユ睜澶у皬
+ subscriptionConnectionPoolSize: 50
+
+--- # mail 閭欢鍙戦��
+mail:
+ enabled: false
+ host: smtp.163.com
+ port: 465
+ # 鏄惁闇�瑕佺敤鎴峰悕瀵嗙爜楠岃瘉
+ auth: true
+ # 鍙戦�佹柟锛岄伒寰猂FC-822鏍囧噯
+ from: xxx@163.com
+ # 鐢ㄦ埛鍚嶏紙娉ㄦ剰锛氬鏋滀娇鐢╢oxmail閭锛屾澶剈ser涓簈q鍙凤級
+ user: xxx@163.com
+ # 瀵嗙爜锛堟敞鎰忥紝鏌愪簺閭闇�瑕佷负SMTP鏈嶅姟鍗曠嫭璁剧疆瀵嗙爜锛岃鎯呮煡鐪嬬浉鍏冲府鍔╋級
+ pass: xxxxxxxxxx
+ # 浣跨敤 STARTTLS瀹夊叏杩炴帴锛孲TARTTLS鏄绾枃鏈�氫俊鍗忚鐨勬墿灞曘��
+ starttlsEnable: true
+ # 浣跨敤SSL瀹夊叏杩炴帴
+ sslEnable: true
+ # SMTP瓒呮椂鏃堕暱锛屽崟浣嶆绉掞紝缂虹渷鍊间笉瓒呮椂
+ timeout: 0
+ # Socket杩炴帴瓒呮椂鍊硷紝鍗曚綅姣锛岀己鐪佸�间笉瓒呮椂
+ connectionTimeout: 0
+
+--- # sms 鐭俊 鏀寔 闃块噷浜� 鑵捐浜� 浜戠墖 绛夌瓑鍚勫紡鍚勬牱鐨勭煭淇℃湇鍔″晢
+# https://sms4j.com/doc3/ 宸紓閰嶇疆鏂囨。鍦板潃 鏀寔鍗曞巶鍟嗗閰嶇疆锛屽彲浠ラ厤缃涓悓鏃朵娇鐢�
+sms:
+ # 閰嶇疆婧愮被鍨嬬敤浜庢爣瀹氶厤缃潵婧�(interface,yaml)
+ config-type: yaml
+ # 鐢ㄤ簬鏍囧畾yml涓殑閰嶇疆鏄惁寮�鍚煭淇℃嫤鎴紝鎺ュ彛閰嶇疆涓嶅彈姝ら檺鍒�
+ restricted: true
+ # 鐭俊鎷︽埅闄愬埗鍗曟墜鏈哄彿姣忓垎閽熸渶澶у彂閫侊紝鍙寮�鍚簡鎷︽埅鐨勯厤缃湁鏁�
+ minute-max: 1
+ # 鐭俊鎷︽埅闄愬埗鍗曟墜鏈哄彿姣忔棩鏈�澶у彂閫侀噺锛屽彧瀵瑰紑鍚簡鎷︽埅鐨勯厤缃湁鏁�
+ account-max: 30
+ # 浠ヤ笅閰嶇疆鏉ヨ嚜浜� org.dromara.sms4j.provider.config.BaseConfig绫讳腑
+ blends:
+ # 鍞竴ID 鐢ㄤ簬鍙戦�佺煭淇″鎵惧叿浣撻厤缃� 闅忎究瀹氫箟鍒敤涓枃鍗冲彲
+ # 鍙互鍚屾椂瀛樺湪涓や釜鐩稿悓鍘傚晢 渚嬪: ali1 ali2 涓や釜涓嶅悓鐨勯樋閲岀煭淇¤处鍙� 涔熷彲鐢ㄤ簬鍖哄垎绉熸埛
+ config1:
+ # 妗嗘灦瀹氫箟鐨勫巶鍟嗗悕绉版爣璇嗭紝鏍囧畾姝ら厤缃槸鍝釜鍘傚晢锛岃缁嗚鐪嬪巶鍟嗘爣璇嗕粙缁嶉儴鍒�
+ supplier: alibaba
+ # 鏈変簺绉颁负accessKey鏈変簺绉颁箣涓篴piKey锛屼篃鏈夌О涓簊dkKey鎴栬�卆ppId銆�
+ access-key-id: 鎮ㄧ殑accessKey
+ # 绉颁负accessSecret鏈変簺绉颁箣涓篴piSecret
+ access-key-secret: 鎮ㄧ殑accessKeySecret
+ signature: 鎮ㄧ殑鐭俊绛惧悕
+ sdk-app-id: 鎮ㄧ殑sdkAppId
+ config2:
+ # 鍘傚晢鏍囪瘑锛屾爣瀹氭閰嶇疆鏄摢涓巶鍟嗭紝璇︾粏璇风湅鍘傚晢鏍囪瘑浠嬬粛閮ㄥ垎
+ supplier: tencent
+ access-key-id: 鎮ㄧ殑accessKey
+ access-key-secret: 鎮ㄧ殑accessKeySecret
+ signature: 鎮ㄧ殑鐭俊绛惧悕
+ sdk-app-id: 鎮ㄧ殑sdkAppId
+
+--- # 涓夋柟鎺堟潈
+justauth:
+ # 鍓嶇澶栫綉璁块棶鍦板潃
+ address: http://localhost:80
+ type:
+ maxkey:
+ # maxkey 鏈嶅姟鍣ㄥ湴鍧�
+ # 娉ㄦ剰 濡備笅鍧囬厤缃潎涓嶉渶瑕佷慨鏀� maxkey 宸茬粡鍐呯疆濂戒簡鏁版嵁
+ server-url: http://sso.maxkey.top
+ client-id: 876892492581044224
+ client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
+ redirect-uri: ${justauth.address}/social-callback?source=maxkey
+ topiam:
+ # topiam 鏈嶅姟鍣ㄥ湴鍧�
+ server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol
+ client-id: 449c4*********937************759
+ client-secret: ac7***********1e0************28d
+ redirect-uri: ${justauth.address}/social-callback?source=topiam
+ scopes: [ openid, email, phone, profile ]
+ qq:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=qq
+ union-id: false
+ weibo:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=weibo
+ gitee:
+ client-id: 91436b7940090d09c72c7daf85b959cfd5f215d67eea73acbf61b6b590751a98
+ client-secret: 02c6fcfd70342980cd8dd2f2c06c1a350645d76c754d7a264c4e125f9ba915ac
+ redirect-uri: ${justauth.address}/social-callback?source=gitee
+ dingtalk:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=dingtalk
+ baidu:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=baidu
+ csdn:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=csdn
+ coding:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=coding
+ coding-group-name: xx
+ oschina:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=oschina
+ alipay_wallet:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet
+ alipay-public-key: MIIB**************DAQAB
+ wechat_open:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=wechat_open
+ wechat_mp:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=wechat_mp
+ wechat_enterprise:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=wechat_enterprise
+ agent-id: 1000002
+ gitlab:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=gitlab
+ gitea:
+ # 鍓嶇鏀瑰姩 https://gitee.com/JavaLionLi/plus-ui/pulls/204
+ # gitea 鏈嶅姟鍣ㄥ湴鍧�
+ server-url: https://demo.gitea.com
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=gitea
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/application.yml b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/application.yml
new file mode 100755
index 0000000..5d93a5a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/application.yml
@@ -0,0 +1,263 @@
+# 寮�鍙戠幆澧冮厤缃�
+server:
+ # 鏈嶅姟鍣ㄧ殑HTTP绔彛锛岄粯璁や负8080
+ port: 8080
+ servlet:
+ # 搴旂敤鐨勮闂矾寰�
+ context-path: /
+ # undertow 閰嶇疆
+ undertow:
+ # HTTP post鍐呭鐨勬渶澶уぇ灏忋�傚綋鍊间负-1鏃讹紝榛樿鍊间负澶у皬鏄棤闄愮殑
+ max-http-post-size: -1
+ # 浠ヤ笅鐨勯厤缃細褰卞搷buffer,杩欎簺buffer浼氱敤浜庢湇鍔″櫒杩炴帴鐨処O鎿嶄綔,鏈夌偣绫讳技netty鐨勬睜鍖栧唴瀛樼鐞�
+ # 姣忓潡buffer鐨勭┖闂村ぇ灏�,瓒婂皬鐨勭┖闂磋鍒╃敤瓒婂厖鍒�
+ buffer-size: 512
+ # 鏄惁鍒嗛厤鐨勭洿鎺ュ唴瀛�
+ direct-buffers: true
+ threads:
+ # 璁剧疆IO绾跨▼鏁�, 瀹冧富瑕佹墽琛岄潪闃诲鐨勪换鍔�,瀹冧滑浼氳礋璐e涓繛鎺�, 榛樿璁剧疆姣忎釜CPU鏍稿績涓�涓嚎绋�
+ io: 8
+ # 闃诲浠诲姟绾跨▼姹�, 褰撴墽琛岀被浼約ervlet璇锋眰闃诲鎿嶄綔, undertow浼氫粠杩欎釜绾跨▼姹犱腑鍙栧緱绾跨▼,瀹冪殑鍊艰缃彇鍐充簬绯荤粺鐨勮礋杞�
+ worker: 256
+
+captcha:
+ # 鏄惁鍚敤楠岃瘉鐮佹牎楠�
+ enable: false
+ # 楠岃瘉鐮佺被鍨� math 鏁扮粍璁$畻 char 瀛楃楠岃瘉
+ type: math
+ # 鏁板瓧楠岃瘉鐮佷綅鏁�
+ numberLength: 1
+ # 瀛楃楠岃瘉鐮侀暱搴�
+ charLength: 4
+
+# 鏃ュ織閰嶇疆
+logging:
+ level:
+ org.dromara: @logging.level@
+ org.springframework: warn
+ org.mybatis.spring.mapper: error
+ org.apache.fury: warn
+ config: classpath:logback-plus.xml
+
+# 鐢ㄦ埛閰嶇疆
+user:
+ password:
+ # 瀵嗙爜鏈�澶ч敊璇鏁�
+ maxRetryCount: 5
+ # 瀵嗙爜閿佸畾鏃堕棿锛堥粯璁�10鍒嗛挓锛�
+ lockTime: 10
+
+# Spring閰嶇疆
+spring:
+ application:
+ name: RuoYi-Vue-Plus
+ threads:
+ # 寮�鍚櫄鎷熺嚎绋� 浠卝dk21鍙敤
+ virtual:
+ enabled: false
+ task:
+ execution:
+ # 浠� springboot 3.5 寮�濮� spring鑷甫绾跨▼姹�
+ # 涓嶅啀闇�瑕� AsyncConfig涓嶵hreadPoolConfig 鍙洿鎺ユ敞鍏ョ嚎绋嬫睜浣跨敤
+ thread-name-prefix: async-
+ # 鐢眘pring鑷繁鍒濆鍖栫嚎绋嬫睜
+ mode: force
+ # 璧勬簮淇℃伅
+ messages:
+ # 鍥介檯鍖栬祫婧愭枃浠惰矾寰�
+ basename: i18n/messages
+ profiles:
+ active: @profiles.active@
+ # 鏂囦欢涓婁紶
+ servlet:
+ multipart:
+ # 鍗曚釜鏂囦欢澶у皬
+ max-file-size: 10MB
+ # 璁剧疆鎬讳笂浼犵殑鏂囦欢澶у皬
+ max-request-size: 20MB
+ mvc:
+ # 璁剧疆闈欐�佽祫婧愯矾寰� 闃叉鎵�鏈夎姹傞兘鍘绘煡闈欐�佽祫婧�
+ static-path-pattern: /static/**
+ format:
+ date-time: yyyy-MM-dd HH:mm:ss
+ jackson:
+ # 鏃ユ湡鏍煎紡鍖�
+ date-format: yyyy-MM-dd HH:mm:ss
+ serialization:
+ # 鏍煎紡鍖栬緭鍑�
+ indent_output: false
+ # 蹇界暐鏃犳硶杞崲鐨勫璞�
+ fail_on_empty_beans: false
+ deserialization:
+ # 鍏佽瀵硅薄蹇界暐json涓笉瀛樺湪鐨勫睘鎬�
+ fail_on_unknown_properties: false
+
+# Sa-Token閰嶇疆
+sa-token:
+ # token鍚嶇О (鍚屾椂涔熸槸cookie鍚嶇О)
+ token-name: Authorization
+ # 鏄惁鍏佽鍚屼竴璐﹀彿骞跺彂鐧诲綍 (涓簍rue鏃跺厑璁镐竴璧风櫥褰�, 涓篺alse鏃舵柊鐧诲綍鎸ゆ帀鏃х櫥褰�)
+ is-concurrent: true
+ # 鍦ㄥ浜虹櫥褰曞悓涓�璐﹀彿鏃讹紝鏄惁鍏辩敤涓�涓猼oken (涓簍rue鏃舵墍鏈夌櫥褰曞叡鐢ㄤ竴涓猼oken, 涓篺alse鏃舵瘡娆$櫥褰曟柊寤轰竴涓猼oken)
+ is-share: false
+ # jwt绉橀挜
+ jwt-secret-key: abcdefghijklmnopqrstuvwxyz
+
+# security閰嶇疆
+security:
+ # 鎺掗櫎璺緞
+ excludes:
+ - /*.html
+ - /**/*.html
+ - /**/*.css
+ - /**/*.js
+ - /favicon.ico
+ - /error
+ - /*/api-docs
+ - /*/api-docs/**
+ - /warm-flow-ui/config
+
+# 澶氱鎴烽厤缃�
+tenant:
+ # 鏄惁寮�鍚�
+ enable: false
+ # 鎺掗櫎琛�
+ excludes:
+ - sys_menu
+ - sys_tenant
+ - sys_tenant_package
+ - sys_role_dept
+ - sys_role_menu
+ - sys_user_post
+ - sys_user_role
+ - sys_client
+ - sys_oss_config
+ - flow_spel
+
+# MyBatisPlus閰嶇疆
+# https://baomidou.com/config/
+mybatis-plus:
+ # 鑷畾涔夐厤缃� 鏄惁鍏ㄥ眬寮�鍚�昏緫鍒犻櫎 鍏抽棴鍚� 鎵�鏈夐�昏緫鍒犻櫎鍔熻兘灏嗗け鏁�
+ enableLogicDelete: true
+ # 澶氬寘鍚嶄娇鐢� 渚嬪 org.dromara.**.mapper,org.xxx.**.mapper
+ mapperPackage: org.dromara.**.mapper
+ # 瀵瑰簲鐨� XML 鏂囦欢浣嶇疆
+ mapperLocations: classpath*:mapper/**/*Mapper.xml
+ # 瀹炰綋鎵弿锛屽涓猵ackage鐢ㄩ�楀彿鎴栬�呭垎鍙峰垎闅�
+ typeAliasesPackage: org.dromara.**.domain
+ global-config:
+ dbConfig:
+ # 涓婚敭绫诲瀷
+ # AUTO 鑷 NONE 绌� INPUT 鐢ㄦ埛杈撳叆 ASSIGN_ID 闆姳 ASSIGN_UUID 鍞竴 UUID
+ # 濡傞渶鏀逛负鑷 闇�瑕佸皢鏁版嵁搴撹〃鍏ㄩ儴璁剧疆涓鸿嚜澧�
+ idType: ASSIGN_ID
+
+# 鏁版嵁鍔犲瘑
+mybatis-encryptor:
+ # 鏄惁寮�鍚姞瀵�
+ enable: false
+ # 榛樿鍔犲瘑绠楁硶
+ algorithm: BASE64
+ # 缂栫爜鏂瑰紡 BASE64/HEX銆傞粯璁ASE64
+ encode: BASE64
+ # 瀹夊叏绉橀挜 瀵圭О绠楁硶鐨勭閽� 濡傦細AES锛孲M4
+ password:
+ # 鍏閽� 闈炲绉扮畻娉曠殑鍏閽� 濡傦細SM2锛孯SA
+ publicKey:
+ privateKey:
+
+# api鎺ュ彛鍔犲瘑
+api-decrypt:
+ # 鏄惁寮�鍚叏灞�鎺ュ彛鍔犲瘑
+ enabled: false
+ # AES 鍔犲瘑澶存爣璇�
+ headerFlag: encrypt-key
+ # 鍝嶅簲鍔犲瘑鍏挜 闈炲绉扮畻娉曠殑鍏閽� 濡傦細SM2锛孯SA 浣跨敤鑰呰鑷鏇存崲
+ # 瀵瑰簲鍓嶇瑙e瘑绉侀挜 MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
+ publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJnNwrj4hi/y3CCJu868ghCG5dUj8wZK++RNlTLcXoMmdZWEQ/u02RgD5LyLAXGjLOjbMtC+/J9qofpSGTKSx/MCAwEAAQ==
+ # 璇锋眰瑙e瘑绉侀挜 闈炲绉扮畻娉曠殑鍏閽� 濡傦細SM2锛孯SA 浣跨敤鑰呰鑷鏇存崲
+ # 瀵瑰簲鍓嶇鍔犲瘑鍏挜 MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
+ privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y=
+
+springdoc:
+ api-docs:
+ # 鏄惁寮�鍚帴鍙f枃妗�
+ enabled: true
+ info:
+ # 鏍囬
+ title: '鏍囬锛歊uoYi-Vue-Plus澶氱鎴风鐞嗙郴缁焈鎺ュ彛鏂囨。'
+ # 鎻忚堪
+ description: '鎻忚堪锛氱敤浜庣鐞嗛泦鍥㈡棗涓嬪叕鍙哥殑浜哄憳淇℃伅,鍏蜂綋鍖呮嫭XXX,XXX妯″潡...'
+ # 鐗堟湰
+ version: '鐗堟湰鍙�: ${project.version}'
+ # 浣滆�呬俊鎭�
+ contact:
+ name: Lion Li
+ email: crazylionli@163.com
+ url: https://gitee.com/dromara/RuoYi-Vue-Plus
+ #杩欓噷瀹氫箟浜嗕袱涓垎缁勶紝鍙畾涔夊涓紝涔熷彲浠ヤ笉瀹氫箟
+ group-configs:
+ - group: 1.婕旂ず妯″潡
+ packages-to-scan: org.dromara.demo
+ - group: 2.閫氱敤妯″潡
+ packages-to-scan: org.dromara.web
+ - group: 3.绯荤粺妯″潡
+ packages-to-scan: org.dromara.system
+ - group: 4.浠g爜鐢熸垚妯″潡
+ packages-to-scan: org.dromara.generator
+ - group: 5.宸ヤ綔娴佹ā鍧�
+ packages-to-scan: org.dromara.workflow
+
+# 闃叉XSS鏀诲嚮
+xss:
+ # 杩囨护寮�鍏�
+ enabled: true
+ # 鎺掗櫎閾炬帴
+ excludeUrls:
+ - /system/notice
+
+--- # 鍒嗗竷寮忛攣 lock4j 鍏ㄥ眬閰嶇疆
+lock4j:
+ # 鑾峰彇鍒嗗竷寮忛攣瓒呮椂鏃堕棿锛岄粯璁や负 3000 姣
+ acquire-timeout: 3000
+ # 鍒嗗竷寮忛攣鐨勮秴鏃舵椂闂达紝榛樿涓� 30 绉�
+ expire: 30000
+
+--- # Actuator 鐩戞帶绔偣鐨勯厤缃」
+management:
+ endpoints:
+ web:
+ exposure:
+ include: '*'
+ endpoint:
+ health:
+ show-details: ALWAYS
+ logfile:
+ external-file: ./logs/sys-console.log
+
+--- # 榛樿/鎺ㄨ崘浣跨敤sse鎺ㄩ��
+sse:
+ enabled: true
+ path: /resource/sse
+
+--- # websocket
+websocket:
+ # 濡傛灉鍏抽棴 闇�瑕佸拰鍓嶇寮�鍏充竴璧峰叧闂�
+ enabled: false
+ # 璺緞
+ path: /resource/websocket
+ # 璁剧疆璁块棶婧愬湴鍧�
+ allowedOrigins: '*'
+
+--- # warm-flow宸ヤ綔娴侀厤缃�
+warm-flow:
+ # 鏄惁寮�鍚伐浣滄祦锛岄粯璁rue
+ enabled: false
+ # 鏄惁寮�鍚璁″櫒ui
+ ui: true
+ # 鏄惁鏄剧ず娴佺▼鍥鹃《閮ㄦ枃瀛�
+ top-text-show: true
+ # 鏄惁娓叉煋鑺傜偣鎮诞鎻愮ず锛岄粯璁rue
+ node-tooltip: true
+ # 榛樿Authorization锛屽鏋滄湁澶氫釜token锛岀敤閫楀彿鍒嗛殧
+ token-name: ${sa-token.token-name},clientid
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/banner.txt b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/banner.txt
new file mode 100755
index 0000000..21b1126
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/banner.txt
@@ -0,0 +1,8 @@
+Application Version: ${revision}
+Spring Boot Version: ${spring-boot.version}
+__________ _____.___.__ ____ ____ __________.__
+\______ \__ __ ____\__ | |__| \ \ / /_ __ ____ \______ \ | __ __ ______
+ | _/ | \/ _ \/ | | | ______ \ Y / | \_/ __ \ ______ | ___/ | | | \/ ___/
+ | | \ | ( <_> )____ | | /_____/ \ /| | /\ ___/ /_____/ | | | |_| | /\___ \
+ |____|_ /____/ \____// ______|__| \___/ |____/ \___ > |____| |____/____//____ >
+ \/ \/ \/ \/
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/i18n/messages.properties b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/i18n/messages.properties
new file mode 100755
index 0000000..e1e5263
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/i18n/messages.properties
@@ -0,0 +1,62 @@
+#閿欒娑堟伅
+not.null=* 蹇呴』濉啓
+user.jcaptcha.error=楠岃瘉鐮侀敊璇�
+user.jcaptcha.expire=楠岃瘉鐮佸凡澶辨晥
+user.not.exists=瀵逛笉璧�, 鎮ㄧ殑璐﹀彿锛歿0} 涓嶅瓨鍦�.
+user.password.not.match=鐢ㄦ埛涓嶅瓨鍦�/瀵嗙爜閿欒
+user.password.retry.limit.count=瀵嗙爜杈撳叆閿欒{0}娆�
+user.password.retry.limit.exceed=瀵嗙爜杈撳叆閿欒{0}娆★紝璐︽埛閿佸畾{1}鍒嗛挓
+user.password.delete=瀵逛笉璧凤紝鎮ㄧ殑璐﹀彿锛歿0} 宸茶鍒犻櫎
+user.blocked=瀵逛笉璧凤紝鎮ㄧ殑璐﹀彿锛歿0} 宸茬鐢紝璇疯仈绯荤鐞嗗憳
+role.blocked=瑙掕壊宸插皝绂侊紝璇疯仈绯荤鐞嗗憳
+user.logout.success=閫�鍑烘垚鍔�
+length.not.valid=闀垮害蹇呴』鍦▄min}鍒皗max}涓瓧绗︿箣闂�
+user.username.not.blank=鐢ㄦ埛鍚嶄笉鑳戒负绌�
+user.username.not.valid=* 2鍒�20涓眽瀛椼�佸瓧姣嶃�佹暟瀛楁垨涓嬪垝绾跨粍鎴愶紝涓斿繀椤讳互闈炴暟瀛楀紑澶�
+user.username.length.valid=璐︽埛闀垮害蹇呴』鍦▄min}鍒皗max}涓瓧绗︿箣闂�
+user.password.not.blank=鐢ㄦ埛瀵嗙爜涓嶈兘涓虹┖
+user.password.length.valid=鐢ㄦ埛瀵嗙爜闀垮害蹇呴』鍦▄min}鍒皗max}涓瓧绗︿箣闂�
+user.password.not.valid=* 5-50涓瓧绗�
+user.password.format.valid=瀵嗙爜蹇呴』鍖呭惈澶у啓瀛楁瘝銆佸皬鍐欏瓧姣嶃�佹暟瀛楀拰鐗规畩瀛楃
+user.email.not.valid=閭鏍煎紡閿欒
+user.email.not.blank=閭涓嶈兘涓虹┖
+user.phonenumber.not.blank=鐢ㄦ埛鎵嬫満鍙蜂笉鑳戒负绌�
+user.mobile.phone.number.not.valid=鎵嬫満鍙锋牸寮忛敊璇�
+user.login.success=鐧诲綍鎴愬姛
+user.register.success=娉ㄥ唽鎴愬姛
+user.register.save.error=淇濆瓨鐢ㄦ埛 {0} 澶辫触锛屾敞鍐岃处鍙峰凡瀛樺湪
+user.register.error=娉ㄥ唽澶辫触锛岃鑱旂郴绯荤粺绠$悊浜哄憳
+user.notfound=璇烽噸鏂扮櫥褰�
+user.forcelogout=绠$悊鍛樺己鍒堕��鍑猴紝璇烽噸鏂扮櫥褰�
+user.unknown.error=鏈煡閿欒锛岃閲嶆柊鐧诲綍
+auth.grant.type.error=璁よ瘉鏉冮檺绫诲瀷閿欒
+auth.grant.type.blocked=璁よ瘉鏉冮檺绫诲瀷宸茬鐢�
+auth.grant.type.not.blank=璁よ瘉鏉冮檺绫诲瀷涓嶈兘涓虹┖
+auth.clientid.not.blank=璁よ瘉瀹㈡埛绔痠d涓嶈兘涓虹┖
+##鏂囦欢涓婁紶娑堟伅
+upload.exceed.maxSize=涓婁紶鐨勬枃浠跺ぇ灏忚秴鍑洪檺鍒剁殑鏂囦欢澶у皬锛�<br/>鍏佽鐨勬枃浠舵渶澶уぇ灏忔槸锛歿0}MB锛�
+upload.filename.exceed.length=涓婁紶鐨勬枃浠跺悕鏈�闀縶0}涓瓧绗�
+##鏉冮檺
+no.permission=鎮ㄦ病鏈夋暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.create.permission=鎮ㄦ病鏈夊垱寤烘暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.update.permission=鎮ㄦ病鏈変慨鏀规暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.delete.permission=鎮ㄦ病鏈夊垹闄ゆ暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.export.permission=鎮ㄦ病鏈夊鍑烘暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.view.permission=鎮ㄦ病鏈夋煡鐪嬫暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+repeat.submit.message=涓嶅厑璁搁噸澶嶆彁浜わ紝璇风◢鍊欏啀璇�
+rate.limiter.message=璁块棶杩囦簬棰戠箒锛岃绋嶅�欏啀璇�
+sms.code.not.blank=鐭俊楠岃瘉鐮佷笉鑳戒负绌�
+sms.code.retry.limit.count=鐭俊楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆�
+sms.code.retry.limit.exceed=鐭俊楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆★紝璐︽埛閿佸畾{1}鍒嗛挓
+email.code.not.blank=閭楠岃瘉鐮佷笉鑳戒负绌�
+email.code.retry.limit.count=閭楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆�
+email.code.retry.limit.exceed=閭楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆★紝璐︽埛閿佸畾{1}鍒嗛挓
+xcx.code.not.blank=灏忕▼搴廩code]涓嶈兘涓虹┖
+social.source.not.blank=绗笁鏂圭櫥褰曞钩鍙癧source]涓嶈兘涓虹┖
+social.code.not.blank=绗笁鏂圭櫥褰曞钩鍙癧code]涓嶈兘涓虹┖
+social.state.not.blank=绗笁鏂圭櫥褰曞钩鍙癧state]涓嶈兘涓虹┖
+##绉熸埛
+tenant.number.not.blank=绉熸埛缂栧彿涓嶈兘涓虹┖
+tenant.not.exists=瀵逛笉璧�, 鎮ㄧ殑绉熸埛涓嶅瓨鍦紝璇疯仈绯荤鐞嗗憳
+tenant.blocked=瀵逛笉璧凤紝鎮ㄧ殑绉熸埛宸茬鐢紝璇疯仈绯荤鐞嗗憳
+tenant.expired=瀵逛笉璧凤紝鎮ㄧ殑绉熸埛宸茶繃鏈燂紝璇疯仈绯荤鐞嗗憳
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
new file mode 100755
index 0000000..306a48f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
@@ -0,0 +1,62 @@
+#閿欒娑堟伅
+not.null=* Required fill in
+user.jcaptcha.error=Captcha error
+user.jcaptcha.expire=Captcha invalid
+user.not.exists=Sorry, your account: {0} does not exist
+user.password.not.match=User does not exist/Password error
+user.password.retry.limit.count=Password input error {0} times
+user.password.retry.limit.exceed=Password input error {0} times, account locked for {1} minutes
+user.password.delete=Sorry, your account锛歿0} has been deleted
+user.blocked=Sorry, your account: {0} has been disabled. Please contact the administrator
+role.blocked=Role disabled锛宲lease contact administrators
+user.logout.success=Exit successful
+length.not.valid=The length must be between {min} and {max} characters
+user.username.not.blank=Username cannot be blank
+user.username.not.valid=* 2 to 20 chinese characters, letters, numbers or underscores, and must start with a non number
+user.username.length.valid=Account length must be between {min} and {max} characters
+user.password.not.blank=Password cannot be empty
+user.password.length.valid=Password length must be between {min} and {max} characters
+user.password.not.valid=* 5-50 characters
+user.password.format.valid=Password must contain uppercase, lowercase, digit, and special character
+user.email.not.valid=Mailbox format error
+user.email.not.blank=Mailbox cannot be blank
+user.phonenumber.not.blank=Phone number cannot be blank
+user.mobile.phone.number.not.valid=Phone number format error
+user.login.success=Login successful
+user.register.success=Register successful
+user.register.save.error=Failed to save user {0}, The registered account already exists
+user.register.error=Register failed, please contact system administrator
+user.notfound=Please login again
+user.forcelogout=The administrator is forced to exit锛宲lease login again
+user.unknown.error=Unknown error, please login again
+auth.grant.type.error=Auth grant type error
+auth.grant.type.blocked=Auth grant type disabled
+auth.grant.type.not.blank=Auth grant type cannot be blank
+auth.clientid.not.blank=Auth clientid cannot be blank
+##鏂囦欢涓婁紶娑堟伅
+upload.exceed.maxSize=The uploaded file size exceeds the limit file size锛�<br/>the maximum allowed file size is锛歿0}MB锛�
+upload.filename.exceed.length=The maximum length of uploaded file name is {0} characters
+##鏉冮檺
+no.permission=You do not have permission to the data锛宲lease contact your administrator to add permissions [{0}]
+no.create.permission=You do not have permission to create data锛宲lease contact your administrator to add permissions [{0}]
+no.update.permission=You do not have permission to modify data锛宲lease contact your administrator to add permissions [{0}]
+no.delete.permission=You do not have permission to delete data锛宲lease contact your administrator to add permissions [{0}]
+no.export.permission=You do not have permission to export data锛宲lease contact your administrator to add permissions [{0}]
+no.view.permission=You do not have permission to view data锛宲lease contact your administrator to add permissions [{0}]
+repeat.submit.message=Repeat submit is not allowed, please try again later
+rate.limiter.message=Visit too frequently, please try again later
+sms.code.not.blank=Sms code cannot be blank
+sms.code.retry.limit.count=Sms code input error {0} times
+sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes
+email.code.not.blank=Email code cannot be blank
+email.code.retry.limit.count=Email code input error {0} times
+email.code.retry.limit.exceed=Email code input error {0} times, account locked for {1} minutes
+xcx.code.not.blank=Mini program [code] cannot be blank
+social.source.not.blank=Social login platform [source] cannot be blank
+social.code.not.blank=Social login platform [code] cannot be blank
+social.state.not.blank=Social login platform [state] cannot be blank
+##绉熸埛
+tenant.number.not.blank=Tenant number cannot be blank
+tenant.not.exists=Sorry, your tenant does not exist. Please contact the administrator
+tenant.blocked=Sorry, your tenant is disabled. Please contact the administrator
+tenant.expired=Sorry, your tenant has expired. Please contact the administrator.
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
new file mode 100755
index 0000000..e1e5263
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
@@ -0,0 +1,62 @@
+#閿欒娑堟伅
+not.null=* 蹇呴』濉啓
+user.jcaptcha.error=楠岃瘉鐮侀敊璇�
+user.jcaptcha.expire=楠岃瘉鐮佸凡澶辨晥
+user.not.exists=瀵逛笉璧�, 鎮ㄧ殑璐﹀彿锛歿0} 涓嶅瓨鍦�.
+user.password.not.match=鐢ㄦ埛涓嶅瓨鍦�/瀵嗙爜閿欒
+user.password.retry.limit.count=瀵嗙爜杈撳叆閿欒{0}娆�
+user.password.retry.limit.exceed=瀵嗙爜杈撳叆閿欒{0}娆★紝璐︽埛閿佸畾{1}鍒嗛挓
+user.password.delete=瀵逛笉璧凤紝鎮ㄧ殑璐﹀彿锛歿0} 宸茶鍒犻櫎
+user.blocked=瀵逛笉璧凤紝鎮ㄧ殑璐﹀彿锛歿0} 宸茬鐢紝璇疯仈绯荤鐞嗗憳
+role.blocked=瑙掕壊宸插皝绂侊紝璇疯仈绯荤鐞嗗憳
+user.logout.success=閫�鍑烘垚鍔�
+length.not.valid=闀垮害蹇呴』鍦▄min}鍒皗max}涓瓧绗︿箣闂�
+user.username.not.blank=鐢ㄦ埛鍚嶄笉鑳戒负绌�
+user.username.not.valid=* 2鍒�20涓眽瀛椼�佸瓧姣嶃�佹暟瀛楁垨涓嬪垝绾跨粍鎴愶紝涓斿繀椤讳互闈炴暟瀛楀紑澶�
+user.username.length.valid=璐︽埛闀垮害蹇呴』鍦▄min}鍒皗max}涓瓧绗︿箣闂�
+user.password.not.blank=鐢ㄦ埛瀵嗙爜涓嶈兘涓虹┖
+user.password.length.valid=鐢ㄦ埛瀵嗙爜闀垮害蹇呴』鍦▄min}鍒皗max}涓瓧绗︿箣闂�
+user.password.not.valid=* 5-50涓瓧绗�
+user.password.format.valid=瀵嗙爜蹇呴』鍖呭惈澶у啓瀛楁瘝銆佸皬鍐欏瓧姣嶃�佹暟瀛楀拰鐗规畩瀛楃
+user.email.not.valid=閭鏍煎紡閿欒
+user.email.not.blank=閭涓嶈兘涓虹┖
+user.phonenumber.not.blank=鐢ㄦ埛鎵嬫満鍙蜂笉鑳戒负绌�
+user.mobile.phone.number.not.valid=鎵嬫満鍙锋牸寮忛敊璇�
+user.login.success=鐧诲綍鎴愬姛
+user.register.success=娉ㄥ唽鎴愬姛
+user.register.save.error=淇濆瓨鐢ㄦ埛 {0} 澶辫触锛屾敞鍐岃处鍙峰凡瀛樺湪
+user.register.error=娉ㄥ唽澶辫触锛岃鑱旂郴绯荤粺绠$悊浜哄憳
+user.notfound=璇烽噸鏂扮櫥褰�
+user.forcelogout=绠$悊鍛樺己鍒堕��鍑猴紝璇烽噸鏂扮櫥褰�
+user.unknown.error=鏈煡閿欒锛岃閲嶆柊鐧诲綍
+auth.grant.type.error=璁よ瘉鏉冮檺绫诲瀷閿欒
+auth.grant.type.blocked=璁よ瘉鏉冮檺绫诲瀷宸茬鐢�
+auth.grant.type.not.blank=璁よ瘉鏉冮檺绫诲瀷涓嶈兘涓虹┖
+auth.clientid.not.blank=璁よ瘉瀹㈡埛绔痠d涓嶈兘涓虹┖
+##鏂囦欢涓婁紶娑堟伅
+upload.exceed.maxSize=涓婁紶鐨勬枃浠跺ぇ灏忚秴鍑洪檺鍒剁殑鏂囦欢澶у皬锛�<br/>鍏佽鐨勬枃浠舵渶澶уぇ灏忔槸锛歿0}MB锛�
+upload.filename.exceed.length=涓婁紶鐨勬枃浠跺悕鏈�闀縶0}涓瓧绗�
+##鏉冮檺
+no.permission=鎮ㄦ病鏈夋暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.create.permission=鎮ㄦ病鏈夊垱寤烘暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.update.permission=鎮ㄦ病鏈変慨鏀规暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.delete.permission=鎮ㄦ病鏈夊垹闄ゆ暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.export.permission=鎮ㄦ病鏈夊鍑烘暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.view.permission=鎮ㄦ病鏈夋煡鐪嬫暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+repeat.submit.message=涓嶅厑璁搁噸澶嶆彁浜わ紝璇风◢鍊欏啀璇�
+rate.limiter.message=璁块棶杩囦簬棰戠箒锛岃绋嶅�欏啀璇�
+sms.code.not.blank=鐭俊楠岃瘉鐮佷笉鑳戒负绌�
+sms.code.retry.limit.count=鐭俊楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆�
+sms.code.retry.limit.exceed=鐭俊楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆★紝璐︽埛閿佸畾{1}鍒嗛挓
+email.code.not.blank=閭楠岃瘉鐮佷笉鑳戒负绌�
+email.code.retry.limit.count=閭楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆�
+email.code.retry.limit.exceed=閭楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆★紝璐︽埛閿佸畾{1}鍒嗛挓
+xcx.code.not.blank=灏忕▼搴廩code]涓嶈兘涓虹┖
+social.source.not.blank=绗笁鏂圭櫥褰曞钩鍙癧source]涓嶈兘涓虹┖
+social.code.not.blank=绗笁鏂圭櫥褰曞钩鍙癧code]涓嶈兘涓虹┖
+social.state.not.blank=绗笁鏂圭櫥褰曞钩鍙癧state]涓嶈兘涓虹┖
+##绉熸埛
+tenant.number.not.blank=绉熸埛缂栧彿涓嶈兘涓虹┖
+tenant.not.exists=瀵逛笉璧�, 鎮ㄧ殑绉熸埛涓嶅瓨鍦紝璇疯仈绯荤鐞嗗憳
+tenant.blocked=瀵逛笉璧凤紝鎮ㄧ殑绉熸埛宸茬鐢紝璇疯仈绯荤鐞嗗憳
+tenant.expired=瀵逛笉璧凤紝鎮ㄧ殑绉熸埛宸茶繃鏈燂紝璇疯仈绯荤鐞嗗憳
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/ip2region_v4.xdb b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/ip2region_v4.xdb
new file mode 100755
index 0000000..6f86c7d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/ip2region_v4.xdb
Binary files differ
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/logback-plus.xml b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/logback-plus.xml
new file mode 100755
index 0000000..2d786f9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/main/resources/logback-plus.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+ <property name="log.path" value="./logs"/>
+ <property name="console.log.pattern"
+ value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
+ <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
+
+ <!-- 鎺у埗鍙拌緭鍑� -->
+ <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>${console.log.pattern}</pattern>
+ <charset>utf-8</charset>
+ </encoder>
+ </appender>
+
+ <!-- 鎺у埗鍙拌緭鍑� -->
+ <appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${log.path}/sys-console.log</file>
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <!-- 鏃ュ織鏂囦欢鍚嶆牸寮� -->
+ <fileNamePattern>${log.path}/sys-console.%d{yyyy-MM-dd}.log</fileNamePattern>
+ <!-- 鏃ュ織鏈�澶� 1澶� -->
+ <maxHistory>1</maxHistory>
+ </rollingPolicy>
+ <encoder>
+ <pattern>${log.pattern}</pattern>
+ <charset>utf-8</charset>
+ </encoder>
+ <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+ <!-- 杩囨护鐨勭骇鍒� -->
+ <level>INFO</level>
+ </filter>
+ </appender>
+
+ <!-- 绯荤粺鏃ュ織杈撳嚭 -->
+ <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${log.path}/sys-info.log</file>
+ <!-- 寰幆鏀跨瓥锛氬熀浜庢椂闂村垱寤烘棩蹇楁枃浠� -->
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <!-- 鏃ュ織鏂囦欢鍚嶆牸寮� -->
+ <fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
+ <!-- 鏃ュ織鏈�澶х殑鍘嗗彶 60澶� -->
+ <maxHistory>60</maxHistory>
+ </rollingPolicy>
+ <encoder>
+ <pattern>${log.pattern}</pattern>
+ </encoder>
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <!-- 杩囨护鐨勭骇鍒� -->
+ <level>INFO</level>
+ <!-- 鍖归厤鏃剁殑鎿嶄綔锛氭帴鏀讹紙璁板綍锛� -->
+ <onMatch>ACCEPT</onMatch>
+ <!-- 涓嶅尮閰嶆椂鐨勬搷浣滐細鎷掔粷锛堜笉璁板綍锛� -->
+ <onMismatch>DENY</onMismatch>
+ </filter>
+ </appender>
+
+ <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${log.path}/sys-error.log</file>
+ <!-- 寰幆鏀跨瓥锛氬熀浜庢椂闂村垱寤烘棩蹇楁枃浠� -->
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <!-- 鏃ュ織鏂囦欢鍚嶆牸寮� -->
+ <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
+ <!-- 鏃ュ織鏈�澶х殑鍘嗗彶 60澶� -->
+ <maxHistory>60</maxHistory>
+ </rollingPolicy>
+ <encoder>
+ <pattern>${log.pattern}</pattern>
+ </encoder>
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <!-- 杩囨护鐨勭骇鍒� -->
+ <level>ERROR</level>
+ <!-- 鍖归厤鏃剁殑鎿嶄綔锛氭帴鏀讹紙璁板綍锛� -->
+ <onMatch>ACCEPT</onMatch>
+ <!-- 涓嶅尮閰嶆椂鐨勬搷浣滐細鎷掔粷锛堜笉璁板綍锛� -->
+ <onMismatch>DENY</onMismatch>
+ </filter>
+ </appender>
+
+ <!-- info寮傛杈撳嚭 -->
+ <appender name="async_info" class="ch.qos.logback.classic.AsyncAppender">
+ <!-- 涓嶄涪澶辨棩蹇�.榛樿鐨�,濡傛灉闃熷垪鐨�80%宸叉弧,鍒欎細涓㈠純TRACT銆丏EBUG銆両NFO绾у埆鐨勬棩蹇� -->
+ <discardingThreshold>0</discardingThreshold>
+ <!-- 鏇存敼榛樿鐨勯槦鍒楃殑娣卞害,璇ュ�间細褰卞搷鎬ц兘.榛樿鍊间负256 -->
+ <queueSize>512</queueSize>
+ <!-- 娣诲姞闄勫姞鐨刟ppender,鏈�澶氬彧鑳芥坊鍔犱竴涓� -->
+ <appender-ref ref="file_info"/>
+ </appender>
+
+ <!-- error寮傛杈撳嚭 -->
+ <appender name="async_error" class="ch.qos.logback.classic.AsyncAppender">
+ <!-- 涓嶄涪澶辨棩蹇�.榛樿鐨�,濡傛灉闃熷垪鐨�80%宸叉弧,鍒欎細涓㈠純TRACT銆丏EBUG銆両NFO绾у埆鐨勬棩蹇� -->
+ <discardingThreshold>0</discardingThreshold>
+ <!-- 鏇存敼榛樿鐨勯槦鍒楃殑娣卞害,璇ュ�间細褰卞搷鎬ц兘.榛樿鍊间负256 -->
+ <queueSize>512</queueSize>
+ <!-- 娣诲姞闄勫姞鐨刟ppender,鏈�澶氬彧鑳芥坊鍔犱竴涓� -->
+ <appender-ref ref="file_error"/>
+ </appender>
+
+ <!-- 鏁村悎 skywalking 鎺у埗鍙拌緭鍑� tid -->
+<!-- <appender name="console" class="ch.qos.logback.core.ConsoleAppender">-->
+<!-- <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">-->
+<!-- <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">-->
+<!-- <pattern>[%tid] ${console.log.pattern}</pattern>-->
+<!-- </layout>-->
+<!-- <charset>utf-8</charset>-->
+<!-- </encoder>-->
+<!-- </appender>-->
+
+ <!-- 鏁村悎 skywalking 鎺ㄩ�侀噰闆嗘棩蹇� -->
+<!-- <appender name="sky_log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">-->
+<!-- <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">-->
+<!-- <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">-->
+<!-- <pattern>[%tid] ${console.log.pattern}</pattern>-->
+<!-- </layout>-->
+<!-- <charset>utf-8</charset>-->
+<!-- </encoder>-->
+<!-- </appender>-->
+
+ <!--绯荤粺鎿嶄綔鏃ュ織-->
+ <root level="info">
+ <appender-ref ref="console" />
+ <appender-ref ref="async_info" />
+ <appender-ref ref="async_error" />
+ <appender-ref ref="file_console" />
+<!-- <appender-ref ref="sky_log"/>-->
+ </root>
+
+</configuration>
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/AssertUnitTest.java b/RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/AssertUnitTest.java
new file mode 100755
index 0000000..dba2323
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/AssertUnitTest.java
@@ -0,0 +1,45 @@
+package org.dromara.test;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * 鏂█鍗曞厓娴嬭瘯妗堜緥
+ *
+ * @author Lion Li
+ */
+@DisplayName("鏂█鍗曞厓娴嬭瘯妗堜緥")
+public class AssertUnitTest {
+
+ @DisplayName("娴嬭瘯 assertEquals 鏂规硶")
+ @Test
+ public void testAssertEquals() {
+ Assertions.assertEquals("666", new String("666"));
+ Assertions.assertNotEquals("666", new String("666"));
+ }
+
+ @DisplayName("娴嬭瘯 assertSame 鏂规硶")
+ @Test
+ public void testAssertSame() {
+ Object obj = new Object();
+ Object obj1 = obj;
+ Assertions.assertSame(obj, obj1);
+ Assertions.assertNotSame(obj, obj1);
+ }
+
+ @DisplayName("娴嬭瘯 assertTrue 鏂规硶")
+ @Test
+ public void testAssertTrue() {
+ Assertions.assertTrue(true);
+ Assertions.assertFalse(true);
+ }
+
+ @DisplayName("娴嬭瘯 assertNull 鏂规硶")
+ @Test
+ public void testAssertNull() {
+ Assertions.assertNull(null);
+ Assertions.assertNotNull(null);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/DemoUnitTest.java b/RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/DemoUnitTest.java
new file mode 100755
index 0000000..2d11a10
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/DemoUnitTest.java
@@ -0,0 +1,70 @@
+package org.dromara.test;
+
+import org.dromara.common.web.config.properties.CaptchaProperties;
+import org.junit.jupiter.api.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 鍗曞厓娴嬭瘯妗堜緥
+ *
+ * @author Lion Li
+ */
+@SpringBootTest // 姝ゆ敞瑙e彧鑳藉湪 springboot 涓诲寘涓嬩娇鐢� 闇�鍖呭惈 main 鏂规硶涓� yml 閰嶇疆鏂囦欢
+@DisplayName("鍗曞厓娴嬭瘯妗堜緥")
+public class DemoUnitTest {
+
+ @Autowired
+ private CaptchaProperties captchaProperties;
+
+ @DisplayName("娴嬭瘯 @SpringBootTest @Test @DisplayName 娉ㄨВ")
+ @Test
+ public void testTest() {
+ System.out.println(captchaProperties);
+ }
+
+ @Disabled
+ @DisplayName("娴嬭瘯 @Disabled 娉ㄨВ")
+ @Test
+ public void testDisabled() {
+ System.out.println(captchaProperties);
+ }
+
+ @Timeout(value = 2L, unit = TimeUnit.SECONDS)
+ @DisplayName("娴嬭瘯 @Timeout 娉ㄨВ")
+ @Test
+ public void testTimeout() throws InterruptedException {
+ Thread.sleep(3000);
+ System.out.println(captchaProperties);
+ }
+
+
+ @DisplayName("娴嬭瘯 @RepeatedTest 娉ㄨВ")
+ @RepeatedTest(3)
+ public void testRepeatedTest() {
+ System.out.println(666);
+ }
+
+ @BeforeAll
+ public static void testBeforeAll() {
+ System.out.println("@BeforeAll ==================");
+ }
+
+ @BeforeEach
+ public void testBeforeEach() {
+ System.out.println("@BeforeEach ==================");
+ }
+
+ @AfterEach
+ public void testAfterEach() {
+ System.out.println("@AfterEach ==================");
+ }
+
+ @AfterAll
+ public static void testAfterAll() {
+ System.out.println("@AfterAll ==================");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/ParamUnitTest.java b/RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/ParamUnitTest.java
new file mode 100755
index 0000000..1db51df
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/ParamUnitTest.java
@@ -0,0 +1,72 @@
+package org.dromara.test;
+
+import org.dromara.common.core.enums.UserType;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+/**
+ * 甯﹀弬鏁板崟鍏冩祴璇曟渚�
+ *
+ * @author Lion Li
+ */
+@DisplayName("甯﹀弬鏁板崟鍏冩祴璇曟渚�")
+public class ParamUnitTest {
+
+ @DisplayName("娴嬭瘯 @ValueSource 娉ㄨВ")
+ @ParameterizedTest
+ @ValueSource(strings = {"t1", "t2", "t3"})
+ public void testValueSource(String str) {
+ System.out.println(str);
+ }
+
+ @DisplayName("娴嬭瘯 @NullSource 娉ㄨВ")
+ @ParameterizedTest
+ @NullSource
+ public void testNullSource(String str) {
+ System.out.println(str);
+ }
+
+ @DisplayName("娴嬭瘯 @EnumSource 娉ㄨВ")
+ @ParameterizedTest
+ @EnumSource(UserType.class)
+ public void testEnumSource(UserType type) {
+ System.out.println(type.getUserType());
+ }
+
+ @DisplayName("娴嬭瘯 @MethodSource 娉ㄨВ")
+ @ParameterizedTest
+ @MethodSource("getParam")
+ public void testMethodSource(String str) {
+ System.out.println(str);
+ }
+
+ public static Stream<String> getParam() {
+ List<String> list = new ArrayList<>();
+ list.add("t1");
+ list.add("t2");
+ list.add("t3");
+ return list.stream();
+ }
+
+ @BeforeEach
+ public void testBeforeEach() {
+ System.out.println("@BeforeEach ==================");
+ }
+
+ @AfterEach
+ public void testAfterEach() {
+ System.out.println("@AfterEach ==================");
+ }
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/TagUnitTest.java b/RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/TagUnitTest.java
new file mode 100755
index 0000000..b50afa6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-admin/src/test/java/org/dromara/test/TagUnitTest.java
@@ -0,0 +1,54 @@
+package org.dromara.test;
+
+import org.junit.jupiter.api.*;
+import org.springframework.boot.test.context.SpringBootTest;
+
+/**
+ * 鏍囩鍗曞厓娴嬭瘯妗堜緥
+ *
+ * @author Lion Li
+ */
+@SpringBootTest
+@DisplayName("鏍囩鍗曞厓娴嬭瘯妗堜緥")
+public class TagUnitTest {
+
+ @Tag("dev")
+ @DisplayName("娴嬭瘯 @Tag dev")
+ @Test
+ public void testTagDev() {
+ System.out.println("dev");
+ }
+
+ @Tag("prod")
+ @DisplayName("娴嬭瘯 @Tag prod")
+ @Test
+ public void testTagProd() {
+ System.out.println("prod");
+ }
+
+ @Tag("local")
+ @DisplayName("娴嬭瘯 @Tag local")
+ @Test
+ public void testTagLocal() {
+ System.out.println("local");
+ }
+
+ @Tag("exclude")
+ @DisplayName("娴嬭瘯 @Tag exclude")
+ @Test
+ public void testTagExclude() {
+ System.out.println("exclude");
+ }
+
+ @BeforeEach
+ public void testBeforeEach() {
+ System.out.println("@BeforeEach ==================");
+ }
+
+ @AfterEach
+ public void testAfterEach() {
+ System.out.println("@AfterEach ==================");
+ }
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/.DS_Store b/RuoYi-Vue-Plus/ruoyi-common/.DS_Store
new file mode 100755
index 0000000..d0dc3b0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/.DS_Store
Binary files differ
diff --git a/RuoYi-Vue-Plus/ruoyi-common/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/.flattened-pom.xml
new file mode 100644
index 0000000..60d91df
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/.flattened-pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-vue-plus</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ <packaging>pom</packaging>
+ <description>common 閫氱敤妯″潡</description>
+ <modules>
+ <module>ruoyi-common-bom</module>
+ <module>ruoyi-common-social</module>
+ <module>ruoyi-common-core</module>
+ <module>ruoyi-common-doc</module>
+ <module>ruoyi-common-excel</module>
+ <module>ruoyi-common-idempotent</module>
+ <module>ruoyi-common-job</module>
+ <module>ruoyi-common-log</module>
+ <module>ruoyi-common-mail</module>
+ <module>ruoyi-common-mybatis</module>
+ <module>ruoyi-common-oss</module>
+ <module>ruoyi-common-ratelimiter</module>
+ <module>ruoyi-common-redis</module>
+ <module>ruoyi-common-satoken</module>
+ <module>ruoyi-common-security</module>
+ <module>ruoyi-common-sms</module>
+ <module>ruoyi-common-web</module>
+ <module>ruoyi-common-translation</module>
+ <module>ruoyi-common-sensitive</module>
+ <module>ruoyi-common-json</module>
+ <module>ruoyi-common-encrypt</module>
+ <module>ruoyi-common-tenant</module>
+ <module>ruoyi-common-websocket</module>
+ <module>ruoyi-common-sse</module>
+ </modules>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/pom.xml
new file mode 100755
index 0000000..2930fd0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>ruoyi-vue-plus</artifactId>
+ <groupId>org.dromara</groupId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <modules>
+ <module>ruoyi-common-bom</module>
+ <module>ruoyi-common-social</module>
+ <module>ruoyi-common-core</module>
+ <module>ruoyi-common-doc</module>
+ <module>ruoyi-common-excel</module>
+ <module>ruoyi-common-idempotent</module>
+ <module>ruoyi-common-job</module>
+ <module>ruoyi-common-log</module>
+ <module>ruoyi-common-mail</module>
+ <module>ruoyi-common-mybatis</module>
+ <module>ruoyi-common-oss</module>
+ <module>ruoyi-common-ratelimiter</module>
+ <module>ruoyi-common-redis</module>
+ <module>ruoyi-common-satoken</module>
+ <module>ruoyi-common-security</module>
+ <module>ruoyi-common-sms</module>
+ <module>ruoyi-common-web</module>
+ <module>ruoyi-common-translation</module>
+ <module>ruoyi-common-sensitive</module>
+ <module>ruoyi-common-json</module>
+ <module>ruoyi-common-encrypt</module>
+ <module>ruoyi-common-tenant</module>
+ <module>ruoyi-common-websocket</module>
+ <module>ruoyi-common-sse</module>
+ </modules>
+
+ <artifactId>ruoyi-common</artifactId>
+ <packaging>pom</packaging>
+
+ <description>
+ common 閫氱敤妯″潡
+ </description>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-bom/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-bom/pom.xml
new file mode 100755
index 0000000..dd04368
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-bom/pom.xml
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-bom</artifactId>
+ <version>${revision}</version>
+ <packaging>pom</packaging>
+
+ <description>
+ ruoyi-common-bom common渚濊禆椤�
+ </description>
+
+ <properties>
+ <revision>5.5.3</revision>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <!-- 鏍稿績妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 鎺ュ彛妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- excel -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-excel</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 骞傜瓑 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-idempotent</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 璋冨害妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-job</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 鏃ュ織璁板綍 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-log</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 閭欢鏈嶅姟 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mail</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 鏁版嵁搴撴湇鍔� -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- OSS -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-oss</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 闄愭祦 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-ratelimiter</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 缂撳瓨鏈嶅姟 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- satoken -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-satoken</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 瀹夊叏妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-security</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 鐭俊妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sms</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-social</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- web鏈嶅姟 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-web</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 缈昏瘧妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-translation</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 鑴辨晱妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sensitive</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 搴忓垪鍖栨ā鍧� -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 鏁版嵁搴撳姞瑙e瘑妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-encrypt</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 绉熸埛妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-tenant</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- WebSocket妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-websocket</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- SSE妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sse</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ </dependencies>
+ </dependencyManagement>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/.flattened-pom.xml
new file mode 100644
index 0000000..ee4b17b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/.flattened-pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-core 鏍稿績妯″潡</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-validation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-aop</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-extra</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-configuration-processor</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-properties-migrator</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.github.linpeilie</groupId>
+ <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.lionsoul</groupId>
+ <artifactId>ip2region</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/pom.xml
new file mode 100755
index 0000000..ad37e90
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/pom.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-core</artifactId>
+
+ <description>
+ ruoyi-common-core 鏍稿績妯″潡
+ </description>
+
+ <dependencies>
+ <!-- Spring妗嗘灦鍩烘湰鐨勬牳蹇冨伐鍏� -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </dependency>
+
+ <!-- SpringWeb妯″潡 -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ </dependency>
+
+ <!-- 鑷畾涔夐獙璇佹敞瑙� -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-validation</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-aop</artifactId>
+ </dependency>
+
+ <!--甯哥敤宸ュ叿绫� -->
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+
+ <!-- servlet鍖� -->
+ <dependency>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-http</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-extra</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </dependency>
+
+ <!-- 鑷姩鐢熸垚YML閰嶇疆鍏宠仈JSON鏂囦欢 -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-configuration-processor</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-properties-migrator</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>io.github.linpeilie</groupId>
+ <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
+ </dependency>
+
+ <!-- 绂荤嚎IP鍦板潃瀹氫綅搴� -->
+ <dependency>
+ <groupId>org.lionsoul</groupId>
+ <artifactId>ip2region</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java
new file mode 100755
index 0000000..d9f70e4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java
@@ -0,0 +1,17 @@
+package org.dromara.common.core.config;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+/**
+ * 绋嬪簭娉ㄨВ閰嶇疆
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+@EnableAspectJAutoProxy
+@EnableAsync(proxyTargetClass = true)
+public class ApplicationConfig {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java
new file mode 100755
index 0000000..1cc3bd6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java
@@ -0,0 +1,109 @@
+package org.dromara.common.core.config;
+
+import jakarta.annotation.PreDestroy;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.dromara.common.core.utils.SpringUtils;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.task.VirtualThreadTaskExecutor;
+
+import java.util.concurrent.*;
+
+/**
+ * 绾跨▼姹犻厤缃�
+ *
+ * @author Lion Li
+ **/
+@Slf4j
+@AutoConfiguration
+public class ThreadPoolConfig {
+
+ /**
+ * 鏍稿績绾跨▼鏁� = cpu 鏍稿績鏁� + 1
+ */
+ private final int core = Runtime.getRuntime().availableProcessors() + 1;
+
+ private ScheduledExecutorService scheduledExecutorService;
+
+ /**
+ * 鎵ц鍛ㄦ湡鎬ф垨瀹氭椂浠诲姟
+ */
+ @Bean(name = "scheduledExecutorService")
+ protected ScheduledExecutorService scheduledExecutorService() {
+ // daemon 蹇呴』涓� true
+ BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder().daemon(true);
+ if (SpringUtils.isVirtual()) {
+ builder.namingPattern("virtual-schedule-pool-%d").wrappedFactory(new VirtualThreadTaskExecutor().getVirtualThreadFactory());
+ } else {
+ builder.namingPattern("schedule-pool-%d");
+ }
+ ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(core,
+ builder.build(),
+ new ThreadPoolExecutor.CallerRunsPolicy()) {
+ @Override
+ protected void afterExecute(Runnable r, Throwable t) {
+ super.afterExecute(r, t);
+ printException(r, t);
+ }
+ };
+ this.scheduledExecutorService = scheduledThreadPoolExecutor;
+ return scheduledThreadPoolExecutor;
+ }
+
+ /**
+ * 閿�姣佷簨浠�
+ * 鍋滄绾跨▼姹�
+ * 鍏堜娇鐢╯hutdown, 鍋滄鎺ユ敹鏂颁换鍔″苟灏濊瘯瀹屾垚鎵�鏈夊凡瀛樺湪浠诲姟.
+ * 濡傛灉瓒呮椂, 鍒欒皟鐢╯hutdownNow, 鍙栨秷鍦╳orkQueue涓璓ending鐨勪换鍔�,骞朵腑鏂墍鏈夐樆濉炲嚱鏁�.
+ * 濡傛灉浠嶇劧瓒呮檪锛屽墖寮峰埗閫�鍑�.
+ * 鍙﹀鍦╯hutdown鏃剁嚎绋嬫湰韬璋冪敤涓柇鍋氫簡澶勭悊.
+ */
+ @PreDestroy
+ public void destroy() {
+ try {
+ log.info("====鍏抽棴鍚庡彴浠诲姟浠诲姟绾跨▼姹�====");
+ ScheduledExecutorService pool = scheduledExecutorService;
+ if (pool != null && !pool.isShutdown()) {
+ pool.shutdown();
+ try {
+ if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
+ pool.shutdownNow();
+ if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
+ log.info("Pool did not terminate");
+ }
+ }
+ } catch (InterruptedException ie) {
+ pool.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 鎵撳嵃绾跨▼寮傚父淇℃伅
+ */
+ public static void printException(Runnable r, Throwable t) {
+ if (t == null && r instanceof Future<?>) {
+ try {
+ Future<?> future = (Future<?>) r;
+ if (future.isDone()) {
+ future.get();
+ }
+ } catch (CancellationException ce) {
+ t = ce;
+ } catch (ExecutionException ee) {
+ t = ee.getCause();
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ if (t != null) {
+ log.error(t.getMessage(), t);
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java
new file mode 100755
index 0000000..ddcd836
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java
@@ -0,0 +1,41 @@
+package org.dromara.common.core.config;
+
+import jakarta.validation.Validator;
+import org.hibernate.validator.HibernateValidator;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+
+import java.util.Properties;
+
+/**
+ * 鏍¢獙妗嗘灦閰嶇疆绫�
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration(before = ValidationAutoConfiguration.class)
+public class ValidatorConfig {
+
+ /**
+ * 閰嶇疆鏍¢獙妗嗘灦 蹇�熷け璐ユā寮�
+ */
+ @Bean
+ public Validator validator(MessageSource messageSource) {
+ try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
+ // 鍥介檯鍖�
+ factoryBean.setValidationMessageSource(messageSource);
+ // 璁剧疆浣跨敤 HibernateValidator 鏍¢獙鍣�
+ factoryBean.setProviderClass(HibernateValidator.class);
+ Properties properties = new Properties();
+ // 璁剧疆蹇�熷け璐ユā寮忥紙fail-fast锛夛紝鍗虫牎楠岃繃绋嬩腑涓�鏃﹂亣鍒板け璐ワ紝绔嬪嵆鍋滄骞惰繑鍥為敊璇�
+ properties.setProperty("hibernate.validator.fail_fast", "true");
+ factoryBean.setValidationProperties(properties);
+ // 鍔犺浇閰嶇疆
+ factoryBean.afterPropertiesSet();
+ return factoryBean.getValidator();
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java
new file mode 100755
index 0000000..ceb8370
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java
@@ -0,0 +1,30 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 缂撳瓨鐨刱ey 甯搁噺
+ *
+ * @author Lion Li
+ */
+public interface CacheConstants {
+
+ /**
+ * 鍦ㄧ嚎鐢ㄦ埛 redis key
+ */
+ String ONLINE_TOKEN_KEY = "online_tokens:";
+
+ /**
+ * 鍙傛暟绠$悊 cache key
+ */
+ String SYS_CONFIG_KEY = "sys_config:";
+
+ /**
+ * 瀛楀吀绠$悊 cache key
+ */
+ String SYS_DICT_KEY = "sys_dict:";
+
+ /**
+ * 鐧诲綍璐︽埛瀵嗙爜閿欒娆℃暟 redis key
+ */
+ String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
new file mode 100755
index 0000000..c38f39b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
@@ -0,0 +1,89 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 缂撳瓨缁勫悕绉板父閲�
+ * <p>
+ * key 鏍煎紡涓� cacheNames#ttl#maxIdleTime#maxSize#local
+ * <p>
+ * ttl 杩囨湡鏃堕棿 濡傛灉璁剧疆涓�0鍒欎笉杩囨湡 榛樿涓�0
+ * maxIdleTime 鏈�澶х┖闂叉椂闂� 鏍规嵁LRU绠楁硶娓呯悊绌洪棽鏁版嵁 濡傛灉璁剧疆涓�0鍒欎笉妫�娴� 榛樿涓�0
+ * maxSize 缁勬渶澶ч暱搴� 鏍规嵁LRU绠楁硶娓呯悊婧㈠嚭鏁版嵁 濡傛灉璁剧疆涓�0鍒欐棤闄愰暱 榛樿涓�0
+ * local 榛樿寮�鍚湰鍦扮紦瀛樹负1 鍏抽棴鏈湴缂撳瓨涓�0
+ * <p>
+ * 渚嬪瓙: test#60s銆乼est#0#60s銆乼est#0#1m#1000銆乼est#1h#0#500銆乼est#1h#0#500#0
+ *
+ * @author Lion Li
+ */
+public interface CacheNames {
+
+ /**
+ * 婕旂ず妗堜緥
+ */
+ String DEMO_CACHE = "demo:cache#60s#10m#20";
+
+ /**
+ * 绯荤粺閰嶇疆
+ */
+ String SYS_CONFIG = "sys_config";
+
+ /**
+ * 鏁版嵁瀛楀吀
+ */
+ String SYS_DICT = "sys_dict";
+
+ /**
+ * 鏁版嵁瀛楀吀绫诲瀷
+ */
+ String SYS_DICT_TYPE = "sys_dict_type";
+
+ /**
+ * 绉熸埛
+ */
+ String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
+
+ /**
+ * 瀹㈡埛绔�
+ */
+ String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d";
+
+ /**
+ * 鐢ㄦ埛璐︽埛
+ */
+ String SYS_USER_NAME = "sys_user_name#30d";
+
+ /**
+ * 鐢ㄦ埛鍚嶇О
+ */
+ String SYS_NICKNAME = "sys_nickname#30d";
+
+ /**
+ * 閮ㄩ棬
+ */
+ String SYS_DEPT = "sys_dept#30d";
+
+ /**
+ * OSS鍐呭
+ */
+ String SYS_OSS = "sys_oss#30d";
+
+ /**
+ * 瑙掕壊鑷畾涔夋潈闄�
+ */
+ String SYS_ROLE_CUSTOM = "sys_role_custom#30d";
+
+ /**
+ * 閮ㄩ棬鍙婁互涓嬫潈闄�
+ */
+ String SYS_DEPT_AND_CHILD = "sys_dept_and_child#30d";
+
+ /**
+ * OSS閰嶇疆
+ */
+ String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
+
+ /**
+ * 鍦ㄧ嚎鐢ㄦ埛
+ */
+ String ONLINE_TOKEN = "online_tokens";
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java
new file mode 100755
index 0000000..0c6671a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java
@@ -0,0 +1,81 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 閫氱敤甯搁噺淇℃伅
+ *
+ * @author ruoyi
+ */
+public interface Constants {
+
+ /**
+ * UTF-8 瀛楃闆�
+ */
+ String UTF8 = "UTF-8";
+
+ /**
+ * GBK 瀛楃闆�
+ */
+ String GBK = "GBK";
+
+ /**
+ * www涓诲煙
+ */
+ String WWW = "www.";
+
+ /**
+ * http璇锋眰
+ */
+ String HTTP = "http://";
+
+ /**
+ * https璇锋眰
+ */
+ String HTTPS = "https://";
+
+ /**
+ * 閫氱敤鎴愬姛鏍囪瘑
+ */
+ String SUCCESS = "0";
+
+ /**
+ * 閫氱敤澶辫触鏍囪瘑
+ */
+ String FAIL = "1";
+
+ /**
+ * 鐧诲綍鎴愬姛
+ */
+ String LOGIN_SUCCESS = "Success";
+
+ /**
+ * 娉ㄩ攢
+ */
+ String LOGOUT = "Logout";
+
+ /**
+ * 娉ㄥ唽
+ */
+ String REGISTER = "Register";
+
+ /**
+ * 鐧诲綍澶辫触
+ */
+ String LOGIN_FAIL = "Error";
+
+ /**
+ * 楠岃瘉鐮佹湁鏁堟湡锛堝垎閽燂級
+ */
+ Integer CAPTCHA_EXPIRATION = 2;
+
+ /**
+ * 椤剁骇鐖剁骇id
+ */
+ Long TOP_PARENT_ID = 0L;
+
+ /**
+ * 鍔犲瘑澶�
+ */
+ String ENCRYPT_HEADER = "ENC_";
+
+}
+
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java
new file mode 100755
index 0000000..5352b11
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java
@@ -0,0 +1,34 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 鍏ㄥ眬鐨刱ey甯搁噺 (涓氬姟鏃犲叧鐨刱ey)
+ *
+ * @author Lion Li
+ */
+public interface GlobalConstants {
+
+ /**
+ * 鍏ㄥ眬 redis key (涓氬姟鏃犲叧鐨刱ey)
+ */
+ String GLOBAL_REDIS_KEY = "global:";
+
+ /**
+ * 楠岃瘉鐮� redis key
+ */
+ String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
+
+ /**
+ * 闃查噸鎻愪氦 redis key
+ */
+ String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:";
+
+ /**
+ * 闄愭祦 redis key
+ */
+ String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
+
+ /**
+ * 涓夋柟璁よ瘉 redis key
+ */
+ String SOCIAL_AUTH_CODE_KEY = GLOBAL_REDIS_KEY + "social_auth_codes:";
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/HttpStatus.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/HttpStatus.java
new file mode 100755
index 0000000..85566e8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/HttpStatus.java
@@ -0,0 +1,93 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 杩斿洖鐘舵�佺爜
+ *
+ * @author Lion Li
+ */
+public interface HttpStatus {
+ /**
+ * 鎿嶄綔鎴愬姛
+ */
+ int SUCCESS = 200;
+
+ /**
+ * 瀵硅薄鍒涘缓鎴愬姛
+ */
+ int CREATED = 201;
+
+ /**
+ * 璇锋眰宸茬粡琚帴鍙�
+ */
+ int ACCEPTED = 202;
+
+ /**
+ * 鎿嶄綔宸茬粡鎵ц鎴愬姛锛屼絾鏄病鏈夎繑鍥炴暟鎹�
+ */
+ int NO_CONTENT = 204;
+
+ /**
+ * 璧勬簮宸茶绉婚櫎
+ */
+ int MOVED_PERM = 301;
+
+ /**
+ * 閲嶅畾鍚�
+ */
+ int SEE_OTHER = 303;
+
+ /**
+ * 璧勬簮娌℃湁琚慨鏀�
+ */
+ int NOT_MODIFIED = 304;
+
+ /**
+ * 鍙傛暟鍒楄〃閿欒锛堢己灏戯紝鏍煎紡涓嶅尮閰嶏級
+ */
+ int BAD_REQUEST = 400;
+
+ /**
+ * 鏈巿鏉�
+ */
+ int UNAUTHORIZED = 401;
+
+ /**
+ * 璁块棶鍙楅檺锛屾巿鏉冭繃鏈�
+ */
+ int FORBIDDEN = 403;
+
+ /**
+ * 璧勬簮锛屾湇鍔℃湭鎵惧埌
+ */
+ int NOT_FOUND = 404;
+
+ /**
+ * 涓嶅厑璁哥殑http鏂规硶
+ */
+ int BAD_METHOD = 405;
+
+ /**
+ * 璧勬簮鍐茬獊锛屾垨鑰呰祫婧愯閿�
+ */
+ int CONFLICT = 409;
+
+ /**
+ * 涓嶆敮鎸佺殑鏁版嵁锛屽獟浣撶被鍨�
+ */
+ int UNSUPPORTED_TYPE = 415;
+
+ /**
+ * 绯荤粺鍐呴儴閿欒
+ */
+ int ERROR = 500;
+
+ /**
+ * 鎺ュ彛鏈疄鐜�
+ */
+ int NOT_IMPLEMENTED = 501;
+
+ /**
+ * 绯荤粺璀﹀憡娑堟伅
+ */
+ int WARN = 601;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/RegexConstants.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/RegexConstants.java
new file mode 100755
index 0000000..f1e04f7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/RegexConstants.java
@@ -0,0 +1,59 @@
+package org.dromara.common.core.constant;
+
+import cn.hutool.core.lang.RegexPool;
+
+/**
+ * 甯哥敤姝e垯琛ㄨ揪寮忓瓧绗︿覆
+ * <p>
+ * 甯哥敤姝e垯琛ㄨ揪寮忛泦鍚堬紝鏇村姝e垯瑙�: https://any86.github.io/any-rule/
+ *
+ * @author Feng
+ */
+public interface RegexConstants extends RegexPool {
+
+ /**
+ * 瀛楀吀绫诲瀷蹇呴』浠ュ瓧姣嶅紑澶达紝涓斿彧鑳戒负锛堝皬鍐欏瓧姣嶏紝鏁板瓧锛屼笅婊戠嚎锛�
+ */
+ String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
+
+ /**
+ * 鏉冮檺鏍囪瘑蹇呴』绗﹀悎浠ヤ笅鏍煎紡锛�
+ * 1. 鏍囧噯鏍煎紡锛歺xx:yyy:zzz
+ * - 绗竴閮ㄥ垎锛坸xx锛夛細鍙兘鍖呭惈瀛楁瘝銆佹暟瀛楀拰涓嬪垝绾匡紙_锛夛紝涓嶈兘浣跨敤 `*`
+ * - 绗簩閮ㄥ垎锛坹yy锛夛細鍙互鍖呭惈瀛楁瘝銆佹暟瀛椼�佷笅鍒掔嚎锛坃锛夊拰 `*`
+ * - 绗笁閮ㄥ垎锛坺zz锛夛細鍙互鍖呭惈瀛楁瘝銆佹暟瀛椼�佷笅鍒掔嚎锛坃锛夊拰 `*`
+ * 2. 鍏佽绌哄瓧绗︿覆锛�""锛夛紝琛ㄧず娌℃湁鏉冮檺鏍囪瘑
+ */
+ String PERMISSION_STRING = "^$|^[a-zA-Z0-9_]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$";
+
+ /**
+ * 韬唤璇佸彿鐮侊紙鍚�6浣嶏級
+ */
+ String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
+
+ /**
+ * QQ鍙风爜
+ */
+ String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$";
+
+ /**
+ * 閭斂缂栫爜
+ */
+ String POSTAL_CODE = "^[1-9]\\d{5}$";
+
+ /**
+ * 娉ㄥ唽璐﹀彿
+ */
+ String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$";
+
+ /**
+ * 瀵嗙爜锛氬寘鍚嚦灏�8涓瓧绗︼紝鍖呮嫭澶у啓瀛楁瘝銆佸皬鍐欏瓧姣嶃�佹暟瀛楀拰鐗规畩瀛楃
+ */
+ String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
+
+ /**
+ * 閫氱敤鐘舵�侊紙0琛ㄧず姝e父锛�1琛ㄧず鍋滅敤锛�
+ */
+ String STATUS = "^[01]$";
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/SystemConstants.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/SystemConstants.java
new file mode 100755
index 0000000..e38d9fd
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/SystemConstants.java
@@ -0,0 +1,91 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 绯荤粺甯搁噺淇℃伅
+ *
+ * @author Lion Li
+ */
+public interface SystemConstants {
+
+ /**
+ * 姝e父鐘舵��
+ */
+ String NORMAL = "0";
+
+ /**
+ * 寮傚父鐘舵��
+ */
+ String DISABLE = "1";
+
+ /**
+ * 鏄惁涓虹郴缁熼粯璁わ紙鏄級
+ */
+ String YES = "Y";
+
+ /**
+ * 鏄惁涓虹郴缁熼粯璁わ紙鍚︼級
+ */
+ String NO = "N";
+
+ /**
+ * 鏄惁鑿滃崟澶栭摼锛堟槸锛�
+ */
+ String YES_FRAME = "0";
+
+ /**
+ * 鏄惁鑿滃崟澶栭摼锛堝惁锛�
+ */
+ String NO_FRAME = "1";
+
+ /**
+ * 鑿滃崟绫诲瀷锛堢洰褰曪級
+ */
+ String TYPE_DIR = "M";
+
+ /**
+ * 鑿滃崟绫诲瀷锛堣彍鍗曪級
+ */
+ String TYPE_MENU = "C";
+
+ /**
+ * 鑿滃崟绫诲瀷锛堟寜閽級
+ */
+ String TYPE_BUTTON = "F";
+
+ /**
+ * Layout缁勪欢鏍囪瘑
+ */
+ String LAYOUT = "Layout";
+
+ /**
+ * ParentView缁勪欢鏍囪瘑
+ */
+ String PARENT_VIEW = "ParentView";
+
+ /**
+ * InnerLink缁勪欢鏍囪瘑
+ */
+ String INNER_LINK = "InnerLink";
+
+ /**
+ * 瓒呯骇绠$悊鍛業D
+ */
+ Long SUPER_ADMIN_ID = 1L;
+
+ /**
+ * 鏍归儴闂ㄧ绾у垪琛�
+ */
+ String ROOT_DEPT_ANCESTORS = "0";
+
+ /**
+ * 榛樿閮ㄩ棬 ID
+ */
+ Long DEFAULT_DEPT_ID = 100L;
+
+ /**
+ * 鎺掗櫎鏁忔劅灞炴�у瓧娈�
+ */
+ String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/TenantConstants.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/TenantConstants.java
new file mode 100755
index 0000000..33ce0cf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/TenantConstants.java
@@ -0,0 +1,35 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 绉熸埛甯搁噺淇℃伅
+ *
+ * @author Lion Li
+ */
+public interface TenantConstants {
+
+ /**
+ * 瓒呯骇绠$悊鍛業D
+ */
+ Long SUPER_ADMIN_ID = 1L;
+
+ /**
+ * 瓒呯骇绠$悊鍛樿鑹� roleKey
+ */
+ String SUPER_ADMIN_ROLE_KEY = "superadmin";
+
+ /**
+ * 绉熸埛绠$悊鍛樿鑹� roleKey
+ */
+ String TENANT_ADMIN_ROLE_KEY = "admin";
+
+ /**
+ * 绉熸埛绠$悊鍛樿鑹插悕绉�
+ */
+ String TENANT_ADMIN_ROLE_NAME = "绠$悊鍛�";
+
+ /**
+ * 榛樿绉熸埛ID
+ */
+ String DEFAULT_TENANT_ID = "000000";
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/R.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/R.java
new file mode 100755
index 0000000..be85805
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/R.java
@@ -0,0 +1,110 @@
+package org.dromara.common.core.domain;
+
+import org.dromara.common.core.constant.HttpStatus;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 鍝嶅簲淇℃伅涓讳綋
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class R<T> implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鎴愬姛
+ */
+ public static final int SUCCESS = 200;
+
+ /**
+ * 澶辫触
+ */
+ public static final int FAIL = 500;
+
+ private int code;
+
+ private String msg;
+
+ private T data;
+
+ public static <T> R<T> ok() {
+ return restResult(null, SUCCESS, "鎿嶄綔鎴愬姛");
+ }
+
+ public static <T> R<T> ok(T data) {
+ return restResult(data, SUCCESS, "鎿嶄綔鎴愬姛");
+ }
+
+ public static <T> R<T> ok(String msg) {
+ return restResult(null, SUCCESS, msg);
+ }
+
+ public static <T> R<T> ok(String msg, T data) {
+ return restResult(data, SUCCESS, msg);
+ }
+
+ public static <T> R<T> fail() {
+ return restResult(null, FAIL, "鎿嶄綔澶辫触");
+ }
+
+ public static <T> R<T> fail(String msg) {
+ return restResult(null, FAIL, msg);
+ }
+
+ public static <T> R<T> fail(T data) {
+ return restResult(data, FAIL, "鎿嶄綔澶辫触");
+ }
+
+ public static <T> R<T> fail(String msg, T data) {
+ return restResult(data, FAIL, msg);
+ }
+
+ public static <T> R<T> fail(int code, String msg) {
+ return restResult(null, code, msg);
+ }
+
+ /**
+ * 杩斿洖璀﹀憡娑堟伅
+ *
+ * @param msg 杩斿洖鍐呭
+ * @return 璀﹀憡娑堟伅
+ */
+ public static <T> R<T> warn(String msg) {
+ return restResult(null, HttpStatus.WARN, msg);
+ }
+
+ /**
+ * 杩斿洖璀﹀憡娑堟伅
+ *
+ * @param msg 杩斿洖鍐呭
+ * @param data 鏁版嵁瀵硅薄
+ * @return 璀﹀憡娑堟伅
+ */
+ public static <T> R<T> warn(String msg, T data) {
+ return restResult(data, HttpStatus.WARN, msg);
+ }
+
+ private static <T> R<T> restResult(T data, int code, String msg) {
+ R<T> r = new R<>();
+ r.setCode(code);
+ r.setData(data);
+ r.setMsg(msg);
+ return r;
+ }
+
+ public static <T> Boolean isError(R<T> ret) {
+ return !isSuccess(ret);
+ }
+
+ public static <T> Boolean isSuccess(R<T> ret) {
+ return R.SUCCESS == ret.getCode();
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/CompleteTaskDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/CompleteTaskDTO.java
new file mode 100755
index 0000000..21a561e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/CompleteTaskDTO.java
@@ -0,0 +1,77 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 鍔炵悊浠诲姟璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class CompleteTaskDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 浠诲姟id
+ */
+ private Long taskId;
+
+ /**
+ * 闄勪欢id
+ */
+ private String fileId;
+
+ /**
+ * 鎶勯�佷汉鍛�
+ */
+ private List<FlowCopyDTO> flowCopyList;
+
+ /**
+ * 娑堟伅绫诲瀷
+ */
+ private List<String> messageType;
+
+ /**
+ * 鍔炵悊鎰忚
+ */
+ private String message;
+
+ /**
+ * 娑堟伅閫氱煡
+ */
+ private String notice;
+
+ /**
+ * 鍔炵悊浜�(鍙笉濉� 鐢ㄤ簬瑕嗙洊褰撳墠鑺傜偣鍔炵悊浜�)
+ */
+ private String handler;
+
+ /**
+ * 娴佺▼鍙橀噺
+ */
+ private Map<String, Object> variables;
+
+ /**
+ * 鎵╁睍鍙橀噺(姝ゅ涓洪�楀彿鍒嗛殧鐨刼ssId)
+ */
+ private String ext;
+
+ public Map<String, Object> getVariables() {
+ if (variables == null) {
+ variables = new HashMap<>(16);
+ return variables;
+ }
+ variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+ return variables;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DeptDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DeptDTO.java
new file mode 100755
index 0000000..7b748b0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DeptDTO.java
@@ -0,0 +1,36 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 閮ㄩ棬
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class DeptDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ private Long deptId;
+
+ /**
+ * 鐖堕儴闂↖D
+ */
+ private Long parentId;
+
+ /**
+ * 閮ㄩ棬鍚嶇О
+ */
+ private String deptName;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictDataDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictDataDTO.java
new file mode 100755
index 0000000..dff1a75
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictDataDTO.java
@@ -0,0 +1,41 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 瀛楀吀鏁版嵁DTO
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class DictDataDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 瀛楀吀鏍囩
+ */
+ private String dictLabel;
+
+ /**
+ * 瀛楀吀閿��
+ */
+ private String dictValue;
+
+ /**
+ * 鏄惁榛樿锛圷鏄� N鍚︼級
+ */
+ private String isDefault;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictTypeDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictTypeDTO.java
new file mode 100755
index 0000000..43ab142
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictTypeDTO.java
@@ -0,0 +1,41 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 瀛楀吀绫诲瀷DTO
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class DictTypeDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 瀛楀吀涓婚敭
+ */
+ private Long dictId;
+
+ /**
+ * 瀛楀吀鍚嶇О
+ */
+ private String dictName;
+
+ /**
+ * 瀛楀吀绫诲瀷
+ */
+ private String dictType;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowCopyDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowCopyDTO.java
new file mode 100755
index 0000000..2f20b21
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowCopyDTO.java
@@ -0,0 +1,30 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 鎶勯��
+ *
+ * @author may
+ */
+@Data
+public class FlowCopyDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鐢ㄦ埛id
+ */
+ private Long userId;
+
+ /**
+ * 鐢ㄦ埛鍚嶇О
+ */
+ private String userName;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowInstanceBizExtDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowInstanceBizExtDTO.java
new file mode 100755
index 0000000..d22937b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowInstanceBizExtDTO.java
@@ -0,0 +1,45 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 娴佺▼瀹炰緥涓氬姟鎵╁睍瀵硅薄
+ *
+ * @author may
+ * @date 2025-08-05
+ */
+@Data
+public class FlowInstanceBizExtDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭
+ */
+ private Long id;
+
+ /**
+ * 娴佺▼瀹炰緥ID
+ */
+ private Long instanceId;
+
+ /**
+ * 涓氬姟ID
+ */
+ private String businessId;
+
+ /**
+ * 涓氬姟缂栫爜
+ */
+ private String businessCode;
+
+ /**
+ * 涓氬姟鏍囬
+ */
+ private String businessTitle;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/OssDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/OssDTO.java
new file mode 100755
index 0000000..463821c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/OssDTO.java
@@ -0,0 +1,46 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * OSS瀵硅薄
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class OssDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 瀵硅薄瀛樺偍涓婚敭
+ */
+ private Long ossId;
+
+ /**
+ * 鏂囦欢鍚�
+ */
+ private String fileName;
+
+ /**
+ * 鍘熷悕
+ */
+ private String originalName;
+
+ /**
+ * 鏂囦欢鍚庣紑鍚�
+ */
+ private String fileSuffix;
+
+ /**
+ * URL鍦板潃
+ */
+ private String url;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/PostDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/PostDTO.java
new file mode 100755
index 0000000..7536ee3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/PostDTO.java
@@ -0,0 +1,46 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 宀椾綅
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class PostDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 宀椾綅ID
+ */
+ private Long postId;
+
+ /**
+ * 閮ㄩ棬id
+ */
+ private Long deptId;
+
+ /**
+ * 宀椾綅缂栫爜
+ */
+ private String postCode;
+
+ /**
+ * 宀椾綅鍚嶇О
+ */
+ private String postName;
+
+ /**
+ * 宀椾綅绫诲埆缂栫爜
+ */
+ private String postCategory;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/RoleDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/RoleDTO.java
new file mode 100755
index 0000000..d14ffbb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/RoleDTO.java
@@ -0,0 +1,42 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 瑙掕壊
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+public class RoleDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 瑙掕壊ID
+ */
+ private Long roleId;
+
+ /**
+ * 瑙掕壊鍚嶇О
+ */
+ private String roleName;
+
+ /**
+ * 瑙掕壊鏉冮檺
+ */
+ private String roleKey;
+
+ /**
+ * 鏁版嵁鑼冨洿锛�1锛氬叏閮ㄦ暟鎹潈闄� 2锛氳嚜瀹氭暟鎹潈闄� 3锛氭湰閮ㄩ棬鏁版嵁鏉冮檺 4锛氭湰閮ㄩ棬鍙婁互涓嬫暟鎹潈闄� 5锛氫粎鏈汉鏁版嵁鏉冮檺 6锛氶儴闂ㄥ強浠ヤ笅鎴栨湰浜烘暟鎹潈闄愶級
+ */
+ private String dataScope;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java
new file mode 100755
index 0000000..fa35657
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java
@@ -0,0 +1,63 @@
+package org.dromara.common.core.domain.dto;
+
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 鍚姩娴佺▼瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class StartProcessDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓氬姟鍞竴鍊糹d
+ */
+ private String businessId;
+
+ /**
+ * 娴佺▼瀹氫箟缂栫爜
+ */
+ private String flowCode;
+
+ /**
+ * 鍔炵悊浜�(鍙笉濉� 鐢ㄤ簬瑕嗙洊褰撳墠鑺傜偣鍔炵悊浜�)
+ */
+ private String handler;
+
+ /**
+ * 娴佺▼鍙橀噺锛屽墠绔細鎻愪氦涓�涓厓绱爗'entity': {涓氬姟璇︽儏鏁版嵁瀵硅薄}}
+ */
+ private Map<String, Object> variables;
+
+ /**
+ * 娴佺▼涓氬姟鎵╁睍淇℃伅
+ */
+ private FlowInstanceBizExtDTO bizExt;
+
+ public Map<String, Object> getVariables() {
+ if (variables == null) {
+ return new HashMap<>(16);
+ }
+ variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+ return variables;
+ }
+
+ public FlowInstanceBizExtDTO getBizExt() {
+ if (ObjectUtil.isNull(bizExt)) {
+ bizExt = new FlowInstanceBizExtDTO();
+ }
+ return bizExt;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessReturnDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessReturnDTO.java
new file mode 100755
index 0000000..9bcbd12
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessReturnDTO.java
@@ -0,0 +1,30 @@
+package org.dromara.common.core.domain.dto;
+
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 鍚姩娴佺▼杩斿洖瀵硅薄
+ *
+ * @author Lion Li
+ */
+@Data
+public class StartProcessReturnDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 娴佺▼瀹炰緥id
+ */
+ private Long processInstanceId;
+
+ /**
+ * 浠诲姟id
+ */
+ private Long taskId;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/TaskAssigneeDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/TaskAssigneeDTO.java
new file mode 100755
index 0000000..78fb40e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/TaskAssigneeDTO.java
@@ -0,0 +1,101 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 浠诲姟鍙楄浜�
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class TaskAssigneeDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鎬诲ぇ灏�
+ */
+ private Long total = 0L;
+
+ /**
+ *
+ */
+ private List<TaskHandler> list;
+
+ public TaskAssigneeDTO(Long total, List<TaskHandler> list) {
+ this.total = total;
+ this.list = list;
+ }
+
+ /**
+ * 灏嗘簮鍒楄〃杞崲涓� TaskHandler 鍒楄〃
+ *
+ * @param <T> 閫氱敤绫诲瀷
+ * @param sourceList 寰呰浆鎹㈢殑婧愬垪琛�
+ * @param storageId 鎻愬彇 storageId 鐨勫嚱鏁�
+ * @param handlerCode 鎻愬彇 handlerCode 鐨勫嚱鏁�
+ * @param handlerName 鎻愬彇 handlerName 鐨勫嚱鏁�
+ * @param groupName 鎻愬彇 groupName 鐨勫嚱鏁�
+ * @param createTimeMapper 鎻愬彇 createTime 鐨勫嚱鏁�
+ * @return 杞崲鍚庣殑 TaskHandler 鍒楄〃
+ */
+ public static <T> List<TaskHandler> convertToHandlerList(
+ List<T> sourceList,
+ Function<T, String> storageId,
+ Function<T, String> handlerCode,
+ Function<T, String> handlerName,
+ Function<T, String> groupName,
+ Function<T, Date> createTimeMapper) {
+ return sourceList.stream()
+ .map(item -> new TaskHandler(
+ storageId.apply(item),
+ handlerCode.apply(item),
+ handlerName.apply(item),
+ groupName.apply(item),
+ createTimeMapper.apply(item)
+ )).collect(Collectors.toList());
+ }
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class TaskHandler {
+
+ /**
+ * 涓婚敭
+ */
+ private String storageId;
+
+ /**
+ * 鏉冮檺缂栫爜
+ */
+ private String handlerCode;
+
+ /**
+ * 鏉冮檺鍚嶇О
+ */
+ private String handlerName;
+
+ /**
+ * 鏉冮檺鍒嗙粍
+ */
+ private String groupName;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ private Date createTime;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserDTO.java
new file mode 100755
index 0000000..393a0f0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserDTO.java
@@ -0,0 +1,73 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 鐢ㄦ埛
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@NoArgsConstructor
+public class UserDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ private Long userId;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ private Long deptId;
+
+ /**
+ * 鐢ㄦ埛璐﹀彿
+ */
+ private String userName;
+
+ /**
+ * 鐢ㄦ埛鏄电О
+ */
+ private String nickName;
+
+ /**
+ * 鐢ㄦ埛绫诲瀷锛坰ys_user绯荤粺鐢ㄦ埛锛�
+ */
+ private String userType;
+
+ /**
+ * 鐢ㄦ埛閭
+ */
+ private String email;
+
+ /**
+ * 鎵嬫満鍙风爜
+ */
+ private String phonenumber;
+
+ /**
+ * 鐢ㄦ埛鎬у埆锛�0鐢� 1濂� 2鏈煡锛�
+ */
+ private String sex;
+
+ /**
+ * 璐﹀彿鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ private Date createTime;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserOnlineDTO.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserOnlineDTO.java
new file mode 100755
index 0000000..43d8c3c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserOnlineDTO.java
@@ -0,0 +1,72 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 褰撳墠鍦ㄧ嚎浼氳瘽
+ *
+ * @author ruoyi
+ */
+
+@Data
+@NoArgsConstructor
+public class UserOnlineDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 浼氳瘽缂栧彿
+ */
+ private String tokenId;
+
+ /**
+ * 閮ㄩ棬鍚嶇О
+ */
+ private String deptName;
+
+ /**
+ * 鐢ㄦ埛鍚嶇О
+ */
+ private String userName;
+
+ /**
+ * 瀹㈡埛绔�
+ */
+ private String clientKey;
+
+ /**
+ * 璁惧绫诲瀷
+ */
+ private String deviceType;
+
+ /**
+ * 鐧诲綍IP鍦板潃
+ */
+ private String ipaddr;
+
+ /**
+ * 鐧诲綍鍦板潃
+ */
+ private String loginLocation;
+
+ /**
+ * 娴忚鍣ㄧ被鍨�
+ */
+ private String browser;
+
+ /**
+ * 鎿嶄綔绯荤粺
+ */
+ private String os;
+
+ /**
+ * 鐧诲綍鏃堕棿
+ */
+ private Long loginTime;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessDeleteEvent.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessDeleteEvent.java
new file mode 100755
index 0000000..d570c31
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessDeleteEvent.java
@@ -0,0 +1,34 @@
+package org.dromara.common.core.domain.event;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 鍒犻櫎娴佺▼鐩戝惉
+ *
+ * @author AprilWind
+ */
+@Data
+public class ProcessDeleteEvent implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 娴佺▼瀹氫箟缂栫爜
+ */
+ private String flowCode;
+
+ /**
+ * 涓氬姟id
+ */
+ private String businessId;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java
new file mode 100755
index 0000000..d830dbe
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java
@@ -0,0 +1,70 @@
+package org.dromara.common.core.domain.event;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * 鎬讳綋娴佺▼鐩戝惉
+ *
+ * @author may
+ */
+@Data
+public class ProcessEvent implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 娴佺▼瀹氫箟缂栫爜
+ */
+ private String flowCode;
+
+ /**
+ * 瀹炰緥id
+ */
+ private Long instanceId;
+
+ /**
+ * 涓氬姟id
+ */
+ private String businessId;
+
+ /**
+ * 鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�
+ */
+ private Integer nodeType;
+
+ /**
+ * 娴佺▼鑺傜偣缂栫爜
+ */
+ private String nodeCode;
+
+ /**
+ * 娴佺▼鑺傜偣鍚嶇О
+ */
+ private String nodeName;
+
+ /**
+ * 娴佺▼鐘舵��
+ */
+ private String status;
+
+ /**
+ * 鍔炵悊鍙傛暟
+ */
+ private Map<String, Object> params;
+
+ /**
+ * 褰撲负true鏃朵负鐢宠浜鸿妭鐐瑰姙鐞�
+ */
+ private Boolean submit;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java
new file mode 100755
index 0000000..0984727
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java
@@ -0,0 +1,70 @@
+package org.dromara.common.core.domain.event;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * 娴佺▼浠诲姟鐩戝惉
+ *
+ * @author may
+ */
+@Data
+public class ProcessTaskEvent implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 娴佺▼瀹氫箟缂栫爜
+ */
+ private String flowCode;
+
+ /**
+ * 鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�
+ */
+ private Integer nodeType;
+
+ /**
+ * 娴佺▼鑺傜偣缂栫爜
+ */
+ private String nodeCode;
+
+ /**
+ * 娴佺▼鑺傜偣鍚嶇О
+ */
+ private String nodeName;
+
+ /**
+ * 浠诲姟id
+ */
+ private Long taskId;
+
+ /**
+ * 瀹炰緥id
+ */
+ private Long instanceId;
+
+ /**
+ * 涓氬姟id
+ */
+ private String businessId;
+
+ /**
+ * 娴佺▼鐘舵��
+ */
+ private String status;
+
+ /**
+ * 鍔炵悊鍙傛暟
+ */
+ private Map<String, Object> params;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/EmailLoginBody.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/EmailLoginBody.java
new file mode 100755
index 0000000..ffde8c6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/EmailLoginBody.java
@@ -0,0 +1,31 @@
+package org.dromara.common.core.domain.model;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 閭欢鐧诲綍瀵硅薄
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class EmailLoginBody extends LoginBody {
+
+ /**
+ * 閭
+ */
+ @NotBlank(message = "{user.email.not.blank}")
+ @Email(message = "{user.email.not.valid}")
+ private String email;
+
+ /**
+ * 閭code
+ */
+ @NotBlank(message = "{email.code.not.blank}")
+ private String emailCode;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java
new file mode 100755
index 0000000..63bee0d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java
@@ -0,0 +1,48 @@
+package org.dromara.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 鐢ㄦ埛鐧诲綍瀵硅薄
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class LoginBody implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 瀹㈡埛绔痠d
+ */
+ @NotBlank(message = "{auth.clientid.not.blank}")
+ private String clientId;
+
+ /**
+ * 鎺堟潈绫诲瀷
+ */
+ @NotBlank(message = "{auth.grant.type.not.blank}")
+ private String grantType;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 楠岃瘉鐮�
+ */
+ private String code;
+
+ /**
+ * 鍞竴鏍囪瘑
+ */
+ private String uuid;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginUser.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginUser.java
new file mode 100755
index 0000000..338d4d7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginUser.java
@@ -0,0 +1,148 @@
+package org.dromara.common.core.domain.model;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.domain.dto.PostDTO;
+import org.dromara.common.core.domain.dto.RoleDTO;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 鐧诲綍鐢ㄦ埛韬唤鏉冮檺
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class LoginUser implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ private Long userId;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ private Long deptId;
+
+ /**
+ * 閮ㄩ棬绫诲埆缂栫爜
+ */
+ private String deptCategory;
+
+ /**
+ * 閮ㄩ棬鍚�
+ */
+ private String deptName;
+
+ /**
+ * 鐢ㄦ埛鍞竴鏍囪瘑
+ */
+ private String token;
+
+ /**
+ * 鐢ㄦ埛绫诲瀷
+ */
+ private String userType;
+
+ /**
+ * 鐧诲綍鏃堕棿
+ */
+ private Long loginTime;
+
+ /**
+ * 杩囨湡鏃堕棿
+ */
+ private Long expireTime;
+
+ /**
+ * 鐧诲綍IP鍦板潃
+ */
+ private String ipaddr;
+
+ /**
+ * 鐧诲綍鍦扮偣
+ */
+ private String loginLocation;
+
+ /**
+ * 娴忚鍣ㄧ被鍨�
+ */
+ private String browser;
+
+ /**
+ * 鎿嶄綔绯荤粺
+ */
+ private String os;
+
+ /**
+ * 鑿滃崟鏉冮檺
+ */
+ private Set<String> menuPermission;
+
+ /**
+ * 瑙掕壊鏉冮檺
+ */
+ private Set<String> rolePermission;
+
+ /**
+ * 鐢ㄦ埛鍚�
+ */
+ private String username;
+
+ /**
+ * 鐢ㄦ埛鏄电О
+ */
+ private String nickname;
+
+ /**
+ * 瑙掕壊瀵硅薄
+ */
+ private List<RoleDTO> roles;
+
+ /**
+ * 宀椾綅瀵硅薄
+ */
+ private List<PostDTO> posts;
+
+ /**
+ * 鏁版嵁鏉冮檺 褰撳墠瑙掕壊ID
+ */
+ private Long roleId;
+
+ /**
+ * 瀹㈡埛绔�
+ */
+ private String clientKey;
+
+ /**
+ * 璁惧绫诲瀷
+ */
+ private String deviceType;
+
+ /**
+ * 鑾峰彇鐧诲綍id
+ */
+ public String getLoginId() {
+ if (userType == null) {
+ throw new IllegalArgumentException("鐢ㄦ埛绫诲瀷涓嶈兘涓虹┖");
+ }
+ if (userId == null) {
+ throw new IllegalArgumentException("鐢ㄦ埛ID涓嶈兘涓虹┖");
+ }
+ return userType + ":" + userId;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/PasswordLoginBody.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/PasswordLoginBody.java
new file mode 100755
index 0000000..143c959
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/PasswordLoginBody.java
@@ -0,0 +1,32 @@
+package org.dromara.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.hibernate.validator.constraints.Length;
+
+/**
+ * 瀵嗙爜鐧诲綍瀵硅薄
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PasswordLoginBody extends LoginBody {
+
+ /**
+ * 鐢ㄦ埛鍚�
+ */
+ @NotBlank(message = "{user.username.not.blank}")
+ @Length(min = 2, max = 30, message = "{user.username.length.valid}")
+ private String username;
+
+ /**
+ * 鐢ㄦ埛瀵嗙爜
+ */
+ @NotBlank(message = "{user.password.not.blank}")
+ @Length(min = 5, max = 30, message = "{user.password.length.valid}")
+// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
+ private String password;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/RegisterBody.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/RegisterBody.java
new file mode 100755
index 0000000..3f23249
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/RegisterBody.java
@@ -0,0 +1,37 @@
+package org.dromara.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.hibernate.validator.constraints.Length;
+
+/**
+ * 鐢ㄦ埛娉ㄥ唽瀵硅薄
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class RegisterBody extends LoginBody {
+
+ /**
+ * 鐢ㄦ埛鍚�
+ */
+ @NotBlank(message = "{user.username.not.blank}")
+ @Length(min = 2, max = 30, message = "{user.username.length.valid}")
+ private String username;
+
+ /**
+ * 鐢ㄦ埛瀵嗙爜
+ */
+ @NotBlank(message = "{user.password.not.blank}")
+ @Length(min = 5, max = 30, message = "{user.password.length.valid}")
+// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
+ private String password;
+
+ /**
+ * 鐢ㄦ埛绫诲瀷
+ */
+ private String userType;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/SmsLoginBody.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/SmsLoginBody.java
new file mode 100755
index 0000000..a878348
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/SmsLoginBody.java
@@ -0,0 +1,29 @@
+package org.dromara.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 鐭俊鐧诲綍瀵硅薄
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SmsLoginBody extends LoginBody {
+
+ /**
+ * 鎵嬫満鍙�
+ */
+ @NotBlank(message = "{user.phonenumber.not.blank}")
+ private String phonenumber;
+
+ /**
+ * 鐭俊code
+ */
+ @NotBlank(message = "{sms.code.not.blank}")
+ private String smsCode;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/SocialLoginBody.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/SocialLoginBody.java
new file mode 100755
index 0000000..0d1b121
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/SocialLoginBody.java
@@ -0,0 +1,35 @@
+package org.dromara.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 涓夋柟鐧诲綍瀵硅薄
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SocialLoginBody extends LoginBody {
+
+ /**
+ * 绗笁鏂圭櫥褰曞钩鍙�
+ */
+ @NotBlank(message = "{social.source.not.blank}")
+ private String source;
+
+ /**
+ * 绗笁鏂圭櫥褰昪ode
+ */
+ @NotBlank(message = "{social.code.not.blank}")
+ private String socialCode;
+
+ /**
+ * 绗笁鏂圭櫥褰晄ocialState
+ */
+ @NotBlank(message = "{social.state.not.blank}")
+ private String socialState;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/TaskAssigneeBody.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/TaskAssigneeBody.java
new file mode 100755
index 0000000..0cbed2f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/TaskAssigneeBody.java
@@ -0,0 +1,56 @@
+package org.dromara.common.core.domain.model;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 浠诲姟鍙楄浜�
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class TaskAssigneeBody implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏉冮檺缂栫爜
+ */
+ private String handlerCode;
+
+ /**
+ * 鏉冮檺鍚嶇О
+ */
+ private String handlerName;
+
+ /**
+ * 鏉冮檺鍒嗙粍
+ */
+ private String groupId;
+
+ /**
+ * 寮�濮嬫椂闂�
+ */
+ private String beginTime;
+
+ /**
+ * 缁撴潫鏃堕棿
+ */
+ private String endTime;
+
+ /**
+ * 褰撳墠椤�
+ */
+ private Integer pageNum = 1;
+
+ /**
+ * 姣忛〉鏄剧ず鏉℃暟
+ */
+ private Integer pageSize = 10;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/XcxLoginBody.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/XcxLoginBody.java
new file mode 100755
index 0000000..518fe2e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/XcxLoginBody.java
@@ -0,0 +1,28 @@
+package org.dromara.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 涓夋柟鐧诲綍瀵硅薄
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class XcxLoginBody extends LoginBody {
+
+ /**
+ * 灏忕▼搴廼d(澶氫釜灏忕▼搴忔椂浣跨敤)
+ */
+ private String appid;
+
+ /**
+ * 灏忕▼搴廲ode
+ */
+ @NotBlank(message = "{xcx.code.not.blank}")
+ private String xcxCode;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/XcxLoginUser.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/XcxLoginUser.java
new file mode 100755
index 0000000..e5f3d6c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/XcxLoginUser.java
@@ -0,0 +1,27 @@
+package org.dromara.common.core.domain.model;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * 灏忕▼搴忕櫥褰曠敤鎴疯韩浠芥潈闄�
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+public class XcxLoginUser extends LoginUser {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * openid
+ */
+ private String openid;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/BusinessStatusEnum.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/BusinessStatusEnum.java
new file mode 100755
index 0000000..c1660ee
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/BusinessStatusEnum.java
@@ -0,0 +1,215 @@
+package org.dromara.common.core.enums;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 涓氬姟鐘舵�佹灇涓�
+ *
+ * @author may
+ */
+@Getter
+@AllArgsConstructor
+public enum BusinessStatusEnum {
+
+ /**
+ * 宸叉挙閿�
+ */
+ CANCEL("cancel", "宸叉挙閿�"),
+
+ /**
+ * 鑽夌
+ */
+ DRAFT("draft", "鑽夌"),
+
+ /**
+ * 寰呭鏍�
+ */
+ WAITING("waiting", "寰呭鏍�"),
+
+ /**
+ * 宸插畬鎴�
+ */
+ FINISH("finish", "宸插畬鎴�"),
+
+ /**
+ * 宸蹭綔搴�
+ */
+ INVALID("invalid", "宸蹭綔搴�"),
+
+ /**
+ * 宸查��鍥�
+ */
+ BACK("back", "宸查��鍥�"),
+
+ /**
+ * 宸茬粓姝�
+ */
+ TERMINATION("termination", "宸茬粓姝�");
+
+ /**
+ * 鐘舵��
+ */
+ private final String status;
+
+ /**
+ * 鎻忚堪
+ */
+ private final String desc;
+
+ private static final Map<String, BusinessStatusEnum> STATUS_MAP = Arrays.stream(BusinessStatusEnum.values())
+ .collect(Collectors.toConcurrentMap(BusinessStatusEnum::getStatus, Function.identity()));
+
+ /**
+ * 鏍规嵁鐘舵�佽幏鍙栧搴旂殑 BusinessStatusEnum 鏋氫妇
+ *
+ * @param status 涓氬姟鐘舵�佺爜
+ * @return 瀵瑰簲鐨� BusinessStatusEnum 鏋氫妇锛屽鏋滄壘涓嶅埌鍒欒繑鍥� null
+ */
+ public static BusinessStatusEnum getByStatus(String status) {
+ // 浣跨敤 STATUS_MAP 鑾峰彇瀵瑰簲鐨勬灇涓撅紝鑻ユ壘涓嶅埌鍒欒繑鍥� null
+ return STATUS_MAP.get(status);
+ }
+
+ /**
+ * 鏍规嵁鐘舵�佽幏鍙栧搴旂殑涓氬姟鐘舵�佹弿杩颁俊鎭�
+ *
+ * @param status 涓氬姟鐘舵�佺爜
+ * @return 杩斿洖涓氬姟鐘舵�佹弿杩帮紝鑻ョ姸鎬佺爜涓虹┖鎴栨湭鎵惧埌瀵瑰簲鐨勬灇涓撅紝杩斿洖绌哄瓧绗︿覆
+ */
+ public static String findByStatus(String status) {
+ if (StringUtils.isBlank(status)) {
+ return StrUtil.EMPTY;
+ }
+ BusinessStatusEnum statusEnum = STATUS_MAP.get(status);
+ return (statusEnum != null) ? statusEnum.getDesc() : StrUtil.EMPTY;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓烘寚瀹氱殑鐘舵�佷箣涓�锛氳崏绋裤�佸凡鎾ら攢鎴栧凡閫�鍥�
+ *
+ * @param status 瑕佹鏌ョ殑鐘舵��
+ * @return 濡傛灉鐘舵�佷负鑽夌銆佸凡鎾ら攢鎴栧凡閫�鍥炰箣涓�锛屽垯杩斿洖 true锛涘惁鍒欒繑鍥� false
+ */
+ public static boolean isDraftOrCancelOrBack(String status) {
+ return DRAFT.status.equals(status) || CANCEL.status.equals(status) || BACK.status.equals(status);
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓烘挙閿�锛岄��鍥烇紝浣滃簾锛岀粓姝�
+ *
+ * @param status status
+ * @return 缁撴灉
+ */
+ public static boolean initialState(String status) {
+ return CANCEL.status.equals(status) || BACK.status.equals(status) || INVALID.status.equals(status) || TERMINATION.status.equals(status);
+ }
+
+ /**
+ * 鑾峰彇杩愯涓殑瀹炰緥鐘舵�佸垪琛�
+ *
+ * @return 鍖呭惈杩愯涓疄渚嬬姸鎬佺殑涓嶅彲鍙樺垪琛�
+ * 锛堝寘鍚� DRAFT銆乄AITING銆丅ACK 鍜� CANCEL 鐘舵�侊級
+ */
+ public static List<String> runningStatus() {
+ return Arrays.asList(DRAFT.status, WAITING.status, BACK.status, CANCEL.status);
+ }
+
+ /**
+ * 鑾峰彇缁撴潫瀹炰緥鐨勭姸鎬佸垪琛�
+ *
+ * @return 鍖呭惈缁撴潫瀹炰緥鐘舵�佺殑涓嶅彲鍙樺垪琛�
+ * 锛堝寘鍚� FINISH銆両NVALID 鍜� TERMINATION 鐘舵�侊級
+ */
+ public static List<String> finishStatus() {
+ return Arrays.asList(FINISH.status, INVALID.status, TERMINATION.status);
+ }
+
+ /**
+ * 鍚姩娴佺▼鏍¢獙
+ *
+ * @param status 鐘舵��
+ */
+ public static void checkStartStatus(String status) {
+ if (WAITING.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡鎻愪氦杩囩敵璇�,姝e湪瀹℃壒涓紒");
+ } else if (FINISH.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡瀹屾垚鐢宠锛�");
+ } else if (INVALID.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡浣滃簾锛�");
+ } else if (TERMINATION.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡缁堟锛�");
+ } else if (StringUtils.isBlank(status)) {
+ throw new ServiceException("娴佺▼鐘舵�佷负绌猴紒");
+ }
+ }
+
+ /**
+ * 鎾ら攢娴佺▼鏍¢獙
+ *
+ * @param status 鐘舵��
+ */
+ public static void checkCancelStatus(String status) {
+ if (CANCEL.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡鎾ら攢锛�");
+ } else if (FINISH.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡瀹屾垚鐢宠锛�");
+ } else if (INVALID.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡浣滃簾锛�");
+ } else if (TERMINATION.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡缁堟锛�");
+ } else if (BACK.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡閫�鍥烇紒");
+ } else if (StringUtils.isBlank(status)) {
+ throw new ServiceException("娴佺▼鐘舵�佷负绌猴紒");
+ }
+ }
+
+ /**
+ * 椹冲洖娴佺▼鏍¢獙
+ *
+ * @param status 鐘舵��
+ */
+ public static void checkBackStatus(String status) {
+ if (BACK.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡閫�鍥烇紒");
+ } else if (FINISH.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡瀹屾垚鐢宠锛�");
+ } else if (INVALID.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡浣滃簾锛�");
+ } else if (TERMINATION.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡缁堟锛�");
+ } else if (CANCEL.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡鎾ら攢锛�");
+ } else if (StringUtils.isBlank(status)) {
+ throw new ServiceException("娴佺▼鐘舵�佷负绌猴紒");
+ }
+ }
+
+ /**
+ * 浣滃簾,缁堟娴佺▼鏍¢獙
+ *
+ * @param status 鐘舵��
+ */
+ public static void checkInvalidStatus(String status) {
+ if (FINISH.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡瀹屾垚鐢宠锛�");
+ } else if (INVALID.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡浣滃簾锛�");
+ } else if (TERMINATION.getStatus().equals(status)) {
+ throw new ServiceException("璇ュ崟鎹凡缁堟锛�");
+ } else if (StringUtils.isBlank(status)) {
+ throw new ServiceException("娴佺▼鐘舵�佷负绌猴紒");
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceType.java
new file mode 100755
index 0000000..1667ac7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceType.java
@@ -0,0 +1,39 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 璁惧绫诲瀷
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum DeviceType {
+
+ /**
+ * pc绔�
+ */
+ PC("pc"),
+
+ /**
+ * app绔�
+ */
+ APP("app"),
+
+ /**
+ * 灏忕▼搴忕
+ */
+ XCX("xcx"),
+
+ /**
+ * 绗笁鏂圭ぞ浜ょ櫥褰曞钩鍙�
+ */
+ SOCIAL("social");
+
+ /**
+ * 璁惧鏍囪瘑
+ */
+ private final String device;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/FormatsType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/FormatsType.java
new file mode 100755
index 0000000..8d4b6d9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/FormatsType.java
@@ -0,0 +1,146 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.dromara.common.core.utils.StringUtils;
+
+/*
+ * 鏃ユ湡鏍煎紡
+ * "yyyy"锛�4浣嶆暟鐨勫勾浠斤紝渚嬪锛�2023骞磋〃绀轰负"2023"銆�
+ * "yy"锛�2浣嶆暟鐨勫勾浠斤紝渚嬪锛�2023骞磋〃绀轰负"23"銆�
+ * "MM"锛�2浣嶆暟鐨勬湀浠斤紝鍙栧�艰寖鍥翠负01鍒�12锛屼緥濡傦細7鏈堣〃绀轰负"07"銆�
+ * "M"锛氫笉甯﹀墠瀵奸浂鐨勬湀浠斤紝鍙栧�艰寖鍥翠负1鍒�12锛屼緥濡傦細7鏈堣〃绀轰负"7"銆�
+ * "dd"锛�2浣嶆暟鐨勬棩鏈燂紝鍙栧�艰寖鍥翠负01鍒�31锛屼緥濡傦細22鏃ヨ〃绀轰负"22"銆�
+ * "d"锛氫笉甯﹀墠瀵奸浂鐨勬棩鏈燂紝鍙栧�艰寖鍥翠负1鍒�31锛屼緥濡傦細22鏃ヨ〃绀轰负"22"銆�
+ * "EEEE"锛氭槦鏈熺殑鍏ㄥ悕锛屼緥濡傦細鏄熸湡涓夎〃绀轰负"Wednesday"銆�
+ * "E"锛氭槦鏈熺殑缂╁啓锛屼緥濡傦細鏄熸湡涓夎〃绀轰负"Wed"銆�
+ * "DDD" 鎴� "D"锛氫竴骞翠腑鐨勭鍑犲ぉ锛屽彇鍊艰寖鍥翠负001鍒�366锛屼緥濡傦細绗�200澶╄〃绀轰负"200"銆�
+ * 鏃堕棿鏍煎紡
+ * "HH"锛�24灏忔椂鍒剁殑灏忔椂鏁帮紝鍙栧�艰寖鍥翠负00鍒�23锛屼緥濡傦細涓嬪崍5鐐硅〃绀轰负"17"銆�
+ * "hh"锛�12灏忔椂鍒剁殑灏忔椂鏁帮紝鍙栧�艰寖鍥翠负01鍒�12锛屼緥濡傦細涓嬪崍5鐐硅〃绀轰负"05"銆�
+ * "mm"锛氬垎閽熸暟锛屽彇鍊艰寖鍥翠负00鍒�59锛屼緥濡傦細30鍒嗛挓琛ㄧず涓�"30"銆�
+ * "ss"锛氱鏁帮紝鍙栧�艰寖鍥翠负00鍒�59锛屼緥濡傦細45绉掕〃绀轰负"45"銆�
+ * "SSS"锛氭绉掓暟锛屽彇鍊艰寖鍥翠负000鍒�999锛屼緥濡傦細123姣琛ㄧず涓�"123"銆�
+ */
+
+/**
+ * 鏃ユ湡鏍煎紡涓庢椂闂存牸寮忔灇涓�
+ */
+@Getter
+@AllArgsConstructor
+public enum FormatsType {
+
+ /**
+ * 渚嬪锛�2023骞磋〃绀轰负"23"
+ */
+ YY("yy"),
+
+ /**
+ * 渚嬪锛�2023骞磋〃绀轰负"2023"
+ */
+ YYYY("yyyy"),
+
+ /**
+ * 渚嬩緥濡傦紝2023骞�7鏈堝彲浠ヨ〃绀轰负 "2023-07"
+ */
+ YYYY_MM("yyyy-MM"),
+
+ /**
+ * 渚嬪锛屾棩鏈� "2023骞�7鏈�22鏃�" 鍙互琛ㄧず涓� "2023-07-22"
+ */
+ YYYY_MM_DD("yyyy-MM-dd"),
+
+ /**
+ * 渚嬪锛屽綋鍓嶆椂闂村鏋滄槸 "2023骞�7鏈�22鏃ヤ笅鍗�3鐐�30鍒�"锛屽垯鍙互琛ㄧず涓� "2023-07-22 15:30"
+ */
+ YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm"),
+
+ /**
+ * 渚嬪锛屽綋鍓嶆椂闂村鏋滄槸 "2023骞�7鏈�22鏃ヤ笅鍗�3鐐�30鍒�45绉�"锛屽垯鍙互琛ㄧず涓� "2023-07-22 15:30:45"
+ */
+ YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss"),
+
+ /**
+ * 渚嬪锛氫笅鍗�3鐐�30鍒�45绉掞紝琛ㄧず涓� "15:30:45"
+ */
+ HH_MM_SS("HH:mm:ss"),
+
+ /**
+ * 渚嬩緥濡傦紝2023骞�7鏈堝彲浠ヨ〃绀轰负 "2023/07"
+ */
+ YYYY_MM_SLASH("yyyy/MM"),
+
+ /**
+ * 渚嬪锛屾棩鏈� "2023骞�7鏈�22鏃�" 鍙互琛ㄧず涓� "2023/07/22"
+ */
+ YYYY_MM_DD_SLASH("yyyy/MM/dd"),
+
+ /**
+ * 渚嬪锛屽綋鍓嶆椂闂村鏋滄槸 "2023骞�7鏈�22鏃ヤ笅鍗�3鐐�30鍒�45绉�"锛屽垯鍙互琛ㄧず涓� "2023/07/22 15:30:45"
+ */
+ YYYY_MM_DD_HH_MM_SLASH("yyyy/MM/dd HH:mm"),
+
+ /**
+ * 渚嬪锛屽綋鍓嶆椂闂村鏋滄槸 "2023骞�7鏈�22鏃ヤ笅鍗�3鐐�30鍒�45绉�"锛屽垯鍙互琛ㄧず涓� "2023/07/22 15:30:45"
+ */
+ YYYY_MM_DD_HH_MM_SS_SLASH("yyyy/MM/dd HH:mm:ss"),
+
+ /**
+ * 渚嬩緥濡傦紝2023骞�7鏈堝彲浠ヨ〃绀轰负 "2023.07"
+ */
+ YYYY_MM_DOT("yyyy.MM"),
+
+ /**
+ * 渚嬪锛屾棩鏈� "2023骞�7鏈�22鏃�" 鍙互琛ㄧず涓� "2023.07.22"
+ */
+ YYYY_MM_DD_DOT("yyyy.MM.dd"),
+
+ /**
+ * 渚嬪锛屽綋鍓嶆椂闂村鏋滄槸 "2023骞�7鏈�22鏃ヤ笅鍗�3鐐�30鍒�"锛屽垯鍙互琛ㄧず涓� "2023.07.22 15:30"
+ */
+ YYYY_MM_DD_HH_MM_DOT("yyyy.MM.dd HH:mm"),
+
+ /**
+ * 渚嬪锛屽綋鍓嶆椂闂村鏋滄槸 "2023骞�7鏈�22鏃ヤ笅鍗�3鐐�30鍒�45绉�"锛屽垯鍙互琛ㄧず涓� "2023.07.22 15:30:45"
+ */
+ YYYY_MM_DD_HH_MM_SS_DOT("yyyy.MM.dd HH:mm:ss"),
+
+ /**
+ * 渚嬪锛�2023骞�7鏈堝彲浠ヨ〃绀轰负 "202307"
+ */
+ YYYYMM("yyyyMM"),
+
+ /**
+ * 渚嬪锛�2023骞�7鏈�22鏃ュ彲浠ヨ〃绀轰负 "20230722"
+ */
+ YYYYMMDD("yyyyMMdd"),
+
+ /**
+ * 渚嬪锛�2023骞�7鏈�22鏃ヤ笅鍗�3鐐瑰彲浠ヨ〃绀轰负 "2023072215"
+ */
+ YYYYMMDDHH("yyyyMMddHH"),
+
+ /**
+ * 渚嬪锛�2023骞�7鏈�22鏃ヤ笅鍗�3鐐�30鍒嗗彲浠ヨ〃绀轰负 "202307221530"
+ */
+ YYYYMMDDHHMM("yyyyMMddHHmm"),
+
+ /**
+ * 渚嬪锛�2023骞�7鏈�22鏃ヤ笅鍗�3鐐�30鍒�45绉掑彲浠ヨ〃绀轰负 "20230722153045"
+ */
+ YYYYMMDDHHMMSS("yyyyMMddHHmmss");
+
+ /**
+ * 鏃堕棿鏍煎紡
+ */
+ private final String timeFormat;
+
+ public static FormatsType getFormatsType(String str) {
+ for (FormatsType value : values()) {
+ if (StringUtils.contains(str, value.getTimeFormat())) {
+ return value;
+ }
+ }
+ throw new RuntimeException("'FormatsType' not found By " + str);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/LoginType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/LoginType.java
new file mode 100755
index 0000000..f9cac66
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/LoginType.java
@@ -0,0 +1,44 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鐧诲綍绫诲瀷
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum LoginType {
+
+ /**
+ * 瀵嗙爜鐧诲綍
+ */
+ PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"),
+
+ /**
+ * 鐭俊鐧诲綍
+ */
+ SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
+
+ /**
+ * 閭鐧诲綍
+ */
+ EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"),
+
+ /**
+ * 灏忕▼搴忕櫥褰�
+ */
+ XCX("", "");
+
+ /**
+ * 鐧诲綍閲嶈瘯瓒呭嚭闄愬埗鎻愮ず
+ */
+ final String retryLimitExceed;
+
+ /**
+ * 鐧诲綍閲嶈瘯闄愬埗璁℃暟鎻愮ず
+ */
+ final String retryLimitCount;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserStatus.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserStatus.java
new file mode 100755
index 0000000..be7e44d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserStatus.java
@@ -0,0 +1,30 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鐢ㄦ埛鐘舵��
+ *
+ * @author ruoyi
+ */
+@Getter
+@AllArgsConstructor
+public enum UserStatus {
+ /**
+ * 姝e父
+ */
+ OK("0", "姝e父"),
+ /**
+ * 鍋滅敤
+ */
+ DISABLE("1", "鍋滅敤"),
+ /**
+ * 鍒犻櫎
+ */
+ DELETED("2", "鍒犻櫎");
+
+ private final String code;
+ private final String info;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserType.java
new file mode 100755
index 0000000..636988f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserType.java
@@ -0,0 +1,39 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.dromara.common.core.utils.StringUtils;
+
+/**
+ * 鐢ㄦ埛绫诲瀷
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum UserType {
+
+ /**
+ * 鍚庡彴绯荤粺鐢ㄦ埛
+ */
+ SYS_USER("sys_user"),
+
+ /**
+ * 绉诲姩瀹㈡埛绔敤鎴�
+ */
+ APP_USER("app_user");
+
+ /**
+ * 鐢ㄦ埛绫诲瀷鏍囪瘑锛堢敤浜� token銆佹潈闄愯瘑鍒瓑锛�
+ */
+ private final String userType;
+
+ public static UserType getUserType(String str) {
+ for (UserType value : values()) {
+ if (StringUtils.contains(str, value.getUserType())) {
+ return value;
+ }
+ }
+ throw new RuntimeException("'UserType' not found By " + str);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java
new file mode 100755
index 0000000..90f5752
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java
@@ -0,0 +1,67 @@
+package org.dromara.common.core.exception;
+
+import cn.hutool.core.text.StrFormatter;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * 涓氬姟寮傚父锛堟敮鎸佸崰浣嶇 {} 锛�
+ *
+ * @author ruoyi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public final class ServiceException extends RuntimeException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 閿欒鐮�
+ */
+ private Integer code;
+
+ /**
+ * 閿欒鎻愮ず
+ */
+ private String message;
+
+ /**
+ * 閿欒鏄庣粏锛屽唴閮ㄨ皟璇曢敊璇�
+ */
+ private String detailMessage;
+
+ public ServiceException(String message) {
+ this.message = message;
+ }
+
+ public ServiceException(String message, Integer code) {
+ this.message = message;
+ this.code = code;
+ }
+
+ public ServiceException(String message, Object... args) {
+ this.message = StrFormatter.format(message, args);
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ public ServiceException setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+ public ServiceException setDetailMessage(String detailMessage) {
+ this.detailMessage = detailMessage;
+ return this;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/SseException.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/SseException.java
new file mode 100755
index 0000000..a76e16d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/SseException.java
@@ -0,0 +1,62 @@
+package org.dromara.common.core.exception;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * sse 鐗瑰埗寮傚父
+ *
+ * @author LionLi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public final class SseException extends RuntimeException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 閿欒鐮�
+ */
+ private Integer code;
+
+ /**
+ * 閿欒鎻愮ず
+ */
+ private String message;
+
+ /**
+ * 閿欒鏄庣粏锛屽唴閮ㄨ皟璇曢敊璇�
+ */
+ private String detailMessage;
+
+ public SseException(String message) {
+ this.message = message;
+ }
+
+ public SseException(String message, Integer code) {
+ this.message = message;
+ this.code = code;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ public SseException setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+ public SseException setDetailMessage(String detailMessage) {
+ this.detailMessage = detailMessage;
+ return this;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/base/BaseException.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/base/BaseException.java
new file mode 100755
index 0000000..40ce01b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/base/BaseException.java
@@ -0,0 +1,74 @@
+package org.dromara.common.core.exception.base;
+
+import lombok.AllArgsConstructor;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.StringUtils;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * 鍩虹寮傚父
+ *
+ * @author ruoyi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class BaseException extends RuntimeException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鎵�灞炴ā鍧�
+ */
+ private String module;
+
+ /**
+ * 閿欒鐮�
+ */
+ private String code;
+
+ /**
+ * 閿欒鐮佸搴旂殑鍙傛暟
+ */
+ private Object[] args;
+
+ /**
+ * 閿欒娑堟伅
+ */
+ private String defaultMessage;
+
+ public BaseException(String module, String code, Object[] args) {
+ this(module, code, args, null);
+ }
+
+ public BaseException(String module, String defaultMessage) {
+ this(module, null, null, defaultMessage);
+ }
+
+ public BaseException(String code, Object[] args) {
+ this(null, code, args, null);
+ }
+
+ public BaseException(String defaultMessage) {
+ this(null, null, null, defaultMessage);
+ }
+
+ @Override
+ public String getMessage() {
+ String message = null;
+ if (!StringUtils.isEmpty(code)) {
+ message = MessageUtils.message(code, args);
+ }
+ if (message == null) {
+ message = defaultMessage;
+ }
+ return message;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java
new file mode 100755
index 0000000..d374fc0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java
@@ -0,0 +1,21 @@
+package org.dromara.common.core.exception.file;
+
+import org.dromara.common.core.exception.base.BaseException;
+
+import java.io.Serial;
+
+/**
+ * 鏂囦欢淇℃伅寮傚父绫�
+ *
+ * @author ruoyi
+ */
+public class FileException extends BaseException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public FileException(String code, Object[] args) {
+ super("file", code, args, null);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java
new file mode 100755
index 0000000..af98124
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java
@@ -0,0 +1,18 @@
+package org.dromara.common.core.exception.file;
+
+import java.io.Serial;
+
+/**
+ * 鏂囦欢鍚嶇О瓒呴暱闄愬埗寮傚父绫�
+ *
+ * @author ruoyi
+ */
+public class FileNameLengthLimitExceededException extends FileException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public FileNameLengthLimitExceededException(int defaultFileNameLength) {
+ super("upload.filename.exceed.length", new Object[]{defaultFileNameLength});
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java
new file mode 100755
index 0000000..1eb8d40
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java
@@ -0,0 +1,18 @@
+package org.dromara.common.core.exception.file;
+
+import java.io.Serial;
+
+/**
+ * 鏂囦欢鍚嶅ぇ灏忛檺鍒跺紓甯哥被
+ *
+ * @author ruoyi
+ */
+public class FileSizeLimitExceededException extends FileException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public FileSizeLimitExceededException(long defaultMaxSize) {
+ super("upload.exceed.maxSize", new Object[]{defaultMaxSize});
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaException.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaException.java
new file mode 100755
index 0000000..43824e0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaException.java
@@ -0,0 +1,18 @@
+package org.dromara.common.core.exception.user;
+
+import java.io.Serial;
+
+/**
+ * 楠岃瘉鐮侀敊璇紓甯哥被
+ *
+ * @author ruoyi
+ */
+public class CaptchaException extends UserException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public CaptchaException() {
+ super("user.jcaptcha.error");
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaExpireException.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaExpireException.java
new file mode 100755
index 0000000..f4b8cac
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaExpireException.java
@@ -0,0 +1,18 @@
+package org.dromara.common.core.exception.user;
+
+import java.io.Serial;
+
+/**
+ * 楠岃瘉鐮佸け鏁堝紓甯哥被
+ *
+ * @author ruoyi
+ */
+public class CaptchaExpireException extends UserException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public CaptchaExpireException() {
+ super("user.jcaptcha.expire");
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/UserException.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/UserException.java
new file mode 100755
index 0000000..024fed6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/UserException.java
@@ -0,0 +1,20 @@
+package org.dromara.common.core.exception.user;
+
+import org.dromara.common.core.exception.base.BaseException;
+
+import java.io.Serial;
+
+/**
+ * 鐢ㄦ埛淇℃伅寮傚父绫�
+ *
+ * @author ruoyi
+ */
+public class UserException extends BaseException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public UserException(String code, Object... args) {
+ super("user", code, args, null);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/RegexPatternPoolFactory.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/RegexPatternPoolFactory.java
new file mode 100755
index 0000000..fd907d2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/RegexPatternPoolFactory.java
@@ -0,0 +1,52 @@
+package org.dromara.common.core.factory;
+
+import cn.hutool.core.lang.PatternPool;
+import org.dromara.common.core.constant.RegexConstants;
+
+import java.util.regex.Pattern;
+
+/**
+ * 姝e垯琛ㄨ揪寮忔ā寮忔睜宸ュ巶
+ * <p>鍒濆鍖栫殑鏃跺�欏皢姝e垯琛ㄨ揪寮忓姞鍏ョ紦瀛樻睜褰撲腑</p>
+ * <p>鎻愰珮姝e垯琛ㄨ揪寮忕殑鎬ц兘锛岄伩鍏嶉噸澶嶇紪璇戠浉鍚岀殑姝e垯琛ㄨ揪寮�</p>
+ *
+ * @author 21001
+ */
+public class RegexPatternPoolFactory extends PatternPool {
+
+ /**
+ * 瀛楀吀绫诲瀷蹇呴』浠ュ瓧姣嶅紑澶达紝涓斿彧鑳戒负锛堝皬鍐欏瓧姣嶏紝鏁板瓧锛屼笅婊戠嚎锛�
+ */
+ public static final Pattern DICTIONARY_TYPE = get(RegexConstants.DICTIONARY_TYPE);
+
+ /**
+ * 韬唤璇佸彿鐮侊紙鍚�6浣嶏級
+ */
+ public static final Pattern ID_CARD_LAST_6 = get(RegexConstants.ID_CARD_LAST_6);
+
+ /**
+ * QQ鍙风爜
+ */
+ public static final Pattern QQ_NUMBER = get(RegexConstants.QQ_NUMBER);
+
+ /**
+ * 閭斂缂栫爜
+ */
+ public static final Pattern POSTAL_CODE = get(RegexConstants.POSTAL_CODE);
+
+ /**
+ * 娉ㄥ唽璐﹀彿
+ */
+ public static final Pattern ACCOUNT = get(RegexConstants.ACCOUNT);
+
+ /**
+ * 瀵嗙爜锛氬寘鍚嚦灏�8涓瓧绗︼紝鍖呮嫭澶у啓瀛楁瘝銆佸皬鍐欏瓧姣嶃�佹暟瀛楀拰鐗规畩瀛楃
+ */
+ public static final Pattern PASSWORD = get(RegexConstants.PASSWORD);
+
+ /**
+ * 閫氱敤鐘舵�侊紙0琛ㄧず姝e父锛�1琛ㄧず鍋滅敤锛�
+ */
+ public static final Pattern STATUS = get(RegexConstants.STATUS);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/YmlPropertySourceFactory.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/YmlPropertySourceFactory.java
new file mode 100755
index 0000000..af61b90
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/YmlPropertySourceFactory.java
@@ -0,0 +1,31 @@
+package org.dromara.common.core.factory;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.support.DefaultPropertySourceFactory;
+import org.springframework.core.io.support.EncodedResource;
+
+import java.io.IOException;
+
+/**
+ * yml 閰嶇疆婧愬伐鍘�
+ *
+ * @author Lion Li
+ */
+public class YmlPropertySourceFactory extends DefaultPropertySourceFactory {
+
+ @Override
+ public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
+ String sourceName = resource.getResource().getFilename();
+ if (StringUtils.isNotBlank(sourceName) && StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) {
+ YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
+ factory.setResources(resource.getResource());
+ factory.afterPropertiesSet();
+ return new PropertiesPropertySource(sourceName, factory.getObject());
+ }
+ return super.createPropertySource(name, resource);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/ConfigService.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/ConfigService.java
new file mode 100755
index 0000000..9ae52c7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/ConfigService.java
@@ -0,0 +1,100 @@
+package org.dromara.common.core.service;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 閫氱敤 鍙傛暟閰嶇疆鏈嶅姟
+ *
+ * @author Lion Li
+ */
+public interface ConfigService {
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇鍙傛暟鍊�
+ *
+ * @param configKey 鍙傛暟 key
+ * @return 鍙傛暟鍊�
+ */
+ String getConfigValue(String configKey);
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇甯冨皵鍊�
+ *
+ * @param configKey 鍙傛暟 key
+ * @return Boolean 鍊�
+ */
+ default Boolean getConfigBool(String configKey) {
+ return Convert.toBool(getConfigValue(configKey));
+ }
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇鏁存暟鍊�
+ *
+ * @param configKey 鍙傛暟 key
+ * @return Integer 鍊�
+ */
+ default Integer getConfigInt(String configKey) {
+ return Convert.toInt(getConfigValue(configKey));
+ }
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇闀挎暣鍨嬪��
+ *
+ * @param configKey 鍙傛暟 key
+ * @return Long 鍊�
+ */
+ default Long getConfigLong(String configKey) {
+ return Convert.toLong(getConfigValue(configKey));
+ }
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇 BigDecimal 鍊�
+ *
+ * @param configKey 鍙傛暟 key
+ * @return BigDecimal 鍊�
+ */
+ default BigDecimal getConfigDecimal(String configKey) {
+ return Convert.toBigDecimal(getConfigValue(configKey));
+ }
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇 Map 绫诲瀷鐨勯厤缃�
+ *
+ * @param configKey 鍙傛暟 key
+ * @return Dict 瀵硅薄锛屽鏋滈厤缃负绌烘垨鏃犳硶瑙f瀽锛岃繑鍥炵┖ Dict
+ */
+ Dict getConfigMap(String configKey);
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇 Map 绫诲瀷鐨勯厤缃垪琛�
+ *
+ * @param configKey 鍙傛暟 key
+ * @return Dict 鍒楄〃锛屽鏋滈厤缃负绌烘垨鏃犳硶瑙f瀽锛岃繑鍥炵┖鍒楄〃
+ */
+ List<Dict> getConfigArrayMap(String configKey);
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇鎸囧畾绫诲瀷鐨勯厤缃璞�
+ *
+ * @param configKey 鍙傛暟 key
+ * @param clazz 鐩爣瀵硅薄绫诲瀷
+ * @param <T> 鐩爣瀵硅薄娉涘瀷
+ * @return 瀵硅薄瀹炰緥锛屽鏋滈厤缃负绌烘垨鏃犳硶瑙f瀽锛岃繑鍥� null
+ */
+ <T> T getConfigObject(String configKey, Class<T> clazz);
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇鎸囧畾绫诲瀷鐨勯厤缃垪琛�
+ *
+ * @param configKey 鍙傛暟 key
+ * @param clazz 鐩爣鍏冪礌绫诲瀷
+ * @param <T> 鍏冪礌绫诲瀷娉涘瀷
+ * @return 鎸囧畾绫诲瀷鍒楄〃锛屽鏋滈厤缃负绌烘垨鏃犳硶瑙f瀽锛岃繑鍥炵┖鍒楄〃
+ */
+ <T> List<T> getConfigArray(String configKey, Class<T> clazz);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DeptService.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DeptService.java
new file mode 100755
index 0000000..725718a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DeptService.java
@@ -0,0 +1,46 @@
+package org.dromara.common.core.service;
+
+import org.dromara.common.core.domain.dto.DeptDTO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 閫氱敤 閮ㄩ棬鏈嶅姟
+ *
+ * @author Lion Li
+ */
+public interface DeptService {
+
+ /**
+ * 閫氳繃閮ㄩ棬ID鏌ヨ閮ㄩ棬鍚嶇О
+ *
+ * @param deptIds 閮ㄩ棬ID涓查�楀彿鍒嗛殧
+ * @return 閮ㄩ棬鍚嶇О涓查�楀彿鍒嗛殧
+ */
+ String selectDeptNameByIds(String deptIds);
+
+ /**
+ * 鏍规嵁閮ㄩ棬ID鏌ヨ閮ㄩ棬璐熻矗浜�
+ *
+ * @param deptId 閮ㄩ棬ID锛岀敤浜庢寚瀹氶渶瑕佹煡璇㈢殑閮ㄩ棬
+ * @return 杩斿洖璇ラ儴闂ㄧ殑璐熻矗浜篒D
+ */
+ Long selectDeptLeaderById(Long deptId);
+
+ /**
+ * 鏌ヨ閮ㄩ棬
+ *
+ * @return 閮ㄩ棬鍒楄〃
+ */
+ List<DeptDTO> selectDeptsByList();
+
+ /**
+ * 鏍规嵁閮ㄩ棬 ID 鍒楄〃鏌ヨ閮ㄩ棬鍚嶇О鏄犲皠鍏崇郴
+ *
+ * @param deptIds 閮ㄩ棬 ID 鍒楄〃
+ * @return Map锛屽叾涓� key 涓洪儴闂� ID锛寁alue 涓哄搴旂殑閮ㄩ棬鍚嶇О
+ */
+ Map<Long, String> selectDeptNamesByIds(List<Long> deptIds);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java
new file mode 100755
index 0000000..d80395c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java
@@ -0,0 +1,87 @@
+package org.dromara.common.core.service;
+
+import org.dromara.common.core.domain.dto.DictDataDTO;
+import org.dromara.common.core.domain.dto.DictTypeDTO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 閫氱敤 瀛楀吀鏈嶅姟
+ *
+ * @author Lion Li
+ */
+public interface DictService {
+
+ /**
+ * 鍒嗛殧绗�
+ */
+ String SEPARATOR = ",";
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏稿�艰幏鍙栧瓧鍏告爣绛�
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @param dictValue 瀛楀吀鍊�
+ * @return 瀛楀吀鏍囩
+ */
+ default String getDictLabel(String dictType, String dictValue) {
+ return getDictLabel(dictType, dictValue, SEPARATOR);
+ }
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏告爣绛捐幏鍙栧瓧鍏稿��
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @param dictLabel 瀛楀吀鏍囩
+ * @return 瀛楀吀鍊�
+ */
+ default String getDictValue(String dictType, String dictLabel) {
+ return getDictValue(dictType, dictLabel, SEPARATOR);
+ }
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏稿�艰幏鍙栧瓧鍏告爣绛�
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @param dictValue 瀛楀吀鍊�
+ * @param separator 鍒嗛殧绗�
+ * @return 瀛楀吀鏍囩
+ */
+ String getDictLabel(String dictType, String dictValue, String separator);
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏告爣绛捐幏鍙栧瓧鍏稿��
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @param dictLabel 瀛楀吀鏍囩
+ * @param separator 鍒嗛殧绗�
+ * @return 瀛楀吀鍊�
+ */
+ String getDictValue(String dictType, String dictLabel, String separator);
+
+ /**
+ * 鑾峰彇瀛楀吀涓嬫墍鏈夌殑瀛楀吀鍊间笌鏍囩
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return dictValue涓簁ey锛宒ictLabel涓哄�肩粍鎴愮殑Map
+ */
+ Map<String, String> getAllDictByDictType(String dictType);
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鏌ヨ璇︾粏淇℃伅
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return 瀛楀吀绫诲瀷璇︾粏淇℃伅
+ */
+ DictTypeDTO getDictType(String dictType);
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鏌ヨ瀛楀吀鏁版嵁鍒楄〃
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return 瀛楀吀鏁版嵁鍒楄〃
+ */
+ List<DictDataDTO> getDictData(String dictType);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/OssService.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/OssService.java
new file mode 100755
index 0000000..1a52de0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/OssService.java
@@ -0,0 +1,29 @@
+package org.dromara.common.core.service;
+
+import org.dromara.common.core.domain.dto.OssDTO;
+
+import java.util.List;
+
+/**
+ * 閫氱敤 OSS鏈嶅姟
+ *
+ * @author Lion Li
+ */
+public interface OssService {
+
+ /**
+ * 閫氳繃ossId鏌ヨ瀵瑰簲鐨剈rl
+ *
+ * @param ossIds ossId涓查�楀彿鍒嗛殧
+ * @return url涓查�楀彿鍒嗛殧
+ */
+ String selectUrlByIds(String ossIds);
+
+ /**
+ * 閫氳繃ossId鏌ヨ鍒楄〃
+ *
+ * @param ossIds ossId涓查�楀彿鍒嗛殧
+ * @return 鍒楄〃
+ */
+ List<OssDTO> selectByIds(String ossIds);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PermissionService.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PermissionService.java
new file mode 100755
index 0000000..d7db79a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PermissionService.java
@@ -0,0 +1,28 @@
+package org.dromara.common.core.service;
+
+import java.util.Set;
+
+/**
+ * 鐢ㄦ埛鏉冮檺澶勭悊
+ *
+ * @author Lion Li
+ */
+public interface PermissionService {
+
+ /**
+ * 鑾峰彇瑙掕壊鏁版嵁鏉冮檺
+ *
+ * @param userId 鐢ㄦ埛id
+ * @return 瑙掕壊鏉冮檺淇℃伅
+ */
+ Set<String> getRolePermission(Long userId);
+
+ /**
+ * 鑾峰彇鑿滃崟鏁版嵁鏉冮檺
+ *
+ * @param userId 鐢ㄦ埛id
+ * @return 鑿滃崟鏉冮檺淇℃伅
+ */
+ Set<String> getMenuPermission(Long userId);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PostService.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PostService.java
new file mode 100755
index 0000000..58c68d6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PostService.java
@@ -0,0 +1,21 @@
+package org.dromara.common.core.service;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 閫氱敤 宀椾綅鏈嶅姟
+ *
+ * @author AprilWind
+ */
+public interface PostService {
+
+ /**
+ * 鏍规嵁宀椾綅 ID 鍒楄〃鏌ヨ宀椾綅鍚嶇О鏄犲皠鍏崇郴
+ *
+ * @param postIds 宀椾綅 ID 鍒楄〃
+ * @return Map锛屽叾涓� key 涓哄矖浣� ID锛寁alue 涓哄搴旂殑宀椾綅鍚嶇О
+ */
+ Map<Long, String> selectPostNamesByIds(List<Long> postIds);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/RoleService.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/RoleService.java
new file mode 100755
index 0000000..d2805b7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/RoleService.java
@@ -0,0 +1,21 @@
+package org.dromara.common.core.service;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 閫氱敤 瑙掕壊鏈嶅姟
+ *
+ * @author AprilWind
+ */
+public interface RoleService {
+
+ /**
+ * 鏍规嵁瑙掕壊 ID 鍒楄〃鏌ヨ瑙掕壊鍚嶇О鏄犲皠鍏崇郴
+ *
+ * @param roleIds 瑙掕壊 ID 鍒楄〃
+ * @return Map锛屽叾涓� key 涓鸿鑹� ID锛寁alue 涓哄搴旂殑瑙掕壊鍚嶇О
+ */
+ Map<Long, String> selectRoleNamesByIds(List<Long> roleIds);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/TaskAssigneeService.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/TaskAssigneeService.java
new file mode 100755
index 0000000..9af6691
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/TaskAssigneeService.java
@@ -0,0 +1,45 @@
+package org.dromara.common.core.service;
+
+import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
+import org.dromara.common.core.domain.model.TaskAssigneeBody;
+
+/**
+ * 宸ヤ綔娴佽璁″櫒鑾峰彇浠诲姟鎵ц浜�
+ *
+ * @author Lion Li
+ */
+public interface TaskAssigneeService {
+
+ /**
+ * 鏌ヨ瑙掕壊骞惰繑鍥炰换鍔℃寚娲剧殑鍒楄〃锛屾敮鎸佸垎椤�
+ *
+ * @param taskQuery 鏌ヨ鏉′欢
+ * @return 鍔炵悊浜�
+ */
+ TaskAssigneeDTO selectRolesByTaskAssigneeList(TaskAssigneeBody taskQuery);
+
+ /**
+ * 鏌ヨ宀椾綅骞惰繑鍥炰换鍔℃寚娲剧殑鍒楄〃锛屾敮鎸佸垎椤�
+ *
+ * @param taskQuery 鏌ヨ鏉′欢
+ * @return 鍔炵悊浜�
+ */
+ TaskAssigneeDTO selectPostsByTaskAssigneeList(TaskAssigneeBody taskQuery);
+
+ /**
+ * 鏌ヨ閮ㄩ棬骞惰繑鍥炰换鍔℃寚娲剧殑鍒楄〃锛屾敮鎸佸垎椤�
+ *
+ * @param taskQuery 鏌ヨ鏉′欢
+ * @return 鍔炵悊浜�
+ */
+ TaskAssigneeDTO selectDeptsByTaskAssigneeList(TaskAssigneeBody taskQuery);
+
+ /**
+ * 鏌ヨ鐢ㄦ埛骞惰繑鍥炰换鍔℃寚娲剧殑鍒楄〃锛屾敮鎸佸垎椤�
+ *
+ * @param taskQuery 鏌ヨ鏉′欢
+ * @return 鍔炵悊浜�
+ */
+ TaskAssigneeDTO selectUsersByTaskAssigneeList(TaskAssigneeBody taskQuery);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java
new file mode 100755
index 0000000..eefeef0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java
@@ -0,0 +1,103 @@
+package org.dromara.common.core.service;
+
+import org.dromara.common.core.domain.dto.UserDTO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 閫氱敤 鐢ㄦ埛鏈嶅姟
+ *
+ * @author Lion Li
+ */
+public interface UserService {
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛璐︽埛
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鐢ㄦ埛璐︽埛
+ */
+ String selectUserNameById(Long userId);
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛璐︽埛
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鐢ㄦ埛鍚嶇О
+ */
+ String selectNicknameById(Long userId);
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛璐︽埛
+ *
+ * @param userIds 鐢ㄦ埛ID 澶氫釜鐢ㄩ�楀彿闅斿紑
+ * @return 鐢ㄦ埛鍚嶇О
+ */
+ String selectNicknameByIds(String userIds);
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛鎵嬫満鍙�
+ *
+ * @param userId 鐢ㄦ埛id
+ * @return 鐢ㄦ埛鎵嬫満鍙�
+ */
+ String selectPhonenumberById(Long userId);
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛閭
+ *
+ * @param userId 鐢ㄦ埛id
+ * @return 鐢ㄦ埛閭
+ */
+ String selectEmailById(Long userId);
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛鍒楄〃
+ *
+ * @param userIds 鐢ㄦ埛ids
+ * @return 鐢ㄦ埛鍒楄〃
+ */
+ List<UserDTO> selectListByIds(List<Long> userIds);
+
+ /**
+ * 閫氳繃瑙掕壊ID鏌ヨ鐢ㄦ埛ID
+ *
+ * @param roleIds 瑙掕壊ids
+ * @return 鐢ㄦ埛ids
+ */
+ List<Long> selectUserIdsByRoleIds(List<Long> roleIds);
+
+ /**
+ * 閫氳繃瑙掕壊ID鏌ヨ鐢ㄦ埛
+ *
+ * @param roleIds 瑙掕壊ids
+ * @return 鐢ㄦ埛
+ */
+ List<UserDTO> selectUsersByRoleIds(List<Long> roleIds);
+
+ /**
+ * 閫氳繃閮ㄩ棬ID鏌ヨ鐢ㄦ埛
+ *
+ * @param deptIds 閮ㄩ棬ids
+ * @return 鐢ㄦ埛
+ */
+ List<UserDTO> selectUsersByDeptIds(List<Long> deptIds);
+
+ /**
+ * 閫氳繃宀椾綅ID鏌ヨ鐢ㄦ埛
+ *
+ * @param postIds 宀椾綅ids
+ * @return 鐢ㄦ埛
+ */
+ List<UserDTO> selectUsersByPostIds(List<Long> postIds);
+
+ /**
+ * 鏍规嵁鐢ㄦ埛 ID 鍒楄〃鏌ヨ鐢ㄦ埛鍚嶇О鏄犲皠鍏崇郴
+ *
+ * @param userIds 鐢ㄦ埛 ID 鍒楄〃
+ * @return Map锛屽叾涓� key 涓虹敤鎴� ID锛寁alue 涓哄搴旂殑鐢ㄦ埛鍚嶇О
+ */
+ Map<Long, String> selectUserNamesByIds(List<Long> userIds);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java
new file mode 100755
index 0000000..8efeb76
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java
@@ -0,0 +1,105 @@
+package org.dromara.common.core.service;
+
+import org.dromara.common.core.domain.dto.CompleteTaskDTO;
+import org.dromara.common.core.domain.dto.StartProcessDTO;
+import org.dromara.common.core.domain.dto.StartProcessReturnDTO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 閫氱敤 宸ヤ綔娴佹湇鍔�
+ *
+ * @author may
+ */
+public interface WorkflowService {
+
+ /**
+ * 杩愯涓殑瀹炰緥 鍒犻櫎绋嬪疄渚嬶紝鍒犻櫎鍘嗗彶璁板綍锛屽垹闄や笟鍔′笌娴佺▼鍏宠仈淇℃伅
+ *
+ * @param businessIds 涓氬姟id
+ * @return 缁撴灉
+ */
+ boolean deleteInstance(List<String> businessIds);
+
+ /**
+ * 鑾峰彇褰撳墠娴佺▼鐘舵��
+ *
+ * @param taskId 浠诲姟id
+ * @return 鐘舵��
+ */
+ String getBusinessStatusByTaskId(Long taskId);
+
+ /**
+ * 鑾峰彇褰撳墠娴佺▼鐘舵��
+ *
+ * @param businessId 涓氬姟id
+ * @return 鐘舵��
+ */
+ String getBusinessStatus(String businessId);
+
+ /**
+ * 璁剧疆娴佺▼鍙橀噺
+ *
+ * @param instanceId 娴佺▼瀹炰緥id
+ * @param variable 娴佺▼鍙橀噺
+ */
+ void setVariable(Long instanceId, Map<String, Object> variable);
+
+ /**
+ * 鑾峰彇娴佺▼鍙橀噺
+ *
+ * @param instanceId 娴佺▼瀹炰緥id
+ */
+ Map<String, Object> instanceVariable(Long instanceId);
+
+ /**
+ * 鎸夌収涓氬姟id鏌ヨ娴佺▼瀹炰緥id
+ *
+ * @param businessId 涓氬姟id
+ * @return 缁撴灉
+ */
+ Long getInstanceIdByBusinessId(String businessId);
+
+ /**
+ * 鏂板绉熸埛娴佺▼瀹氫箟
+ *
+ * @param tenantId 绉熸埛id
+ */
+ void syncDef(String tenantId);
+
+ /**
+ * 鍚姩娴佺▼
+ *
+ * @param startProcess 鍙傛暟
+ * @return 缁撴灉
+ */
+ StartProcessReturnDTO startWorkFlow(StartProcessDTO startProcess);
+
+ /**
+ * 鍔炵悊浠诲姟
+ * 绯荤粺鍚庡彴鍙戣捣瀹℃壒 鏃犵敤鎴蜂俊鎭� 闇�瑕佸拷鐣ユ潈闄�
+ * completeTask.getVariables().put("ignore", true);
+ *
+ * @param completeTask 鍙傛暟
+ * @return 缁撴灉
+ */
+ boolean completeTask(CompleteTaskDTO completeTask);
+
+ /**
+ * 鍔炵悊浠诲姟
+ *
+ * @param taskId 浠诲姟ID
+ * @param message 鍔炵悊鎰忚
+ * @return 缁撴灉
+ */
+ boolean completeTask(Long taskId, String message);
+
+ /**
+ * 鍚姩娴佺▼骞跺姙鐞嗙涓�涓换鍔�
+ *
+ * @param startProcess 鍙傛暟
+ * @return 缁撴灉
+ */
+ boolean startCompleteTask(StartProcessDTO startProcess);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java
new file mode 100755
index 0000000..6c45085
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java
@@ -0,0 +1,378 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.date.DateUnit;
+import cn.hutool.core.date.DateUtil;
+import org.apache.commons.lang3.time.DateFormatUtils;
+import org.dromara.common.core.enums.FormatsType;
+import org.dromara.common.core.exception.ServiceException;
+
+import java.lang.management.ManagementFactory;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 鏃堕棿宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
+ private static final String[] PARSE_PATTERNS = {
+ "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
+ "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
+ "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+
+ @Deprecated
+ private DateUtils() {
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鏃ユ湡鍜屾椂闂�
+ *
+ * @return 褰撳墠鏃ユ湡鍜屾椂闂寸殑 Date 瀵硅薄琛ㄧず
+ */
+ public static Date getNowDate() {
+ return new Date();
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鏃ユ湡鐨勫瓧绗︿覆琛ㄧず锛屾牸寮忎负YYYY-MM-DD
+ *
+ * @return 褰撳墠鏃ユ湡鐨勫瓧绗︿覆琛ㄧず
+ */
+ public static String getDate() {
+ return dateTimeNow(FormatsType.YYYY_MM_DD);
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鏃ユ湡鐨勫瓧绗︿覆琛ㄧず锛屾牸寮忎负yyyyMMdd
+ *
+ * @return 褰撳墠鏃ユ湡鐨勫瓧绗︿覆琛ㄧず
+ */
+ public static String getCurrentDate() {
+ return DateFormatUtils.format(new Date(), FormatsType.YYYYMMDD.getTimeFormat());
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鏃ユ湡鐨勮矾寰勬牸寮忓瓧绗︿覆锛屾牸寮忎负"yyyy/MM/dd"
+ *
+ * @return 褰撳墠鏃ユ湡鐨勮矾寰勬牸寮忓瓧绗︿覆
+ */
+ public static String datePath() {
+ Date now = new Date();
+ return DateFormatUtils.format(now, FormatsType.YYYY_MM_DD_SLASH.getTimeFormat());
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鏃堕棿鐨勫瓧绗︿覆琛ㄧず锛屾牸寮忎负YYYY-MM-DD HH:MM:SS
+ *
+ * @return 褰撳墠鏃堕棿鐨勫瓧绗︿覆琛ㄧず
+ */
+ public static String getTime() {
+ return dateTimeNow(FormatsType.YYYY_MM_DD_HH_MM_SS);
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鏃堕棿鐨勫瓧绗︿覆琛ㄧず锛屾牸寮忎负 "HH:MM:SS"
+ *
+ * @return 褰撳墠鏃堕棿鐨勫瓧绗︿覆琛ㄧず锛屾牸寮忎负 "HH:MM:SS"
+ */
+ public static String getTimeWithHourMinuteSecond() {
+ return dateTimeNow(FormatsType.HH_MM_SS);
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鏃ユ湡鍜屾椂闂寸殑瀛楃涓茶〃绀猴紝鏍煎紡涓篩YYYMMDDHHMMSS
+ *
+ * @return 褰撳墠鏃ユ湡鍜屾椂闂寸殑瀛楃涓茶〃绀�
+ */
+ public static String dateTimeNow() {
+ return dateTimeNow(FormatsType.YYYYMMDDHHMMSS);
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鏃ユ湡鍜屾椂闂寸殑鎸囧畾鏍煎紡鐨勫瓧绗︿覆琛ㄧず
+ *
+ * @param format 鏃ユ湡鏃堕棿鏍煎紡锛屼緥濡�"YYYY-MM-DD HH:MM:SS"
+ * @return 褰撳墠鏃ユ湡鍜屾椂闂寸殑瀛楃涓茶〃绀�
+ */
+ public static String dateTimeNow(final FormatsType format) {
+ return parseDateToStr(format, new Date());
+ }
+
+ /**
+ * 灏嗘寚瀹氭棩鏈熸牸寮忓寲涓� YYYY-MM-DD 鏍煎紡鐨勫瓧绗︿覆
+ *
+ * @param date 瑕佹牸寮忓寲鐨勬棩鏈熷璞�
+ * @return 鏍煎紡鍖栧悗鐨勬棩鏈熷瓧绗︿覆
+ */
+ public static String formatDate(final Date date) {
+ return parseDateToStr(FormatsType.YYYY_MM_DD, date);
+ }
+
+ /**
+ * 灏嗘寚瀹氭棩鏈熸牸寮忓寲涓� YYYY-MM-DD HH:MM:SS 鏍煎紡鐨勫瓧绗︿覆
+ *
+ * @param date 瑕佹牸寮忓寲鐨勬棩鏈熷璞�
+ * @return 鏍煎紡鍖栧悗鐨勬棩鏈熸椂闂村瓧绗︿覆
+ */
+ public static String formatDateTime(final Date date) {
+ return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, date);
+ }
+
+ /**
+ * 灏嗘寚瀹氭棩鏈熸寜鐓ф寚瀹氭牸寮忚繘琛屾牸寮忓寲
+ *
+ * @param format 瑕佷娇鐢ㄧ殑鏃ユ湡鏃堕棿鏍煎紡锛屼緥濡�"YYYY-MM-DD HH:MM:SS"
+ * @param date 瑕佹牸寮忓寲鐨勬棩鏈熷璞�
+ * @return 鏍煎紡鍖栧悗鐨勬棩鏈熸椂闂村瓧绗︿覆
+ */
+ public static String parseDateToStr(final FormatsType format, final Date date) {
+ return new SimpleDateFormat(format.getTimeFormat()).format(date);
+ }
+
+ /**
+ * 灏嗘寚瀹氭牸寮忕殑鏃ユ湡鏃堕棿瀛楃涓茶浆鎹负 Date 瀵硅薄
+ *
+ * @param format 瑕佽В鏋愮殑鏃ユ湡鏃堕棿鏍煎紡锛屼緥濡�"YYYY-MM-DD HH:MM:SS"
+ * @param ts 瑕佽В鏋愮殑鏃ユ湡鏃堕棿瀛楃涓�
+ * @return 瑙f瀽鍚庣殑 Date 瀵硅薄
+ * @throws RuntimeException 濡傛灉瑙f瀽杩囩▼涓彂鐢熷紓甯�
+ */
+ public static Date parseDateTime(final FormatsType format, final String ts) {
+ try {
+ return new SimpleDateFormat(format.getTimeFormat()).parse(ts);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 灏嗗璞¤浆鎹负鏃ユ湡瀵硅薄
+ *
+ * @param str 瑕佽浆鎹㈢殑瀵硅薄锛岄�氬父鏄瓧绗︿覆
+ * @return 杞崲鍚庣殑鏃ユ湡瀵硅薄锛屽鏋滆浆鎹㈠け璐ユ垨杈撳叆涓簄ull锛屽垯杩斿洖null
+ */
+ public static Date parseDate(Object str) {
+ if (str == null) {
+ return null;
+ }
+ try {
+ return parseDate(str.toString(), PARSE_PATTERNS);
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+
+ /**
+ * 鑾峰彇鏈嶅姟鍣ㄥ惎鍔ㄦ椂闂�
+ *
+ * @return 鏈嶅姟鍣ㄥ惎鍔ㄦ椂闂寸殑 Date 瀵硅薄琛ㄧず
+ */
+ public static Date getServerStartDate() {
+ long time = ManagementFactory.getRuntimeMXBean().getStartTime();
+ return new Date(time);
+ }
+
+ /**
+ * 璁$畻涓や釜鏃堕棿涔嬮棿鐨勬椂闂村樊锛屽苟浠ユ寚瀹氬崟浣嶈繑鍥烇紙缁濆鍊硷級
+ *
+ * @param start 璧峰鏃堕棿
+ * @param end 缁撴潫鏃堕棿
+ * @param unit 鎵�闇�杩斿洖鐨勬椂闂村崟浣嶏紙DAYS銆丠OURS銆丮INUTES銆丼ECONDS銆丮ILLISECONDS銆丮ICROSECONDS銆丯ANOSECONDS锛�
+ * @return 鏃堕棿宸殑缁濆鍊硷紝浠ユ寚瀹氬崟浣嶈〃绀�
+ */
+ public static long difference(Date start, Date end, TimeUnit unit) {
+ // 璁$畻鏃堕棿宸紝鍗曚綅涓烘绉掞紝鍙栫粷瀵瑰�奸伩鍏嶈礋鏁�
+ long diffInMillis = Math.abs(end.getTime() - start.getTime());
+
+ // 鏍规嵁鐩爣鍗曚綅杞崲鏃堕棿宸�
+ return switch (unit) {
+ case DAYS -> diffInMillis / TimeUnit.DAYS.toMillis(1);
+ case HOURS -> diffInMillis / TimeUnit.HOURS.toMillis(1);
+ case MINUTES -> diffInMillis / TimeUnit.MINUTES.toMillis(1);
+ case SECONDS -> diffInMillis / TimeUnit.SECONDS.toMillis(1);
+ case MILLISECONDS -> diffInMillis;
+ case MICROSECONDS -> TimeUnit.MILLISECONDS.toMicros(diffInMillis);
+ case NANOSECONDS -> TimeUnit.MILLISECONDS.toNanos(diffInMillis);
+ };
+ }
+
+ /**
+ * 璁$畻涓や釜鏃ユ湡涔嬮棿鐨勬椂闂村樊锛屽苟浠ュぉ銆佸皬鏃跺拰鍒嗛挓鐨勬牸寮忚繑鍥�
+ *
+ * @param endDate 缁撴潫鏃ユ湡
+ * @param nowDate 褰撳墠鏃ユ湡
+ * @return 琛ㄧず鏃堕棿宸殑瀛楃涓诧紝鏍煎紡涓�"澶� 灏忔椂 鍒嗛挓"
+ */
+ public static String getDatePoor(Date endDate, Date nowDate) {
+ long diffInMillis = endDate.getTime() - nowDate.getTime();
+ long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
+ long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
+ long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
+ return String.format("%d澶� %d灏忔椂 %d鍒嗛挓", day, hour, min);
+ }
+
+ /**
+ * 璁$畻涓や釜鏃堕棿鐐圭殑宸�硷紙澶┿�佸皬鏃躲�佸垎閽熴�佺锛夛紝褰撳�间负0鏃朵笉鏄剧ず璇ュ崟浣�
+ *
+ * @param endDate 缁撴潫鏃堕棿
+ * @param nowDate 褰撳墠鏃堕棿
+ * @return 鏃堕棿宸瓧绗︿覆锛屾牸寮忎负 "x澶� x灏忔椂 x鍒嗛挓 x绉�"锛岃嫢涓� 0 鍒欎笉鏄剧ず
+ */
+ public static String getTimeDifference(Date endDate, Date nowDate) {
+ long diffInMillis = endDate.getTime() - nowDate.getTime();
+ long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
+ long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
+ long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
+ long sec = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60;
+ // 鏋勫缓鏃堕棿宸瓧绗︿覆锛屾潯浠舵槸鍊间笉涓�0鎵嶆樉绀�
+ StringBuilder result = new StringBuilder();
+ if (day > 0) {
+ result.append(String.format("%d澶� ", day));
+ }
+ if (hour > 0) {
+ result.append(String.format("%d灏忔椂 ", hour));
+ }
+ if (min > 0) {
+ result.append(String.format("%d鍒嗛挓 ", min));
+ }
+ if (sec > 0) {
+ result.append(String.format("%d绉�", sec));
+ }
+ return result.length() > 0 ? result.toString().trim() : "0绉�";
+ }
+
+ /**
+ * 灏� LocalDateTime 瀵硅薄杞崲涓� Date 瀵硅薄
+ *
+ * @param temporalAccessor 瑕佽浆鎹㈢殑 LocalDateTime 瀵硅薄
+ * @return 杞崲鍚庣殑 Date 瀵硅薄
+ */
+ public static Date toDate(LocalDateTime temporalAccessor) {
+ ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
+ return Date.from(zdt.toInstant());
+ }
+
+ /**
+ * 灏� LocalDate 瀵硅薄杞崲涓� Date 瀵硅薄
+ *
+ * @param temporalAccessor 瑕佽浆鎹㈢殑 LocalDate 瀵硅薄
+ * @return 杞崲鍚庣殑 Date 瀵硅薄
+ */
+ public static Date toDate(LocalDate temporalAccessor) {
+ LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
+ ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
+ return Date.from(zdt.toInstant());
+ }
+
+ /**
+ * 鏍¢獙鏃ユ湡鑼冨洿
+ *
+ * @param startDate 寮�濮嬫棩鏈�
+ * @param endDate 缁撴潫鏃ユ湡
+ * @param maxValue 鏈�澶ф椂闂磋法搴︾殑闄愬埗鍊�
+ * @param unit 鏃堕棿璺ㄥ害鐨勫崟浣嶏紝鍙�夋嫨 "DAYS"銆�"HOURS" 鎴� "MINUTES"
+ */
+ public static void validateDateRange(Date startDate, Date endDate, int maxValue, TimeUnit unit) {
+ // 鏍¢獙缁撴潫鏃ユ湡涓嶈兘鏃╀簬寮�濮嬫棩鏈�
+ if (endDate.before(startDate)) {
+ throw new ServiceException("缁撴潫鏃ユ湡涓嶈兘鏃╀簬寮�濮嬫棩鏈�");
+ }
+
+ // 璁$畻鏃堕棿璺ㄥ害
+ long diffInMillis = endDate.getTime() - startDate.getTime();
+
+ // 鏍规嵁鍗曚綅杞崲鏃堕棿璺ㄥ害
+ long diff = switch (unit) {
+ case DAYS -> TimeUnit.MILLISECONDS.toDays(diffInMillis);
+ case HOURS -> TimeUnit.MILLISECONDS.toHours(diffInMillis);
+ case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffInMillis);
+ default -> throw new IllegalArgumentException("涓嶆敮鎸佺殑鏃堕棿鍗曚綅");
+ };
+
+ // 鏍¢獙鏃堕棿璺ㄥ害涓嶈秴杩囨渶澶ч檺鍒�
+ if (diff > maxValue) {
+ throw new ServiceException("鏈�澶ф椂闂磋法搴︿负 {} {}", maxValue, unit.toString().toLowerCase());
+ }
+ }
+
+ /**
+ * 鏍规嵁鎸囧畾鏃ユ湡鏃堕棿鑾峰彇鏃堕棿娈碉紙鍑屾櫒 / 涓婂崍 / 涓崍 / 涓嬪崍 / 鏅氫笂锛�
+ *
+ * @param date 鏃ユ湡鏃堕棿
+ * @return 鏃堕棿娈垫弿杩�
+ */
+ public static String getTodayHour(Date date) {
+ int hour = DateUtil.hour(date, true);
+ if (hour <= 6) {
+ return "鍑屾櫒";
+ } else if (hour < 12) {
+ return "涓婂崍";
+ } else if (hour == 12) {
+ return "涓崍";
+ } else if (hour <= 18) {
+ return "涓嬪崍";
+ } else {
+ return "鏅氫笂";
+ }
+ }
+
+ /**
+ * 灏嗘棩鏈熸牸寮忓寲涓轰豢寰俊鐨勫弸濂芥椂闂�
+ * <p>
+ * 瑙勫垯璇存槑锛�
+ * 1. 鏈潵鏃堕棿锛歽yyy-MM-dd HH:mm
+ * 2. 浠婂ぉ锛�
+ * - 1 鍒嗛挓鍐咃細鍒氬垰
+ * - 1 灏忔椂鍐咃細X 鍒嗛挓鍓�
+ * - 瓒呰繃 1 灏忔椂锛氬噷鏅�/涓婂崍/涓崍/涓嬪崍/鏅氫笂 HH:mm
+ * 3. 鏄ㄥぉ锛氭槰澶� HH:mm
+ * 4. 鏈懆锛氬懆X HH:mm
+ * 5. 浠婂勾鍐咃細MM-dd HH:mm
+ * 6. 闈炰粖骞达細yyyy-MM-dd HH:mm
+ *
+ * @param date 鏃ユ湡鏃堕棿
+ * @return 鏍煎紡鍖栧悗鐨勬椂闂存弿杩�
+ */
+ public static String formatFriendlyTime(Date date) {
+ if (date == null) {
+ return "";
+ }
+ Date now = DateUtil.date();
+
+ // 鏈潵鏃堕棿鎴栭潪浠婂勾
+ if (date.after(now) || DateUtil.year(date) != DateUtil.year(now)) {
+ return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM, date);
+ }
+
+ // 浠婂ぉ
+ if (DateUtil.isSameDay(date, now)) {
+ long minutes = DateUtil.between(date, now, DateUnit.MINUTE);
+ if (minutes < 1) {
+ return "鍒氬垰";
+ }
+ if (minutes < 60) {
+ return minutes + "鍒嗛挓鍓�";
+ }
+ return getTodayHour(date) + " " + DateUtil.format(date, "HH:mm");
+ }
+
+ // 鏄ㄥぉ
+ if (DateUtil.isSameDay(date, DateUtil.yesterday())) {
+ return "鏄ㄥぉ " + DateUtil.format(date, "HH:mm");
+ }
+
+ // 鏈懆
+ if (DateUtil.isSameWeek(date, now, true)) {
+ return DateUtil.dayOfWeekEnum(date).toChinese("鍛�")
+ + " " + DateUtil.format(date, "HH:mm");
+ }
+
+ // 浠婂勾鍐呭叾瀹冩椂闂�
+ return DateUtil.format(date, "MM-dd HH:mm");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DesensitizedUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DesensitizedUtils.java
new file mode 100755
index 0000000..8a2ffcf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DesensitizedUtils.java
@@ -0,0 +1,87 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.util.DesensitizedUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * 鑴辨晱宸ュ叿绫�
+ *
+ * @author AprilWind
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class DesensitizedUtils extends DesensitizedUtil {
+
+ /**
+ * 鐏垫椿鑴辨晱鏂规硶
+ *
+ * @param value 鍘熷瀛楃涓�
+ * @param prefixVisible 鍓嶉潰鍙闀垮害
+ * @param suffixVisible 鍚庨潰鍙闀垮害
+ * @param maskLength 涓棿鎺╃爜闀垮害锛堝浐瀹氭樉绀哄灏� *锛屽鏋滄�婚暱搴︿笉瓒冲垯鑷姩缂╁噺锛�
+ * @return 鑴辨晱鍚庡瓧绗︿覆
+ */
+ public static String mask(String value, int prefixVisible, int suffixVisible, int maskLength) {
+ if (StrUtil.isBlank(value)) {
+ return value;
+ }
+
+ int len = value.length();
+ int prefixMaskLimit = prefixVisible + maskLength;
+ int fullLimit = prefixMaskLimit + suffixVisible;
+
+ // 瑙勫垯 1锛氶暱搴� <= 涓棿鎺╃爜闀垮害 鈫� 鍏ㄦ帺鐮�
+ if (len <= maskLength) {
+ return StrUtil.repeat('*', len);
+ }
+ String mask = StrUtil.repeat('*', maskLength);
+
+ // 瑙勫垯 2锛氶暱搴� <= 鍓嶇紑 + 涓棿鎺╃爜
+ if (len <= prefixMaskLimit) {
+ return value.substring(0, len - maskLength) + mask;
+ }
+
+ String prefix = value.substring(0, prefixVisible);
+
+ // 瑙勫垯 3锛氶暱搴� <= 鍓嶇紑 + 涓棿鎺╃爜 + 鍚庣紑
+ if (len <= fullLimit) {
+ int suffixLen = len - prefixMaskLimit;
+ return prefix + mask + value.substring(len - suffixLen);
+ }
+
+ // 瑙勫垯 4锛氭爣鍑嗗舰鎬�
+ return prefix + mask + value.substring(len - suffixVisible);
+ }
+
+ /**
+ * 楂樺畨鍏ㄧ骇鍒劚鏁忔柟娉曪紙Token / 绉侀挜锛�
+ *
+ * @param value 鍘熷瀛楃涓�
+ * @param prefixVisible 鍓嶉潰鍙闀垮害锛堟帹鑽�0~4锛�
+ * @param suffixVisible 鍚庨潰鍙闀垮害锛堟帹鑽�0~4锛�
+ * @return 鑴辨晱鍚庡瓧绗︿覆
+ */
+ public static String maskHighSecurity(String value, int prefixVisible, int suffixVisible) {
+ if (StrUtil.isBlank(value)) {
+ return value;
+ }
+ int len = value.length();
+
+ // 瑙勫垯1锛氶暱搴� <= 鍓嶇紑鍙闀垮害 鈫� 鍏ㄩ儴鎺╃爜
+ if (len <= prefixVisible) {
+ return StrUtil.repeat('*', len);
+ }
+
+ // 瑙勫垯2锛氶暱搴� <= 鍓嶇紑 + 鍚庣紑鍙闀垮害 鈫� 浼樺厛鎺╃爜鍚庨潰
+ if (len <= prefixVisible + suffixVisible) {
+ return value.substring(0, len - prefixVisible) + StrUtil.repeat('*', prefixVisible);
+ }
+
+ // 瑙勫垯3锛氭爣鍑嗗舰鎬� 鈫� 鍓嶅悗鍙锛屼腑闂村叏閮ㄦ帺鐮�
+ return value.substring(0, prefixVisible)
+ + StrUtil.repeat('*', len - prefixVisible - suffixVisible)
+ + value.substring(len - suffixVisible);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MapstructUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MapstructUtils.java
new file mode 100755
index 0000000..b6acff7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MapstructUtils.java
@@ -0,0 +1,93 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import io.github.linpeilie.Converter;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Mapstruct 宸ュ叿绫�
+ * <p>鍙傝�冩枃妗o細<a href="https://mapstruct.plus/introduction/quick-start.html">mapstruct-plus</a></p>
+ *
+ *
+ * @author Michelle.Chung
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MapstructUtils {
+
+ private final static Converter CONVERTER = SpringUtils.getBean(Converter.class);
+
+ /**
+ * 灏� T 绫诲瀷瀵硅薄锛岃浆鎹负 desc 绫诲瀷鐨勫璞″苟杩斿洖
+ *
+ * @param source 鏁版嵁鏉ユ簮瀹炰綋
+ * @param desc 鎻忚堪瀵硅薄 杞崲鍚庣殑瀵硅薄
+ * @return desc
+ */
+ public static <T, V> V convert(T source, Class<V> desc) {
+ if (ObjectUtil.isNull(source)) {
+ return null;
+ }
+ if (ObjectUtil.isNull(desc)) {
+ return null;
+ }
+ return CONVERTER.convert(source, desc);
+ }
+
+ /**
+ * 灏� T 绫诲瀷瀵硅薄锛屾寜鐓ч厤缃殑鏄犲皠瀛楁瑙勫垯锛岀粰 desc 绫诲瀷鐨勫璞¤祴鍊煎苟杩斿洖 desc 瀵硅薄
+ *
+ * @param source 鏁版嵁鏉ユ簮瀹炰綋
+ * @param desc 杞崲鍚庣殑瀵硅薄
+ * @return desc
+ */
+ public static <T, V> V convert(T source, V desc) {
+ if (ObjectUtil.isNull(source)) {
+ return null;
+ }
+ if (ObjectUtil.isNull(desc)) {
+ return null;
+ }
+ return CONVERTER.convert(source, desc);
+ }
+
+ /**
+ * 灏� T 绫诲瀷鐨勯泦鍚堬紝杞崲涓� desc 绫诲瀷鐨勯泦鍚堝苟杩斿洖
+ *
+ * @param sourceList 鏁版嵁鏉ユ簮瀹炰綋鍒楄〃
+ * @param desc 鎻忚堪瀵硅薄 杞崲鍚庣殑瀵硅薄
+ * @return desc
+ */
+ public static <T, V> List<V> convert(List<T> sourceList, Class<V> desc) {
+ if (ObjectUtil.isNull(sourceList)) {
+ return null;
+ }
+ if (CollUtil.isEmpty(sourceList)) {
+ return CollUtil.newArrayList();
+ }
+ return CONVERTER.convert(sourceList, desc);
+ }
+
+ /**
+ * 灏� Map 杞崲涓� beanClass 绫诲瀷鐨勯泦鍚堝苟杩斿洖
+ *
+ * @param map 鏁版嵁鏉ユ簮
+ * @param beanClass bean绫�
+ * @return bean瀵硅薄
+ */
+ public static <T> T convert(Map<String, Object> map, Class<T> beanClass) {
+ if (MapUtil.isEmpty(map)) {
+ return null;
+ }
+ if (ObjectUtil.isNull(beanClass)) {
+ return null;
+ }
+ return CONVERTER.convert(map, beanClass);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MessageUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MessageUtils.java
new file mode 100755
index 0000000..48dfc08
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MessageUtils.java
@@ -0,0 +1,33 @@
+package org.dromara.common.core.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.context.MessageSource;
+import org.springframework.context.NoSuchMessageException;
+import org.springframework.context.i18n.LocaleContextHolder;
+
+/**
+ * 鑾峰彇i18n璧勬簮鏂囦欢
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MessageUtils {
+
+ private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class);
+
+ /**
+ * 鏍规嵁娑堟伅閿拰鍙傛暟 鑾峰彇娑堟伅 濮旀墭缁檚pring messageSource
+ *
+ * @param code 娑堟伅閿�
+ * @param args 鍙傛暟
+ * @return 鑾峰彇鍥介檯鍖栫炕璇戝��
+ */
+ public static String message(String code, Object... args) {
+ try {
+ return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
+ } catch (NoSuchMessageException e) {
+ return code;
+ }
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/NetUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/NetUtils.java
new file mode 100755
index 0000000..72fdf40
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/NetUtils.java
@@ -0,0 +1,84 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.lang.PatternPool;
+import cn.hutool.core.net.NetUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.utils.regex.RegexUtils;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * 澧炲己缃戠粶鐩稿叧宸ュ叿绫�
+ *
+ * @author 绉嬭緸鏈瘨
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class NetUtils extends NetUtil {
+
+ /**
+ * 鍒ゆ柇鏄惁涓篒Pv6鍦板潃
+ *
+ * @param ip IP鍦板潃
+ * @return 鏄惁涓篒Pv6鍦板潃
+ */
+ public static boolean isIPv6(String ip) {
+ try {
+ // 鍒ゆ柇鏄惁涓篒Pv6鍦板潃
+ return InetAddress.getByName(ip) instanceof Inet6Address;
+ } catch (UnknownHostException e) {
+ return false;
+ }
+ }
+
+ /**
+ * 鍒ゆ柇IPv6鍦板潃鏄惁涓哄唴缃戝湴鍧�
+ * <br><br>
+ * 浠ヤ笅鍦板潃灏嗗綊绫讳负鏈湴鍦板潃锛屽鏈変笟鍔″満鏅湁闇�瑕侊紝璇锋牴鎹渶姹傝嚜琛屽鐞嗭細
+ * <pre>
+ * 閫氶厤绗﹀湴鍧� 0:0:0:0:0:0:0:0
+ * 閾捐矾鏈湴鍦板潃 fe80::/10
+ * 鍞竴鏈湴鍦板潃 fec0::/10
+ * 鐜洖鍦板潃 ::1
+ * </pre>
+ *
+ * @param ip IP鍦板潃
+ * @return 鏄惁涓哄唴缃戝湴鍧�
+ */
+ public static boolean isInnerIPv6(String ip) {
+ try {
+ // 鍒ゆ柇鏄惁涓篒Pv6鍦板潃
+ if (InetAddress.getByName(ip) instanceof Inet6Address inet6Address) {
+ // isAnyLocalAddress 鍒ゆ柇鏄惁涓洪�氶厤绗﹀湴鍧�锛岄�氬父涓嶄細灏嗗叾瑙嗕负鍐呯綉鍦板潃锛屾牴鎹笟鍔″満鏅嚜琛屽鐞嗗垽鏂�
+ // isLinkLocalAddress 鍒ゆ柇鏄惁涓洪摼璺湰鍦板湴鍧�锛岄�氬父涓嶇畻鍐呯綉鍦板潃锛屾槸鍚﹀垝鍒嗗綊灞炰簬鍐呯綉闇�瑕佹牴鎹笟鍔″満鏅嚜琛屽鐞嗗垽鏂�
+ // isLoopbackAddress 鍒ゆ柇鏄惁涓虹幆鍥炲湴鍧�锛屼笌IPv4鐨� 127.0.0.1 鍚岀悊锛岀敤浜庤〃绀烘湰鏈�
+ // isSiteLocalAddress 鍒ゆ柇鏄惁涓烘湰鍦扮珯鐐瑰湴鍧�锛孖Pv6鍞竴鏈湴鍦板潃锛圲nique Local Addresses锛岀畝绉癠LA锛�
+ if (inet6Address.isAnyLocalAddress()
+ || inet6Address.isLinkLocalAddress()
+ || inet6Address.isLoopbackAddress()
+ || inet6Address.isSiteLocalAddress()) {
+ return true;
+ }
+ }
+ } catch (UnknownHostException e) {
+ // 娉ㄦ剰锛宨sInnerIPv6鏂规硶鍜宨sIPv6鏂规硶鐨勯�傜敤鑼冨洿涓嶅悓锛屾墍浠ユ澶勪笉鑳藉拷鐣ュ叾寮傚父淇℃伅銆�
+ throw new IllegalArgumentException("Invalid IPv6 address!", e);
+ }
+ return false;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓篒Pv4鍦板潃
+ *
+ * @param ip IP鍦板潃
+ * @return 鏄惁涓篒Pv4鍦板潃
+ */
+ public static boolean isIPv4(String ip) {
+ return RegexUtils.isMatch(PatternPool.IPV4, ip);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ObjectUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ObjectUtils.java
new file mode 100755
index 0000000..93617b0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ObjectUtils.java
@@ -0,0 +1,60 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.function.Function;
+
+/**
+ * 瀵硅薄宸ュ叿绫�
+ *
+ * @author 绉嬭緸鏈瘨
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ObjectUtils extends ObjectUtil {
+
+ /**
+ * 濡傛灉瀵硅薄涓嶄负绌猴紝鍒欒幏鍙栧璞′腑鐨勬煇涓瓧娈� ObjectUtils.notNullGetter(user, User::getName);
+ *
+ * @param obj 瀵硅薄
+ * @param func 鑾峰彇鏂规硶
+ * @return 瀵硅薄瀛楁
+ */
+ public static <T, E> E notNullGetter(T obj, Function<T, E> func) {
+ if (isNotNull(obj) && isNotNull(func)) {
+ return func.apply(obj);
+ }
+ return null;
+ }
+
+ /**
+ * 濡傛灉瀵硅薄涓嶄负绌猴紝鍒欒幏鍙栧璞′腑鐨勬煇涓瓧娈碉紝鍚﹀垯杩斿洖榛樿鍊�
+ *
+ * @param obj 瀵硅薄
+ * @param func 鑾峰彇鏂规硶
+ * @param defaultValue 榛樿鍊�
+ * @return 瀵硅薄瀛楁
+ */
+ public static <T, E> E notNullGetter(T obj, Function<T, E> func, E defaultValue) {
+ if (isNotNull(obj) && isNotNull(func)) {
+ return func.apply(obj);
+ }
+ return defaultValue;
+ }
+
+ /**
+ * 濡傛灉鍊间笉涓虹┖锛屽垯杩斿洖鍊硷紝鍚﹀垯杩斿洖榛樿鍊�
+ *
+ * @param obj 瀵硅薄
+ * @param defaultValue 榛樿鍊�
+ * @return 瀵硅薄瀛楁
+ */
+ public static <T> T notNull(T obj, T defaultValue) {
+ if (isNotNull(obj)) {
+ return obj;
+ }
+ return defaultValue;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java
new file mode 100755
index 0000000..509026f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java
@@ -0,0 +1,289 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.extra.servlet.JakartaServletUtil;
+import cn.hutool.http.HttpStatus;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.util.LinkedCaseInsensitiveMap;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 瀹㈡埛绔伐鍏风被锛屾彁渚涜幏鍙栬姹傚弬鏁般�佸搷搴斿鐞嗐�佸ご閮ㄤ俊鎭瓑甯哥敤鎿嶄綔
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ServletUtils extends JakartaServletUtil {
+
+ /**
+ * 鑾峰彇鎸囧畾鍚嶇О鐨� String 绫诲瀷鐨勮姹傚弬鏁�
+ *
+ * @param name 鍙傛暟鍚�
+ * @return 鍙傛暟鍊�
+ */
+ public static String getParameter(String name) {
+ return getRequest().getParameter(name);
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾鍚嶇О鐨� String 绫诲瀷鐨勮姹傚弬鏁帮紝鑻ュ弬鏁颁笉瀛樺湪锛屽垯杩斿洖榛樿鍊�
+ *
+ * @param name 鍙傛暟鍚�
+ * @param defaultValue 榛樿鍊�
+ * @return 鍙傛暟鍊兼垨榛樿鍊�
+ */
+ public static String getParameter(String name, String defaultValue) {
+ return Convert.toStr(getRequest().getParameter(name), defaultValue);
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾鍚嶇О鐨� Integer 绫诲瀷鐨勮姹傚弬鏁�
+ *
+ * @param name 鍙傛暟鍚�
+ * @return 鍙傛暟鍊�
+ */
+ public static Integer getParameterToInt(String name) {
+ return Convert.toInt(getRequest().getParameter(name));
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾鍚嶇О鐨� Integer 绫诲瀷鐨勮姹傚弬鏁帮紝鑻ュ弬鏁颁笉瀛樺湪锛屽垯杩斿洖榛樿鍊�
+ *
+ * @param name 鍙傛暟鍚�
+ * @param defaultValue 榛樿鍊�
+ * @return 鍙傛暟鍊兼垨榛樿鍊�
+ */
+ public static Integer getParameterToInt(String name, Integer defaultValue) {
+ return Convert.toInt(getRequest().getParameter(name), defaultValue);
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾鍚嶇О鐨� Boolean 绫诲瀷鐨勮姹傚弬鏁�
+ *
+ * @param name 鍙傛暟鍚�
+ * @return 鍙傛暟鍊�
+ */
+ public static Boolean getParameterToBool(String name) {
+ return Convert.toBool(getRequest().getParameter(name));
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾鍚嶇О鐨� Boolean 绫诲瀷鐨勮姹傚弬鏁帮紝鑻ュ弬鏁颁笉瀛樺湪锛屽垯杩斿洖榛樿鍊�
+ *
+ * @param name 鍙傛暟鍚�
+ * @param defaultValue 榛樿鍊�
+ * @return 鍙傛暟鍊兼垨榛樿鍊�
+ */
+ public static Boolean getParameterToBool(String name, Boolean defaultValue) {
+ return Convert.toBool(getRequest().getParameter(name), defaultValue);
+ }
+
+ /**
+ * 鑾峰彇鎵�鏈夎姹傚弬鏁帮紙浠� Map 鐨勫舰寮忚繑鍥烇級
+ *
+ * @param request 璇锋眰瀵硅薄{@link ServletRequest}
+ * @return 璇锋眰鍙傛暟鐨� Map锛岄敭涓哄弬鏁板悕锛屽�间负鍙傛暟鍊兼暟缁�
+ */
+ public static Map<String, String[]> getParams(ServletRequest request) {
+ final Map<String, String[]> map = request.getParameterMap();
+ return Collections.unmodifiableMap(map);
+ }
+
+ /**
+ * 鑾峰彇鎵�鏈夎姹傚弬鏁帮紙浠� Map 鐨勫舰寮忚繑鍥烇紝鍊间负瀛楃涓插舰寮忕殑鎷兼帴锛�
+ *
+ * @param request 璇锋眰瀵硅薄{@link ServletRequest}
+ * @return 璇锋眰鍙傛暟鐨� Map锛岄敭涓哄弬鏁板悕锛屽�间负鎷兼帴鍚庣殑瀛楃涓�
+ */
+ public static Map<String, String> getParamMap(ServletRequest request) {
+ Map<String, String> params = new HashMap<>();
+ for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {
+ params.put(entry.getKey(), StringUtils.joinComma(entry.getValue()));
+ }
+ return params;
+ }
+
+ /**
+ * 鑾峰彇褰撳墠 HTTP 璇锋眰瀵硅薄
+ *
+ * @return 褰撳墠 HTTP 璇锋眰瀵硅薄
+ */
+ public static HttpServletRequest getRequest() {
+ try {
+ return getRequestAttributes().getRequest();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 鑾峰彇褰撳墠 HTTP 鍝嶅簲瀵硅薄
+ *
+ * @return 褰撳墠 HTTP 鍝嶅簲瀵硅薄
+ */
+ public static HttpServletResponse getResponse() {
+ try {
+ return getRequestAttributes().getResponse();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 鑾峰彇褰撳墠璇锋眰鐨� HttpSession 瀵硅薄
+ * <p>
+ * 濡傛灉褰撳墠璇锋眰宸茬粡鍏宠仈浜嗕竴涓細璇濓紙鍗冲凡缁忓瓨鍦ㄦ湁鏁堢殑 session ID锛夛紝
+ * 鍒欒繑鍥炶浼氳瘽瀵硅薄锛涘鏋滄病鏈夊叧鑱斾細璇濓紝鍒欎細鍒涘缓涓�涓柊鐨勪細璇濆璞″苟杩斿洖銆�
+ * <p>
+ * HttpSession 鐢ㄤ簬瀛樺偍浼氳瘽绾у埆鐨勬暟鎹紝濡傜敤鎴风櫥褰曚俊鎭�佽喘鐗╄溅鍐呭绛夛紝
+ * 鍙互鍦ㄥ涓姹備箣闂村叡浜細璇濇暟鎹�
+ *
+ * @return 褰撳墠璇锋眰鐨� HttpSession 瀵硅薄
+ */
+ public static HttpSession getSession() {
+ return getRequest().getSession();
+ }
+
+ /**
+ * 鑾峰彇褰撳墠璇锋眰鐨勮姹傚睘鎬�
+ *
+ * @return {@link ServletRequestAttributes} 璇锋眰灞炴�у璞�
+ */
+ public static ServletRequestAttributes getRequestAttributes() {
+ try {
+ RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+ return (ServletRequestAttributes) attributes;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾璇锋眰澶寸殑鍊硷紝濡傛灉澶撮儴涓虹┖鍒欒繑鍥炵┖瀛楃涓�
+ *
+ * @param request 璇锋眰瀵硅薄
+ * @param name 澶撮儴鍚嶇О
+ * @return 澶撮儴鍊�
+ */
+ public static String getHeader(HttpServletRequest request, String name) {
+ String value = request.getHeader(name);
+ if (StringUtils.isEmpty(value)) {
+ return StringUtils.EMPTY;
+ }
+ return urlDecode(value);
+ }
+
+ /**
+ * 鑾峰彇鎵�鏈夎姹傚ご鐨� Map锛岄敭涓哄ご閮ㄥ悕绉帮紝鍊间负澶撮儴鍊�
+ *
+ * @param request 璇锋眰瀵硅薄
+ * @return 璇锋眰澶寸殑 Map
+ */
+ public static Map<String, String> getHeaders(HttpServletRequest request) {
+ Map<String, String> map = new LinkedCaseInsensitiveMap<>();
+ Enumeration<String> enumeration = request.getHeaderNames();
+ if (enumeration != null) {
+ while (enumeration.hasMoreElements()) {
+ String key = enumeration.nextElement();
+ String value = request.getHeader(key);
+ map.put(key, value);
+ }
+ }
+ return map;
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆娓叉煋鍒板鎴风锛堜互 JSON 鏍煎紡杩斿洖锛�
+ *
+ * @param response 娓叉煋瀵硅薄
+ * @param string 寰呮覆鏌撶殑瀛楃涓�
+ */
+ public static void renderString(HttpServletResponse response, String string) {
+ try {
+ response.setStatus(HttpStatus.HTTP_OK);
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
+ response.getWriter().print(string);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 鍒ゆ柇褰撳墠璇锋眰鏄惁涓� Ajax 寮傛璇锋眰
+ *
+ * @param request 璇锋眰瀵硅薄
+ * @return 鏄惁涓� Ajax 璇锋眰
+ */
+ public static boolean isAjaxRequest(HttpServletRequest request) {
+
+ // 鍒ゆ柇 Accept 澶撮儴鏄惁鍖呭惈 application/json
+ String accept = request.getHeader("accept");
+ if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) {
+ return true;
+ }
+
+ // 鍒ゆ柇 X-Requested-With 澶撮儴鏄惁鍖呭惈 XMLHttpRequest
+ String xRequestedWith = request.getHeader("X-Requested-With");
+ if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) {
+ return true;
+ }
+
+ // 鍒ゆ柇 URI 鍚庣紑鏄惁涓� .json 鎴� .xml
+ String uri = request.getRequestURI();
+ if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) {
+ return true;
+ }
+
+ // 鍒ゆ柇璇锋眰鍙傛暟 __ajax 鏄惁涓� json 鎴� xml
+ String ajax = request.getParameter("__ajax");
+ return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml");
+ }
+
+ /**
+ * 鑾峰彇瀹㈡埛绔� IP 鍦板潃
+ *
+ * @return 瀹㈡埛绔� IP 鍦板潃
+ */
+ public static String getClientIP() {
+ return getClientIP(getRequest());
+ }
+
+ /**
+ * 瀵瑰唴瀹硅繘琛� URL 缂栫爜
+ *
+ * @param str 鍐呭
+ * @return 缂栫爜鍚庣殑鍐呭
+ */
+ public static String urlEncode(String str) {
+ return URLEncoder.encode(str, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * 瀵瑰唴瀹硅繘琛� URL 瑙g爜
+ *
+ * @param str 鍐呭
+ * @return 瑙g爜鍚庣殑鍐呭
+ */
+ public static String urlDecode(String str) {
+ return URLDecoder.decode(str, StandardCharsets.UTF_8);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/SpringUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/SpringUtils.java
new file mode 100755
index 0000000..169c6e2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/SpringUtils.java
@@ -0,0 +1,67 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.extra.spring.SpringUtil;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.boot.autoconfigure.thread.Threading;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+
+/**
+ * spring宸ュ叿绫�
+ *
+ * @author Lion Li
+ */
+@Component
+public final class SpringUtils extends SpringUtil {
+
+ /**
+ * 濡傛灉BeanFactory鍖呭惈涓�涓笌鎵�缁欏悕绉板尮閰嶇殑bean瀹氫箟锛屽垯杩斿洖true
+ */
+ public static boolean containsBean(String name) {
+ return getBeanFactory().containsBean(name);
+ }
+
+ /**
+ * 鍒ゆ柇浠ョ粰瀹氬悕瀛楁敞鍐岀殑bean瀹氫箟鏄竴涓猻ingleton杩樻槸涓�涓猵rototype銆�
+ * 濡傛灉涓庣粰瀹氬悕瀛楃浉搴旂殑bean瀹氫箟娌℃湁琚壘鍒帮紝灏嗕細鎶涘嚭涓�涓紓甯革紙NoSuchBeanDefinitionException锛�
+ */
+ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
+ return getBeanFactory().isSingleton(name);
+ }
+
+ /**
+ * @return Class 娉ㄥ唽瀵硅薄鐨勭被鍨�
+ */
+ public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
+ return getBeanFactory().getType(name);
+ }
+
+ /**
+ * 濡傛灉缁欏畾鐨刡ean鍚嶅瓧鍦╞ean瀹氫箟涓湁鍒悕锛屽垯杩斿洖杩欎簺鍒悕
+ */
+ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
+ return getBeanFactory().getAliases(name);
+ }
+
+ /**
+ * 鑾峰彇aop浠g悊瀵硅薄
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T getAopProxy(T invoker) {
+ return (T) getBean(invoker.getClass());
+ }
+
+
+ /**
+ * 鑾峰彇spring涓婁笅鏂�
+ */
+ public static ApplicationContext context() {
+ return getApplicationContext();
+ }
+
+ public static boolean isVirtual() {
+ return Threading.VIRTUAL.isActive(getBean(Environment.class));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java
new file mode 100755
index 0000000..c5487c0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java
@@ -0,0 +1,328 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * stream 娴佸伐鍏风被
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StreamUtils {
+
+ /**
+ * 灏哻ollection杩囨护
+ *
+ * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+ * @param function 杩囨护鏂规硶
+ * @return 杩囨护鍚庣殑list
+ */
+ public static <E> List<E> filter(Collection<E> collection, Predicate<E> function) {
+ if (CollUtil.isEmpty(collection)) {
+ return CollUtil.newArrayList();
+ }
+ return collection.stream()
+ .filter(function)
+ // 娉ㄦ剰姝ゅ涓嶈浣跨敤 .toList() 鏂拌娉� 鍥犱负杩斿洖鐨勬槸涓嶅彲鍙楲ist 浼氬鑷村簭鍒楀寲闂
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 鎵惧埌娴佷腑婊¤冻鏉′欢鐨勭涓�涓厓绱�
+ *
+ * @param collection 闇�瑕佹煡璇㈢殑闆嗗悎
+ * @param function 杩囨护鏂规硶
+ * @return 鎵惧埌绗﹀悎鏉′欢鐨勭涓�涓厓绱狅紝娌℃湁鍒欒繑鍥� Optional.empty()
+ */
+ public static <E> Optional<E> findFirst(Collection<E> collection, Predicate<E> function) {
+ if (CollUtil.isEmpty(collection)) {
+ return Optional.empty();
+ }
+ return collection.stream()
+ .filter(function)
+ .findFirst();
+ }
+
+ /**
+ * 鎵惧埌娴佷腑婊¤冻鏉′欢鐨勭涓�涓厓绱犲��
+ *
+ * @param collection 闇�瑕佹煡璇㈢殑闆嗗悎
+ * @param function 杩囨护鏂规硶
+ * @return 鎵惧埌绗﹀悎鏉′欢鐨勭涓�涓厓绱狅紝娌℃湁鍒欒繑鍥� null
+ */
+ public static <E> E findFirstValue(Collection<E> collection, Predicate<E> function) {
+ return findFirst(collection,function).orElse(null);
+ }
+
+ /**
+ * 鎵惧埌娴佷腑浠绘剰涓�涓弧瓒虫潯浠剁殑鍏冪礌
+ *
+ * @param collection 闇�瑕佹煡璇㈢殑闆嗗悎
+ * @param function 杩囨护鏂规硶
+ * @return 鎵惧埌绗﹀悎鏉′欢鐨勪换鎰忎竴涓厓绱狅紝娌℃湁鍒欒繑鍥� Optional.empty()
+ */
+ public static <E> Optional<E> findAny(Collection<E> collection, Predicate<E> function) {
+ if (CollUtil.isEmpty(collection)) {
+ return Optional.empty();
+ }
+ return collection.stream()
+ .filter(function)
+ .findAny();
+ }
+
+ /**
+ * 鎵惧埌娴佷腑浠绘剰涓�涓弧瓒虫潯浠剁殑鍏冪礌鍊�
+ *
+ * @param collection 闇�瑕佹煡璇㈢殑闆嗗悎
+ * @param function 杩囨护鏂规硶
+ * @return 鎵惧埌绗﹀悎鏉′欢鐨勪换鎰忎竴涓厓绱狅紝娌℃湁鍒欒繑鍥瀗ull
+ */
+ public static <E> E findAnyValue(Collection<E> collection, Predicate<E> function) {
+ return findAny(collection,function).orElse(null);
+ }
+
+ /**
+ * 灏哻ollection鎷兼帴
+ *
+ * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+ * @param function 鎷兼帴鏂规硶
+ * @return 鎷兼帴鍚庣殑list
+ */
+ public static <E> String join(Collection<E> collection, Function<E, String> function) {
+ return join(collection, function, StringUtils.SEPARATOR);
+ }
+
+ /**
+ * 灏哻ollection鎷兼帴
+ *
+ * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+ * @param function 鎷兼帴鏂规硶
+ * @param delimiter 鎷兼帴绗�
+ * @return 鎷兼帴鍚庣殑list
+ */
+ public static <E> String join(Collection<E> collection, Function<E, String> function, CharSequence delimiter) {
+ if (CollUtil.isEmpty(collection)) {
+ return StringUtils.EMPTY;
+ }
+ return collection.stream()
+ .map(function)
+ .filter(Objects::nonNull)
+ .collect(Collectors.joining(delimiter));
+ }
+
+ /**
+ * 灏哻ollection鎺掑簭
+ *
+ * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+ * @param comparing 鎺掑簭鏂规硶
+ * @return 鎺掑簭鍚庣殑list
+ */
+ public static <E> List<E> sorted(Collection<E> collection, Comparator<E> comparing) {
+ if (CollUtil.isEmpty(collection)) {
+ return CollUtil.newArrayList();
+ }
+ return collection.stream()
+ .filter(Objects::nonNull)
+ .sorted(comparing)
+ // 娉ㄦ剰姝ゅ涓嶈浣跨敤 .toList() 鏂拌娉� 鍥犱负杩斿洖鐨勬槸涓嶅彲鍙楲ist 浼氬鑷村簭鍒楀寲闂
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 灏哻ollection杞寲涓虹被鍨嬩笉鍙樼殑map<br>
+ * <B>{@code Collection<V> ----> Map<K,V>}</B>
+ *
+ * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+ * @param key V绫诲瀷杞寲涓篕绫诲瀷鐨刲ambda鏂规硶
+ * @param <V> collection涓殑娉涘瀷
+ * @param <K> map涓殑key绫诲瀷
+ * @return 杞寲鍚庣殑map
+ */
+ public static <V, K> Map<K, V> toIdentityMap(Collection<V> collection, Function<V, K> key) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection.stream()
+ .filter(Objects::nonNull)
+ .collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
+ }
+
+ /**
+ * 灏咰ollection杞寲涓簃ap(value绫诲瀷涓巆ollection鐨勬硾鍨嬩笉鍚�)<br>
+ * <B>{@code Collection<E> -----> Map<K,V> }</B>
+ *
+ * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+ * @param key E绫诲瀷杞寲涓篕绫诲瀷鐨刲ambda鏂规硶
+ * @param value E绫诲瀷杞寲涓篤绫诲瀷鐨刲ambda鏂规硶
+ * @param <E> collection涓殑娉涘瀷
+ * @param <K> map涓殑key绫诲瀷
+ * @param <V> map涓殑value绫诲瀷
+ * @return 杞寲鍚庣殑map
+ */
+ public static <E, K, V> Map<K, V> toMap(Collection<E> collection, Function<E, K> key, Function<E, V> value) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection.stream()
+ .filter(Objects::nonNull)
+ .collect(Collectors.toMap(key, value, (l, r) -> l));
+ }
+
+ /**
+ * 鑾峰彇 map 涓殑鏁版嵁浣滀负鏂� Map 鐨� value 锛宬ey 涓嶅彉
+ * @param map 闇�瑕佸鐞嗙殑map
+ * @param take 鍙栧�煎嚱鏁�
+ * @param <K> map涓殑key绫诲瀷
+ * @param <E> map涓殑value绫诲瀷
+ * @param <V> 鏂癿ap涓殑value绫诲瀷
+ * @return 鏂扮殑map
+ */
+ public static <K, E, V> Map<K, V> toMap(Map<K, E> map, BiFunction<K, E, V> take) {
+ if (CollUtil.isEmpty(map)) {
+ return MapUtil.newHashMap();
+ }
+ return toMap(map.entrySet(), Map.Entry::getKey, entry -> take.apply(entry.getKey(), entry.getValue()));
+ }
+
+ /**
+ * 灏哻ollection鎸夌収瑙勫垯(姣斿鏈夌浉鍚岀殑鐝骇id)鍒嗙被鎴恗ap<br>
+ * <B>{@code Collection<E> -------> Map<K,List<E>> } </B>
+ *
+ * @param collection 闇�瑕佸垎绫荤殑闆嗗悎
+ * @param key 鍒嗙被鐨勮鍒�
+ * @param <E> collection涓殑娉涘瀷
+ * @param <K> map涓殑key绫诲瀷
+ * @return 鍒嗙被鍚庣殑map
+ */
+ public static <E, K> Map<K, List<E>> groupByKey(Collection<E> collection, Function<E, K> key) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection.stream()
+ .filter(Objects::nonNull)
+ .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
+ }
+
+ /**
+ * 灏哻ollection鎸夌収涓や釜瑙勫垯(姣斿鏈夌浉鍚岀殑骞寸骇id,鐝骇id)鍒嗙被鎴愬弻灞俶ap<br>
+ * <B>{@code Collection<E> ---> Map<T,Map<U,List<E>>> } </B>
+ *
+ * @param collection 闇�瑕佸垎绫荤殑闆嗗悎
+ * @param key1 绗竴涓垎绫荤殑瑙勫垯
+ * @param key2 绗簩涓垎绫荤殑瑙勫垯
+ * @param <E> 闆嗗悎鍏冪礌绫诲瀷
+ * @param <K> 绗竴涓猰ap涓殑key绫诲瀷
+ * @param <U> 绗簩涓猰ap涓殑key绫诲瀷
+ * @return 鍒嗙被鍚庣殑map
+ */
+ public static <E, K, U> Map<K, Map<U, List<E>>> groupBy2Key(Collection<E> collection, Function<E, K> key1, Function<E, U> key2) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection.stream()
+ .filter(Objects::nonNull)
+ .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
+ }
+
+ /**
+ * 灏哻ollection鎸夌収涓や釜瑙勫垯(姣斿鏈夌浉鍚岀殑骞寸骇id,鐝骇id)鍒嗙被鎴愬弻灞俶ap<br>
+ * <B>{@code Collection<E> ---> Map<T,Map<U,E>> } </B>
+ *
+ * @param collection 闇�瑕佸垎绫荤殑闆嗗悎
+ * @param key1 绗竴涓垎绫荤殑瑙勫垯
+ * @param key2 绗簩涓垎绫荤殑瑙勫垯
+ * @param <T> 绗竴涓猰ap涓殑key绫诲瀷
+ * @param <U> 绗簩涓猰ap涓殑key绫诲瀷
+ * @param <E> collection涓殑娉涘瀷
+ * @return 鍒嗙被鍚庣殑map
+ */
+ public static <E, T, U> Map<T, Map<U, E>> group2Map(Collection<E> collection, Function<E, T> key1, Function<E, U> key2) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection.stream()
+ .filter(Objects::nonNull)
+ .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
+ }
+
+ /**
+ * 灏哻ollection杞寲涓篖ist闆嗗悎锛屼絾鏄袱鑰呯殑娉涘瀷涓嶅悓<br>
+ * <B>{@code Collection<E> ------> List<T> } </B>
+ *
+ * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+ * @param function collection涓殑娉涘瀷杞寲涓簂ist娉涘瀷鐨刲ambda琛ㄨ揪寮�
+ * @param <E> collection涓殑娉涘瀷
+ * @param <T> List涓殑娉涘瀷
+ * @return 杞寲鍚庣殑list
+ */
+ public static <E, T> List<T> toList(Collection<E> collection, Function<E, T> function) {
+ if (CollUtil.isEmpty(collection)) {
+ return CollUtil.newArrayList();
+ }
+ return collection.stream()
+ .map(function)
+ .filter(Objects::nonNull)
+ // 娉ㄦ剰姝ゅ涓嶈浣跨敤 .toList() 鏂拌娉� 鍥犱负杩斿洖鐨勬槸涓嶅彲鍙楲ist 浼氬鑷村簭鍒楀寲闂
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 灏哻ollection杞寲涓篠et闆嗗悎锛屼絾鏄袱鑰呯殑娉涘瀷涓嶅悓<br>
+ * <B>{@code Collection<E> ------> Set<T> } </B>
+ *
+ * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+ * @param function collection涓殑娉涘瀷杞寲涓簊et娉涘瀷鐨刲ambda琛ㄨ揪寮�
+ * @param <E> collection涓殑娉涘瀷
+ * @param <T> Set涓殑娉涘瀷
+ * @return 杞寲鍚庣殑Set
+ */
+ public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> function) {
+ if (CollUtil.isEmpty(collection)) {
+ return CollUtil.newHashSet();
+ }
+ return collection.stream()
+ .map(function)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ }
+
+
+ /**
+ * 鍚堝苟涓や釜鐩稿悓key绫诲瀷鐨刴ap
+ *
+ * @param map1 绗竴涓渶瑕佸悎骞剁殑 map
+ * @param map2 绗簩涓渶瑕佸悎骞剁殑 map
+ * @param merge 鍚堝苟鐨刲ambda锛屽皢key value1 value2鍚堝苟鎴愭渶缁堢殑绫诲瀷,娉ㄦ剰value鍙兘涓虹┖鐨勬儏鍐�
+ * @param <K> map涓殑key绫诲瀷
+ * @param <X> 绗竴涓� map鐨剉alue绫诲瀷
+ * @param <Y> 绗簩涓� map鐨剉alue绫诲瀷
+ * @param <V> 鏈�缁坢ap鐨剉alue绫诲瀷
+ * @return 鍚堝苟鍚庣殑map
+ */
+ public static <K, X, Y, V> Map<K, V> merge(Map<K, X> map1, Map<K, Y> map2, BiFunction<X, Y, V> merge) {
+ if (CollUtil.isEmpty(map1) && CollUtil.isEmpty(map2)) {
+ // 濡傛灉涓や釜 map 閮戒负绌猴紝鍒欑洿鎺ヨ繑鍥炵┖鐨� map
+ return MapUtil.newHashMap();
+ } else if (CollUtil.isEmpty(map1)) {
+ // 濡傛灉 map1 涓虹┖锛屽垯鐩存帴澶勭悊杩斿洖 map2
+ return toMap(map2.entrySet(), Map.Entry::getKey, entry -> merge.apply(null, entry.getValue()));
+ } else if (CollUtil.isEmpty(map2)) {
+ // 濡傛灉 map2 涓虹┖锛屽垯鐩存帴澶勭悊杩斿洖 map1
+ return toMap(map1.entrySet(), Map.Entry::getKey, entry -> merge.apply(entry.getValue(), null));
+ }
+ Set<K> keySet = new HashSet<>();
+ keySet.addAll(map1.keySet());
+ keySet.addAll(map2.keySet());
+ return toMap(keySet, key -> key, key -> merge.apply(map1.get(key), map2.get(key)));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java
new file mode 100755
index 0000000..6eac2fc
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java
@@ -0,0 +1,384 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.StrUtil;
+import org.springframework.util.AntPathMatcher;
+
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 瀛楃涓插伐鍏风被
+ *
+ * @author Lion Li
+ */
+public class StringUtils extends org.apache.commons.lang3.StringUtils {
+
+ public static final String SEPARATOR = ",";
+
+ public static final String SLASH = "/";
+
+ @Deprecated
+ private StringUtils() {
+ }
+
+ /**
+ * 鑾峰彇鍙傛暟涓嶄负绌哄��
+ *
+ * @param str defaultValue 瑕佸垽鏂殑value
+ * @return value 杩斿洖鍊�
+ */
+ public static String blankToDefault(String str, String defaultValue) {
+ return StrUtil.blankToDefault(str, defaultValue);
+ }
+
+ /**
+ * * 鍒ゆ柇涓�涓瓧绗︿覆鏄惁涓虹┖涓�
+ *
+ * @param str String
+ * @return true锛氫负绌� false锛氶潪绌�
+ */
+ public static boolean isEmpty(String str) {
+ return StrUtil.isEmpty(str);
+ }
+
+ /**
+ * * 鍒ゆ柇涓�涓瓧绗︿覆鏄惁涓洪潪绌轰覆
+ *
+ * @param str String
+ * @return true锛氶潪绌轰覆 false锛氱┖涓�
+ */
+ public static boolean isNotEmpty(String str) {
+ return !isEmpty(str);
+ }
+
+ /**
+ * 鍘荤┖鏍�
+ */
+ public static String trim(String str) {
+ return StrUtil.trim(str);
+ }
+
+ /**
+ * 鎴彇瀛楃涓�
+ *
+ * @param str 瀛楃涓�
+ * @param start 寮�濮�
+ * @return 缁撴灉
+ */
+ public static String substring(final String str, int start) {
+ return substring(str, start, str.length());
+ }
+
+ /**
+ * 鎴彇瀛楃涓�
+ *
+ * @param str 瀛楃涓�
+ * @param start 寮�濮�
+ * @param end 缁撴潫
+ * @return 缁撴灉
+ */
+ public static String substring(final String str, int start, int end) {
+ return StrUtil.sub(str, start, end);
+ }
+
+ /**
+ * 鏍煎紡鍖栨枃鏈�, {} 琛ㄧず鍗犱綅绗�<br>
+ * 姝ゆ柟娉曞彧鏄畝鍗曞皢鍗犱綅绗� {} 鎸夌収椤哄簭鏇挎崲涓哄弬鏁�<br>
+ * 濡傛灉鎯宠緭鍑� {} 浣跨敤 \\杞箟 { 鍗冲彲锛屽鏋滄兂杈撳嚭 {} 涔嬪墠鐨� \ 浣跨敤鍙岃浆涔夌 \\\\ 鍗冲彲<br>
+ * 渚嬶細<br>
+ * 閫氬父浣跨敤锛歠ormat("this is {} for {}", "a", "b") -> this is a for b<br>
+ * 杞箟{}锛� format("this is \\{} for {}", "a", "b") -> this is {} for a<br>
+ * 杞箟\锛� format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
+ *
+ * @param template 鏂囨湰妯℃澘锛岃鏇挎崲鐨勯儴鍒嗙敤 {} 琛ㄧず
+ * @param params 鍙傛暟鍊�
+ * @return 鏍煎紡鍖栧悗鐨勬枃鏈�
+ */
+ public static String format(String template, Object... params) {
+ return StrUtil.format(template, params);
+ }
+
+ /**
+ * 鏄惁涓篽ttp(s)://寮�澶�
+ *
+ * @param link 閾炬帴
+ * @return 缁撴灉
+ */
+ public static boolean ishttp(String link) {
+ return Validator.isUrl(link);
+ }
+
+ /**
+ * 瀛楃涓茶浆set
+ *
+ * @param str 瀛楃涓�
+ * @param sep 鍒嗛殧绗�
+ * @return set闆嗗悎
+ */
+ public static Set<String> str2Set(String str, String sep) {
+ return new HashSet<>(str2List(str, sep, true, false));
+ }
+
+ /**
+ * 瀛楃涓茶浆list
+ *
+ * @param str 瀛楃涓�
+ * @param sep 鍒嗛殧绗�
+ * @param filterBlank 杩囨护绾┖鐧�
+ * @param trim 鍘绘帀棣栧熬绌虹櫧
+ * @return list闆嗗悎
+ */
+ public static List<String> str2List(String str, String sep, boolean filterBlank, boolean trim) {
+ List<String> list = new ArrayList<>();
+ if (isEmpty(str)) {
+ return list;
+ }
+
+ // 杩囨护绌虹櫧瀛楃涓�
+ if (filterBlank && isBlank(str)) {
+ return list;
+ }
+ String[] split = str.split(sep);
+ for (String string : split) {
+ if (filterBlank && isBlank(string)) {
+ continue;
+ }
+ if (trim) {
+ string = trim(string);
+ }
+ list.add(string);
+ }
+
+ return list;
+ }
+
+ /**
+ * 鏌ユ壘鎸囧畾瀛楃涓叉槸鍚﹀寘鍚寚瀹氬瓧绗︿覆鍒楄〃涓殑浠绘剰涓�涓瓧绗︿覆鍚屾椂涓插拷鐣ュぇ灏忓啓
+ *
+ * @param cs 鎸囧畾瀛楃涓�
+ * @param searchCharSequences 闇�瑕佹鏌ョ殑瀛楃涓叉暟缁�
+ * @return 鏄惁鍖呭惈浠绘剰涓�涓瓧绗︿覆
+ */
+ public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) {
+ return StrUtil.containsAnyIgnoreCase(cs, searchCharSequences);
+ }
+
+ /**
+ * 椹煎嘲杞笅鍒掔嚎鍛藉悕
+ */
+ public static String toUnderScoreCase(String str) {
+ return StrUtil.toUnderlineCase(str);
+ }
+
+ /**
+ * 鏄惁鍖呭惈瀛楃涓�
+ *
+ * @param str 楠岃瘉瀛楃涓�
+ * @param strs 瀛楃涓茬粍
+ * @return 鍖呭惈杩斿洖true
+ */
+ public static boolean inStringIgnoreCase(String str, String... strs) {
+ return StrUtil.equalsAnyIgnoreCase(str, strs);
+ }
+
+ /**
+ * 灏嗕笅鍒掔嚎澶у啓鏂瑰紡鍛藉悕鐨勫瓧绗︿覆杞崲涓洪┘宄板紡銆傚鏋滆浆鎹㈠墠鐨勪笅鍒掔嚎澶у啓鏂瑰紡鍛藉悕鐨勫瓧绗︿覆涓虹┖锛屽垯杩斿洖绌哄瓧绗︿覆銆� 渚嬪锛欻ELLO_WORLD->HelloWorld
+ *
+ * @param name 杞崲鍓嶇殑涓嬪垝绾垮ぇ鍐欐柟寮忓懡鍚嶇殑瀛楃涓�
+ * @return 杞崲鍚庣殑椹煎嘲寮忓懡鍚嶇殑瀛楃涓�
+ */
+ public static String convertToCamelCase(String name) {
+ return StrUtil.upperFirst(StrUtil.toCamelCase(name));
+ }
+
+ /**
+ * 椹煎嘲寮忓懡鍚嶆硶 渚嬪锛歶ser_name->userName
+ */
+ public static String toCamelCase(String s) {
+ return StrUtil.toCamelCase(s);
+ }
+
+ /**
+ * 鏌ユ壘鎸囧畾瀛楃涓叉槸鍚﹀尮閰嶆寚瀹氬瓧绗︿覆鍒楄〃涓殑浠绘剰涓�涓瓧绗︿覆
+ *
+ * @param str 鎸囧畾瀛楃涓�
+ * @param strs 闇�瑕佹鏌ョ殑瀛楃涓叉暟缁�
+ * @return 鏄惁鍖归厤
+ */
+ public static boolean matches(String str, List<String> strs) {
+ if (isEmpty(str) || CollUtil.isEmpty(strs)) {
+ return false;
+ }
+ for (String pattern : strs) {
+ if (isMatch(pattern, str)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 鍒ゆ柇url鏄惁涓庤鍒欓厤缃�:
+ * ? 琛ㄧず鍗曚釜瀛楃;
+ * * 琛ㄧず涓�灞傝矾寰勫唴鐨勪换鎰忓瓧绗︿覆锛屼笉鍙法灞傜骇;
+ * ** 琛ㄧず浠绘剰灞傝矾寰�;
+ *
+ * @param pattern 鍖归厤瑙勫垯
+ * @param url 闇�瑕佸尮閰嶇殑url
+ */
+ public static boolean isMatch(String pattern, String url) {
+ AntPathMatcher matcher = new AntPathMatcher();
+ return matcher.match(pattern, url);
+ }
+
+ /**
+ * 鏁板瓧宸﹁竟琛ラ綈0锛屼娇涔嬭揪鍒版寚瀹氶暱搴︺�傛敞鎰忥紝濡傛灉鏁板瓧杞崲涓哄瓧绗︿覆鍚庯紝闀垮害澶т簬size锛屽垯鍙繚鐣� 鏈�鍚巗ize涓瓧绗︺��
+ *
+ * @param num 鏁板瓧瀵硅薄
+ * @param size 瀛楃涓叉寚瀹氶暱搴�
+ * @return 杩斿洖鏁板瓧鐨勫瓧绗︿覆鏍煎紡锛岃瀛楃涓蹭负鎸囧畾闀垮害銆�
+ */
+ public static String padl(final Number num, final int size) {
+ return padl(num.toString(), size, '0');
+ }
+
+ /**
+ * 瀛楃涓插乏琛ラ綈銆傚鏋滃師濮嬪瓧绗︿覆s闀垮害澶т簬size锛屽垯鍙繚鐣欐渶鍚巗ize涓瓧绗︺��
+ *
+ * @param s 鍘熷瀛楃涓�
+ * @param size 瀛楃涓叉寚瀹氶暱搴�
+ * @param c 鐢ㄤ簬琛ラ綈鐨勫瓧绗�
+ * @return 杩斿洖鎸囧畾闀垮害鐨勫瓧绗︿覆锛岀敱鍘熷瓧绗︿覆宸﹁ˉ榻愭垨鎴彇寰楀埌銆�
+ */
+ public static String padl(final String s, final int size, final char c) {
+ final StringBuilder sb = new StringBuilder(size);
+ if (s != null) {
+ final int len = s.length();
+ if (s.length() <= size) {
+ sb.append(Convert.toStr(c).repeat(size - len));
+ sb.append(s);
+ } else {
+ return s.substring(len - size, len);
+ }
+ } else {
+ sb.append(Convert.toStr(c).repeat(Math.max(0, size)));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓�(鍒嗛殧绗﹂粯璁ら�楀彿)
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @return 鍒嗗壊鍚庣殑鏁版嵁鍒楄〃
+ */
+ public static List<String> splitList(String str) {
+ return splitTo(str, Convert::toStr);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓�
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗�
+ * @return 鍒嗗壊鍚庣殑鏁版嵁鍒楄〃
+ */
+ public static List<String> splitList(String str, String separator) {
+ return splitTo(str, separator, Convert::toStr);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓茶嚜瀹氫箟杞崲(鍒嗛殧绗﹂粯璁ら�楀彿)
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param mapper 鑷畾涔夎浆鎹�
+ * @return 鍒嗗壊鍚庣殑鏁版嵁鍒楄〃
+ */
+ public static <T> List<T> splitTo(String str, Function<? super Object, T> mapper) {
+ return splitTo(str, SEPARATOR, mapper);
+ }
+
+ /**
+ * 鍒囧垎瀛楃涓茶嚜瀹氫箟杞崲
+ *
+ * @param str 琚垏鍒嗙殑瀛楃涓�
+ * @param separator 鍒嗛殧绗�
+ * @param mapper 鑷畾涔夎浆鎹�
+ * @return 鍒嗗壊鍚庣殑鏁版嵁鍒楄〃
+ */
+ public static <T> List<T> splitTo(String str, String separator, Function<? super Object, T> mapper) {
+ if (isBlank(str)) {
+ return new ArrayList<>(0);
+ }
+ return StrUtil.split(str, separator)
+ .stream()
+ .filter(Objects::nonNull)
+ .map(mapper)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 涓嶅尯鍒嗗ぇ灏忓啓妫�鏌� CharSequence 鏄惁浠ユ寚瀹氱殑鍓嶇紑寮�澶淬��
+ *
+ * @param str 瑕佹鏌ョ殑 CharSequence 鍙兘涓� null
+ * @param prefixs 瑕佹煡鎵剧殑鍓嶇紑鍙兘涓� null
+ * @return 鏄惁鍖呭惈
+ */
+ public static boolean startWithAnyIgnoreCase(CharSequence str, CharSequence... prefixs) {
+ // 鍒ゆ柇鏄惁鏄互鎸囧畾瀛楃涓插紑澶�
+ for (CharSequence prefix : prefixs) {
+ if (StringUtils.startsWithIgnoreCase(str, prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 灏嗗瓧绗︿覆浠庢簮瀛楃闆嗚浆鎹负鐩爣瀛楃闆�
+ *
+ * @param input 鍘熷瀛楃涓�
+ * @param fromCharset 婧愬瓧绗﹂泦
+ * @param toCharset 鐩爣瀛楃闆�
+ * @return 杞崲鍚庣殑瀛楃涓�
+ */
+ public static String convert(String input, Charset fromCharset, Charset toCharset) {
+ if (isBlank(input)) {
+ return input;
+ }
+ try {
+ // 浠庢簮瀛楃闆嗚幏鍙栧瓧鑺�
+ byte[] bytes = input.getBytes(fromCharset);
+ // 浣跨敤鐩爣瀛楃闆嗚В鐮�
+ return new String(bytes, toCharset);
+ } catch (Exception e) {
+ return input;
+ }
+ }
+ /**
+ * 灏嗗彲杩唬瀵硅薄涓殑鍏冪礌浣跨敤閫楀彿鎷兼帴鎴愬瓧绗︿覆
+ *
+ * @param iterable 鍙凯浠e璞★紝濡� List銆丼et 绛�
+ * @return 鎷兼帴鍚庣殑瀛楃涓�
+ */
+ public static String joinComma(Iterable<?> iterable) {
+ return StringUtils.join(iterable, SEPARATOR);
+ }
+
+ /**
+ * 灏嗘暟缁勪腑鐨勫厓绱犱娇鐢ㄩ�楀彿鎷兼帴鎴愬瓧绗︿覆
+ *
+ * @param array 浠绘剰绫诲瀷鐨勬暟缁�
+ * @return 鎷兼帴鍚庣殑瀛楃涓�
+ */
+ public static String joinComma(Object[] array) {
+ return StringUtils.join(array, SEPARATOR);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java
new file mode 100755
index 0000000..5f60ebf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java
@@ -0,0 +1,123 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.lang.tree.TreeNodeConfig;
+import cn.hutool.core.lang.tree.TreeUtil;
+import cn.hutool.core.lang.tree.parser.NodeParser;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 鎵╁睍 hutool TreeUtil 灏佽绯荤粺鏍戞瀯寤�
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class TreeBuildUtils extends TreeUtil {
+
+ /**
+ * 鏍规嵁鍓嶇瀹氬埗宸紓鍖栧瓧娈�
+ */
+ public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("label");
+
+ /**
+ * 鏋勫缓鏍戝舰缁撴瀯
+ *
+ * @param <T> 杈撳叆鑺傜偣鐨勭被鍨�
+ * @param <K> 鑺傜偣ID鐨勭被鍨�
+ * @param list 鑺傜偣鍒楄〃锛屽叾涓寘鍚簡瑕佹瀯寤烘爲褰㈢粨鏋勭殑鎵�鏈夎妭鐐�
+ * @param nodeParser 瑙f瀽鍣紝鐢ㄤ簬灏嗚緭鍏ヨ妭鐐硅浆鎹负鏍戣妭鐐�
+ * @return 鏋勫缓濂界殑鏍戝舰缁撴瀯鍒楄〃
+ */
+ public static <T, K> List<Tree<K>> build(List<T> list, NodeParser<T, K> nodeParser) {
+ if (CollUtil.isEmpty(list)) {
+ return CollUtil.newArrayList();
+ }
+ K k = ReflectUtils.invokeGetter(list.get(0), "parentId");
+ return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser);
+ }
+
+ /**
+ * 鏋勫缓鏍戝舰缁撴瀯
+ *
+ * @param <T> 杈撳叆鑺傜偣鐨勭被鍨�
+ * @param <K> 鑺傜偣ID鐨勭被鍨�
+ * @param parentId 椤剁骇鑺傜偣
+ * @param list 鑺傜偣鍒楄〃锛屽叾涓寘鍚簡瑕佹瀯寤烘爲褰㈢粨鏋勭殑鎵�鏈夎妭鐐�
+ * @param nodeParser 瑙f瀽鍣紝鐢ㄤ簬灏嗚緭鍏ヨ妭鐐硅浆鎹负鏍戣妭鐐�
+ * @return 鏋勫缓濂界殑鏍戝舰缁撴瀯鍒楄〃
+ */
+ public static <T, K> List<Tree<K>> build(List<T> list, K parentId, NodeParser<T, K> nodeParser) {
+ if (CollUtil.isEmpty(list)) {
+ return CollUtil.newArrayList();
+ }
+ return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser);
+ }
+
+ /**
+ * 鏋勫缓澶氭牴鑺傜偣鐨勬爲缁撴瀯锛堟敮鎸佸涓《绾ц妭鐐癸級
+ *
+ * @param list 鍘熷鏁版嵁鍒楄〃
+ * @param getId 鑾峰彇鑺傜偣 ID 鐨勬柟娉曞紩鐢紝渚嬪锛歯ode -> node.getId()
+ * @param getParentId 鑾峰彇鑺傜偣鐖剁骇 ID 鐨勬柟娉曞紩鐢紝渚嬪锛歯ode -> node.getParentId()
+ * @param parser 鏍戣妭鐐瑰睘鎬ф槧灏勫櫒锛岀敤浜庡皢鍘熷鑺傜偣 T 杞负 Tree 鑺傜偣
+ * @param <T> 鍘熷鏁版嵁绫诲瀷锛堝瀹炰綋绫汇�丏TO 绛夛級
+ * @param <K> 鑺傜偣 ID 绫诲瀷锛堝 Long銆丼tring锛�
+ * @return 鏋勫缓瀹屾垚鐨勬爲褰㈢粨鏋勶紙鍙兘鍖呭惈澶氫釜椤剁骇鏍硅妭鐐癸級
+ */
+ public static <T, K> List<Tree<K>> buildMultiRoot(List<T> list, Function<T, K> getId, Function<T, K> getParentId, NodeParser<T, K> parser) {
+ if (CollUtil.isEmpty(list)) {
+ return CollUtil.newArrayList();
+ }
+
+ Set<K> rootParentIds = StreamUtils.toSet(list, getParentId);
+ rootParentIds.removeAll(StreamUtils.toSet(list, getId));
+
+ // 鏋勫缓姣忎竴涓牴 parentId 涓嬬殑鏍戯紝骞跺悎骞舵垚鏈�缁堢粨鏋滃垪琛�
+ return rootParentIds.stream()
+ .flatMap(rootParentId -> TreeUtil.build(list, rootParentId, parser).stream())
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 鑾峰彇鑺傜偣鍒楄〃涓墍鏈夎妭鐐圭殑鍙跺瓙鑺傜偣
+ *
+ * @param <K> 鑺傜偣ID鐨勭被鍨�
+ * @param nodes 鑺傜偣鍒楄〃
+ * @return 鍖呭惈鎵�鏈夊彾瀛愯妭鐐圭殑鍒楄〃
+ */
+ public static <K> List<Tree<K>> getLeafNodes(List<Tree<K>> nodes) {
+ if (CollUtil.isEmpty(nodes)) {
+ return CollUtil.newArrayList();
+ }
+ return nodes.stream()
+ .flatMap(TreeBuildUtils::extractLeafNodes)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾鑺傜偣涓嬬殑鎵�鏈夊彾瀛愯妭鐐�
+ *
+ * @param <K> 鑺傜偣ID鐨勭被鍨�
+ * @param node 瑕佹煡鎵惧彾瀛愯妭鐐圭殑鏍硅妭鐐�
+ * @return 鍖呭惈鎵�鏈夊彾瀛愯妭鐐圭殑鍒楄〃
+ */
+ private static <K> Stream<Tree<K>> extractLeafNodes(Tree<K> node) {
+ if (!node.hasChild()) {
+ return Stream.of(node);
+ } else {
+ // 閫掑綊璋冪敤锛岃幏鍙栨墍鏈夊瓙鑺傜偣鐨勫彾瀛愯妭鐐�
+ return node.getChildren().stream()
+ .flatMap(TreeBuildUtils::extractLeafNodes);
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ValidatorUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ValidatorUtils.java
new file mode 100755
index 0000000..06b8fd6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ValidatorUtils.java
@@ -0,0 +1,35 @@
+package org.dromara.common.core.utils;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.Validator;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.Set;
+
+/**
+ * Validator 鏍¢獙妗嗘灦宸ュ叿
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ValidatorUtils {
+
+ private static final Validator VALID = SpringUtils.getBean(Validator.class);
+
+ /**
+ * 瀵圭粰瀹氬璞¤繘琛屽弬鏁版牎楠岋紝骞舵牴鎹寚瀹氱殑鏍¢獙缁勮繘琛屾牎楠�
+ *
+ * @param object 瑕佽繘琛屾牎楠岀殑瀵硅薄
+ * @param groups 鏍¢獙缁�
+ * @throws ConstraintViolationException 濡傛灉鏍¢獙涓嶉�氳繃锛屽垯鎶涘嚭鍙傛暟鏍¢獙寮傚父
+ */
+ public static <T> void validate(T object, Class<?>... groups) {
+ Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
+ if (!validate.isEmpty()) {
+ throw new ConstraintViolationException("鍙傛暟鏍¢獙寮傚父", validate);
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java
new file mode 100755
index 0000000..573b207
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java
@@ -0,0 +1,43 @@
+package org.dromara.common.core.utils.file;
+
+import cn.hutool.core.io.FileUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 鏂囦欢澶勭悊宸ュ叿绫�
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class FileUtils extends FileUtil {
+
+ /**
+ * 涓嬭浇鏂囦欢鍚嶉噸鏂扮紪鐮�
+ *
+ * @param response 鍝嶅簲瀵硅薄
+ * @param realFileName 鐪熷疄鏂囦欢鍚�
+ */
+ public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) {
+ String percentEncodedFileName = percentEncode(realFileName);
+ String contentDispositionValue = "attachment; filename=%s;filename*=utf-8''%s".formatted(percentEncodedFileName, percentEncodedFileName);
+ response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
+ response.setHeader("Content-disposition", contentDispositionValue);
+ response.setHeader("download-filename", percentEncodedFileName);
+ }
+
+ /**
+ * 鐧惧垎鍙风紪鐮佸伐鍏锋柟娉�
+ *
+ * @param s 闇�瑕佺櫨鍒嗗彿缂栫爜鐨勫瓧绗︿覆
+ * @return 鐧惧垎鍙风紪鐮佸悗鐨勫瓧绗︿覆
+ */
+ public static String percentEncode(String s) {
+ String encode = URLEncoder.encode(s, StandardCharsets.UTF_8);
+ return encode.replaceAll("\\+", "%20");
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java
new file mode 100755
index 0000000..23fa2cf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java
@@ -0,0 +1,40 @@
+package org.dromara.common.core.utils.file;
+
+/**
+ * 濯掍綋绫诲瀷宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+public class MimeTypeUtils {
+ public static final String IMAGE_PNG = "image/png";
+
+ public static final String IMAGE_JPG = "image/jpg";
+
+ public static final String IMAGE_JPEG = "image/jpeg";
+
+ public static final String IMAGE_BMP = "image/bmp";
+
+ public static final String IMAGE_GIF = "image/gif";
+
+ public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"};
+
+ public static final String[] FLASH_EXTENSION = {"swf", "flv"};
+
+ public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
+ "asf", "rm", "rmvb"};
+
+ public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"};
+
+ public static final String[] DEFAULT_ALLOWED_EXTENSION = {
+ // 鍥剧墖
+ "bmp", "gif", "jpg", "jpeg", "png",
+ // word excel powerpoint
+ "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
+ // 鍘嬬缉鏂囦欢
+ "rar", "zip", "gz", "bz2",
+ // 瑙嗛鏍煎紡
+ "mp4", "avi", "rmvb",
+ // pdf
+ "pdf"};
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java
new file mode 100755
index 0000000..fe36d9c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java
@@ -0,0 +1,43 @@
+package org.dromara.common.core.utils.ip;
+
+import cn.hutool.http.HtmlUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.utils.NetUtils;
+import org.dromara.common.core.utils.StringUtils;
+
+/**
+ * 鑾峰彇鍦板潃绫�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class AddressUtils {
+
+ // 鏈煡IP
+ public static final String UNKNOWN_IP = "XX XX";
+ // 鍐呯綉鍦板潃
+ public static final String LOCAL_ADDRESS = "鍐呯綉IP";
+
+ public static String getRealAddressByIP(String ip) {
+ // 澶勭悊绌轰覆骞惰繃婊TML鏍囩
+ ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
+ // 鍒ゆ柇鏄惁涓篒Pv4
+ boolean isIPv4 = NetUtils.isIPv4(ip);
+ // 鍒ゆ柇鏄惁涓篒Pv6
+ boolean isIPv6 = NetUtils.isIPv6(ip);
+ // 濡傛灉涓嶆槸IPv4鎴朓Pv6锛屽垯杩斿洖鏈煡IP
+ if (!isIPv4 && !isIPv6) {
+ return UNKNOWN_IP;
+ }
+ // 鍐呯綉涓嶆煡璇�
+ if ((isIPv4 && NetUtils.isInnerIP(ip)) || (isIPv6 && NetUtils.isInnerIPv6(ip))) {
+ return LOCAL_ADDRESS;
+ }
+ // Tips锛欼p2Region 鎻愪緵浜嗙簿绠�鐨処Pv6鍦板潃搴擄紝绮剧畝鐨処Pv6鍦板潃搴撳苟涓嶈兘瀹屽叏鏀寔IPv6鍦板潃鐨勬煡璇紝涓斿噯纭害涓婂彲鑳戒細瀛樺湪闂锛屽闇�瑕佸噯纭殑IPv6鍦板潃鏌ヨ锛屽缓璁嚜琛屽疄鐜�
+ return RegionUtils.getRegion(ip);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java
new file mode 100755
index 0000000..5c74a83
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java
@@ -0,0 +1,155 @@
+package org.dromara.common.core.utils.ip;
+
+import cn.hutool.core.io.resource.ResourceUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StringUtils;
+import org.lionsoul.ip2region.service.Config;
+import org.lionsoul.ip2region.service.Ip2Region;
+import org.lionsoul.ip2region.xdb.Util;
+
+import java.io.InputStream;
+import java.time.Duration;
+
+/**
+ * IP鍦板潃琛屾斂鍖哄煙宸ュ叿绫�
+ * 鍙傝�冨湴鍧�锛�<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">ip2region xdb java 鏌ヨ瀹㈡埛绔疄鐜�</a>
+ * xdb鏁版嵁搴撴枃浠朵笅杞斤細<a href="https://gitee.com/lionsoul/ip2region/tree/master/data">ip2region data</a>
+ *
+ * @author 绉嬭緸鏈瘨
+ */
+@Slf4j
+public class RegionUtils {
+
+ // 榛樿IPv4鍦板潃搴撴枃浠惰矾寰�
+ // 涓嬭浇鍦板潃锛歨ttps://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v4.xdb
+ public static final String DEFAULT_IPV4_XDB_PATH = "ip2region_v4.xdb";
+
+ // 榛樿IPv6鍦板潃搴撴枃浠惰矾寰�
+ // 涓嬭浇鍦板潃锛歨ttps://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v6.xdb
+ public static final String DEFAULT_IPV6_XDB_PATH = "ip2region_v6.xdb";
+
+ // 榛樿缂撳瓨鍒囩墖澶у皬涓�15MB锛堜粎閽堝BufferCache鍏ㄩ噺璇诲彇鏈夋晥锛屽鏋滀綘鐨剎db鏁版嵁搴撳緢澶э紝鍚堢悊璁剧疆璇ュ�煎彲浠ユ湁鏁堟彁鍗嘊ufferCache妯″紡涓嬬殑鏌ヨ鏁堢巼锛屽叿浣撳彲浠ユ煡鐪婭p2Region鐨凴EADME锛�
+ // 娉ㄦ剰锛氳缃繃澶х殑鍊煎彲鑳戒細鐢宠鍐呭瓨鏃讹紝鍥犲唴瀛樹笉瓒宠�屽鑷碠OM锛岃鍚堢悊璁剧疆璇ュ�笺��
+ // README锛歨ttps://gitee.com/lionsoul/ip2region/tree/master/binding/java
+ public static final int DEFAULT_CACHE_SLICE_BYTES = 1024 * 1024 * 15;
+
+ // 鏈煡鍦板潃
+ public static final String UNKNOWN_ADDRESS = "鏈煡";
+
+ // Ip2Region鏈嶅姟瀹炰緥
+ private static Ip2Region ip2Region;
+
+ // 鍒濆鍖朓p2Region鏈嶅姟瀹炰緥
+ static {
+ try {
+ // 娉ㄦ剰锛欼p2Region 鐨剎db鏂囦欢鍔犺浇绛栫暐 CachePolicy 鏈変笁绉嶏紝鍒嗗埆鏄細BufferCache锛堝叏閲忚鍙杧db鍒板唴瀛樹腑锛夈�乂IndexCache锛堥粯璁ょ瓥鐣ワ紝鎸夐渶璇诲彇骞剁紦瀛橈級銆丯oCache锛堝疄鏃惰鍙栵級
+ // 鏈」鐩伐鍏蜂娇鐢ㄧ殑 CachePolicy 涓� BufferCache锛孊ufferCache浼氬姞杞芥暣涓獂db鏂囦欢鍒板唴瀛樹腑锛宻etXdbInputStream 浠呮敮鎸� BufferCache 绛栫暐銆�
+ // 鍥犱负鍔犺浇鏁翠釜xdb鏂囦欢浼氳�楄垂闈炲父澶х殑鍐呭瓨锛屽鏋滀綘涓嶅笇鏈涘姞杞芥暣涓獂db鍒板唴瀛樹腑锛屾洿鎺ㄨ崘浣跨敤 VIndexCache 鎴� NoCache锛堝嵆瀹炴椂璇诲彇鏂囦欢锛夌瓥鐣ュ拰 setXdbPath/setXdbFile 鍔犺浇鏂规硶锛堥渶瑕佹敞鎰忕殑涓�鐐癸紝setXdbPath 鍜� setXdbFile 涓嶆敮鎸佽鍙朇lassPath锛堝嵆婧愮爜鍜宺esource鐩綍锛変腑鐨勬枃浠讹級銆�
+ // 涓�鑸�岃█锛屾洿寤鸿鎶妜db鏁版嵁搴撴斁鍒颁竴涓寚瀹氱殑鏂囦欢鐩綍涓紙鍗充笉鎵撳寘杩沯ar鍖呬腑锛夛紝鐒跺悗浣跨敤 VIndexCache + 閰嶅悎SearcherPool鐨勫苟鍙戞睜璇诲彇鏁版嵁锛屾洿鏂逛究闅忔椂鏇存柊xdb鏁版嵁搴�
+
+ InputStream v4InputStream = ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH);
+
+ // IPv4閰嶇疆
+ Config v4Config = Config.custom()
+ .setCachePolicy(Config.BufferCache)
+ //.setXdbFile(v4TempXdb)
+ .setXdbInputStream(v4InputStream)
+ //
+ .setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
+ .asV4();
+
+ // IPv6閰嶇疆
+ Config v6Config = null;
+ InputStream v6XdbInputStream = ResourceUtil.getStreamSafe(DEFAULT_IPV6_XDB_PATH);
+ if (v6XdbInputStream == null) {
+ log.warn("鏈姞杞� IPv6 鍦板潃搴擄細鏈湪绫昏矾寰勪笅鎵惧埌鏂囦欢 {}銆傚綋鍓嶄粎鍚敤 IPv4 鏌ヨ銆傚闇�鍚敤 IPv6锛岃灏� ip2region_v6.xdb 鏀剧疆鍒� resources 鐩綍", DEFAULT_IPV6_XDB_PATH);
+ } else {
+ v6Config = Config.custom()
+ .setCachePolicy(Config.BufferCache)
+ //.setXdbFile(v6TempXdb)
+ .setXdbInputStream(v6XdbInputStream)
+ .setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
+ .asV6();
+ }
+
+ // 鍒濆鍖朓p2Region瀹炰緥
+ RegionUtils.ip2Region = Ip2Region.create(v4Config, v6Config);
+ log.debug("IP宸ュ叿鍒濆鍖栨垚鍔燂紝鍔犺浇IP鍦板潃搴撴暟鎹垚鍔燂紒");
+ } catch (Exception e) {
+ throw new ServiceException("RegionUtils鍒濆鍖栧け璐ワ紝鍘熷洜锛歿}", e.getMessage());
+ }
+ }
+
+ /**
+ * 鏍规嵁IP鍦板潃绂荤嚎鑾峰彇鍩庡競
+ *
+ * @param ipString ip鍦板潃瀛楃涓�
+ */
+ public static String getRegion(String ipString) {
+ try {
+ String region = ip2Region.search(ipString);
+ if (StringUtils.isBlank(region)) {
+ region = UNKNOWN_ADDRESS;
+ }
+ return region;
+ } catch (Exception e) {
+ log.error("IP鍦板潃绂荤嚎鑾峰彇鍩庡競寮傚父 {}", ipString);
+ return UNKNOWN_ADDRESS;
+ }
+
+ }
+
+ /**
+ * 鏍规嵁IP鍦板潃绂荤嚎鑾峰彇鍩庡競
+ *
+ * @param ipBytes ip鍦板潃瀛楄妭鏁扮粍
+ */
+ public static String getRegion(byte[] ipBytes) {
+ try {
+ String region = ip2Region.search(ipBytes);
+ if (StringUtils.isBlank(region)) {
+ region = UNKNOWN_ADDRESS;
+ }
+ return region;
+ } catch (Exception e) {
+ log.error("IP鍦板潃绂荤嚎鑾峰彇鍩庡競寮傚父 {}", Util.ipToString(ipBytes));
+ return UNKNOWN_ADDRESS;
+ }
+ }
+
+ /**
+ * 鍏抽棴Ip2Region鏈嶅姟
+ */
+ public static void close() {
+ if (ip2Region == null) {
+ return;
+ }
+ try {
+ ip2Region.close(10000);
+ } catch (Exception e) {
+ log.error("Ip2Region鏈嶅姟鍏抽棴寮傚父", e);
+ }
+ }
+
+ /**
+ * 鍏抽棴Ip2Region鏈嶅姟
+ *
+ * @param timeout 鍏抽棴瓒呮椂鏃堕棿
+ */
+ public static void close(final Duration timeout) {
+ if (ip2Region == null) {
+ return;
+ }
+ if (timeout == null) {
+ close();
+ return;
+ }
+ try {
+ ip2Region.close(timeout.toMillis());
+ } catch (Exception e) {
+ log.error("Ip2Region鏈嶅姟鍏抽棴寮傚父", e);
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/reflect/ReflectUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/reflect/ReflectUtils.java
new file mode 100755
index 0000000..367e8c9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/reflect/ReflectUtils.java
@@ -0,0 +1,56 @@
+package org.dromara.common.core.utils.reflect;
+
+import cn.hutool.core.util.ReflectUtil;
+import org.dromara.common.core.utils.StringUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.lang.reflect.Method;
+
+/**
+ * 鍙嶅皠宸ュ叿绫�. 鎻愪緵璋冪敤getter/setter鏂规硶, 璁块棶绉佹湁鍙橀噺, 璋冪敤绉佹湁鏂规硶, 鑾峰彇娉涘瀷绫诲瀷Class, 琚獳OP杩囩殑鐪熷疄绫荤瓑宸ュ叿鍑芥暟.
+ *
+ * @author Lion Li
+ */
+@SuppressWarnings("rawtypes")
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ReflectUtils extends ReflectUtil {
+
+ private static final String SETTER_PREFIX = "set";
+
+ private static final String GETTER_PREFIX = "get";
+
+ /**
+ * 璋冪敤Getter鏂规硶.
+ * 鏀寔澶氱骇锛屽锛氬璞″悕.瀵硅薄鍚�.鏂规硶
+ */
+ @SuppressWarnings("unchecked")
+ public static <E> E invokeGetter(Object obj, String propertyName) {
+ Object object = obj;
+ for (String name : StringUtils.split(propertyName, ".")) {
+ String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
+ object = invoke(object, getterMethodName);
+ }
+ return (E) object;
+ }
+
+ /**
+ * 璋冪敤Setter鏂规硶, 浠呭尮閰嶆柟娉曞悕銆�
+ * 鏀寔澶氱骇锛屽锛氬璞″悕.瀵硅薄鍚�.鏂规硶
+ */
+ public static <E> void invokeSetter(Object obj, String propertyName, E value) {
+ Object object = obj;
+ String[] names = StringUtils.split(propertyName, ".");
+ for (int i = 0; i < names.length; i++) {
+ if (i < names.length - 1) {
+ String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
+ object = invoke(object, getterMethodName);
+ } else {
+ String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
+ Method method = getMethodByName(object.getClass(), setterMethodName);
+ invoke(object, method, value);
+ }
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexUtils.java
new file mode 100755
index 0000000..6dde129
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexUtils.java
@@ -0,0 +1,31 @@
+package org.dromara.common.core.utils.regex;
+
+
+import cn.hutool.core.util.ReUtil;
+import org.dromara.common.core.constant.RegexConstants;
+
+/**
+ * 姝e垯鐩稿叧宸ュ叿绫�
+ *
+ * @author Feng
+ */
+public final class RegexUtils extends ReUtil {
+
+ /**
+ * 浠庤緭鍏ュ瓧绗︿覆涓彁鍙栧尮閰嶇殑閮ㄥ垎锛屽鏋滄病鏈夊尮閰嶅垯杩斿洖榛樿鍊�
+ *
+ * @param input 瑕佹彁鍙栫殑杈撳叆瀛楃涓�
+ * @param regex 鐢ㄤ簬鍖归厤鐨勬鍒欒〃杈惧紡锛屽彲浠ヤ娇鐢� {@link RegexConstants} 涓畾涔夌殑甯搁噺
+ * @param defaultInput 濡傛灉娌℃湁鍖归厤鏃惰繑鍥炵殑榛樿鍊�
+ * @return 濡傛灉鎵惧埌鍖归厤鐨勯儴鍒嗭紝鍒欒繑鍥炲尮閰嶇殑閮ㄥ垎锛屽惁鍒欒繑鍥為粯璁ゅ��
+ */
+ public static String extractFromString(String input, String regex, String defaultInput) {
+ try {
+ String str = ReUtil.get(regex, input, 1);
+ return str == null ? defaultInput : str;
+ } catch (Exception e) {
+ return defaultInput;
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexValidator.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexValidator.java
new file mode 100755
index 0000000..c0dda20
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/regex/RegexValidator.java
@@ -0,0 +1,105 @@
+package org.dromara.common.core.utils.regex;
+
+import cn.hutool.core.exceptions.ValidateException;
+import cn.hutool.core.lang.Validator;
+import org.dromara.common.core.factory.RegexPatternPoolFactory;
+
+import java.util.regex.Pattern;
+
+/**
+ * 姝e垯瀛楁鏍¢獙鍣�
+ * 涓昏楠岃瘉瀛楁闈炵┖銆佹槸鍚︿负婊¤冻鎸囧畾鏍煎紡绛�
+ *
+ * @author Feng
+ */
+public class RegexValidator extends Validator {
+
+ /**
+ * 瀛楀吀绫诲瀷蹇呴』浠ュ瓧姣嶅紑澶达紝涓斿彧鑳戒负锛堝皬鍐欏瓧姣嶏紝鏁板瓧锛屼笅婊戠嚎锛�
+ */
+ public static final Pattern DICTIONARY_TYPE = RegexPatternPoolFactory.DICTIONARY_TYPE;
+
+ /**
+ * 韬唤璇佸彿鐮侊紙鍚�6浣嶏級
+ */
+ public static final Pattern ID_CARD_LAST_6 = RegexPatternPoolFactory.ID_CARD_LAST_6;
+
+ /**
+ * QQ鍙风爜
+ */
+ public static final Pattern QQ_NUMBER = RegexPatternPoolFactory.QQ_NUMBER;
+
+ /**
+ * 閭斂缂栫爜
+ */
+ public static final Pattern POSTAL_CODE = RegexPatternPoolFactory.POSTAL_CODE;
+
+ /**
+ * 娉ㄥ唽璐﹀彿
+ */
+ public static final Pattern ACCOUNT = RegexPatternPoolFactory.ACCOUNT;
+
+ /**
+ * 瀵嗙爜锛氬寘鍚嚦灏�8涓瓧绗︼紝鍖呮嫭澶у啓瀛楁瘝銆佸皬鍐欏瓧姣嶃�佹暟瀛楀拰鐗规畩瀛楃
+ */
+ public static final Pattern PASSWORD = RegexPatternPoolFactory.PASSWORD;
+
+ /**
+ * 閫氱敤鐘舵�侊紙0琛ㄧず姝e父锛�1琛ㄧず鍋滅敤锛�
+ */
+ public static final Pattern STATUS = RegexPatternPoolFactory.STATUS;
+
+
+ /**
+ * 妫�鏌ヨ緭鍏ョ殑璐﹀彿鏄惁鍖归厤棰勫畾涔夌殑瑙勫垯
+ *
+ * @param value 瑕侀獙璇佺殑璐﹀彿
+ * @return 濡傛灉璐﹀彿绗﹀悎瑙勫垯锛岃繑鍥� true锛涘惁鍒欙紝杩斿洖 false銆�
+ */
+ public static boolean isAccount(CharSequence value) {
+ return isMatchRegex(ACCOUNT, value);
+ }
+
+ /**
+ * 楠岃瘉杈撳叆鐨勮处鍙锋槸鍚︾鍚堣鍒欙紝濡傛灉涓嶇鍚堬紝鍒欐姏鍑� ValidateException 寮傚父
+ *
+ * @param value 瑕侀獙璇佺殑璐﹀彿
+ * @param errorMsg 楠岃瘉澶辫触鏃舵姏鍑虹殑寮傚父娑堟伅
+ * @param <T> CharSequence 鐨勫瓙绫诲瀷
+ * @return 濡傛灉楠岃瘉閫氳繃锛岃繑鍥炶緭鍏ョ殑璐﹀彿
+ * @throws ValidateException 濡傛灉楠岃瘉澶辫触
+ */
+ public static <T extends CharSequence> T validateAccount(T value, String errorMsg) throws ValidateException {
+ if (!isAccount(value)) {
+ throw new ValidateException(errorMsg);
+ }
+ return value;
+ }
+
+ /**
+ * 妫�鏌ヨ緭鍏ョ殑鐘舵�佹槸鍚﹀尮閰嶉瀹氫箟鐨勮鍒�
+ *
+ * @param value 瑕侀獙璇佺殑鐘舵��
+ * @return 濡傛灉鐘舵�佺鍚堣鍒欙紝杩斿洖 true锛涘惁鍒欙紝杩斿洖 false銆�
+ */
+ public static boolean isStatus(CharSequence value) {
+ return isMatchRegex(STATUS, value);
+ }
+
+ /**
+ * 楠岃瘉杈撳叆鐨勭姸鎬佹槸鍚︾鍚堣鍒欙紝濡傛灉涓嶇鍚堬紝鍒欐姏鍑� ValidateException 寮傚父
+ *
+ * @param value 瑕侀獙璇佺殑鐘舵��
+ * @param errorMsg 楠岃瘉澶辫触鏃舵姏鍑虹殑寮傚父娑堟伅
+ * @param <T> CharSequence 鐨勫瓙绫诲瀷
+ * @return 濡傛灉楠岃瘉閫氳繃锛岃繑鍥炶緭鍏ョ殑鐘舵��
+ * @throws ValidateException 濡傛灉楠岃瘉澶辫触
+ */
+ public static <T extends CharSequence> T validateStatus(T value, String errorMsg) throws ValidateException {
+ if (!isStatus(value)) {
+ throw new ValidateException(errorMsg);
+ }
+ return value;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/sql/SqlUtil.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/sql/SqlUtil.java
new file mode 100755
index 0000000..1020c81
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/sql/SqlUtil.java
@@ -0,0 +1,56 @@
+package org.dromara.common.core.utils.sql;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.StringUtils;
+
+/**
+ * sql鎿嶄綔宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class SqlUtil {
+
+ /**
+ * 瀹氫箟甯哥敤鐨� sql鍏抽敭瀛�
+ */
+ public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
+
+ /**
+ * 浠呮敮鎸佸瓧姣嶃�佹暟瀛椼�佷笅鍒掔嚎銆佺┖鏍笺�侀�楀彿銆佸皬鏁扮偣锛堟敮鎸佸涓瓧娈垫帓搴忥級
+ */
+ public static final String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
+
+ /**
+ * 妫�鏌ュ瓧绗︼紝闃叉娉ㄥ叆缁曡繃
+ */
+ public static String escapeOrderBySql(String value) {
+ if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
+ throw new IllegalArgumentException("鍙傛暟涓嶇鍚堣鑼冿紝涓嶈兘杩涜鏌ヨ");
+ }
+ return value;
+ }
+
+ /**
+ * 楠岃瘉 order by 璇硶鏄惁绗﹀悎瑙勮寖
+ */
+ public static boolean isValidOrderBySql(String value) {
+ return value.matches(SQL_PATTERN);
+ }
+
+ /**
+ * SQL鍏抽敭瀛楁鏌�
+ */
+ public static void filterKeyword(String value) {
+ if (StringUtils.isEmpty(value)) {
+ return;
+ }
+ String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
+ for (String sqlKeyword : sqlKeywords) {
+ if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) {
+ throw new IllegalArgumentException("鍙傛暟瀛樺湪SQL娉ㄥ叆椋庨櫓");
+ }
+ }
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/AddGroup.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/AddGroup.java
new file mode 100755
index 0000000..0275899
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/AddGroup.java
@@ -0,0 +1,9 @@
+package org.dromara.common.core.validate;
+
+/**
+ * 鏍¢獙鍒嗙粍 add
+ *
+ * @author Lion Li
+ */
+public interface AddGroup {
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/EditGroup.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/EditGroup.java
new file mode 100755
index 0000000..77c5040
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/EditGroup.java
@@ -0,0 +1,9 @@
+package org.dromara.common.core.validate;
+
+/**
+ * 鏍¢獙鍒嗙粍 edit
+ *
+ * @author Lion Li
+ */
+public interface EditGroup {
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/QueryGroup.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/QueryGroup.java
new file mode 100755
index 0000000..02a0ac2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/QueryGroup.java
@@ -0,0 +1,9 @@
+package org.dromara.common.core.validate;
+
+/**
+ * 鏍¢獙鍒嗙粍 query
+ *
+ * @author Lion Li
+ */
+public interface QueryGroup {
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/dicts/DictPattern.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/dicts/DictPattern.java
new file mode 100755
index 0000000..73fc4c4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/dicts/DictPattern.java
@@ -0,0 +1,40 @@
+package org.dromara.common.core.validate.dicts;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 瀛楀吀椤规牎楠屾敞瑙�
+ *
+ * @author AprilWind
+ */
+@Constraint(validatedBy = DictPatternValidator.class)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DictPattern {
+
+ /**
+ * 瀛楀吀绫诲瀷锛屽 "sys_user_sex"
+ */
+ String dictType();
+
+ /**
+ * 鍒嗛殧绗�
+ */
+ String separator();
+
+ /**
+ * 榛樿鏍¢獙澶辫触鎻愮ず淇℃伅
+ */
+ String message() default "瀛楀吀鍊兼棤鏁�";
+
+ Class<?>[] groups() default {};
+
+ Class<? extends Payload>[] payload() default {};
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/dicts/DictPatternValidator.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/dicts/DictPatternValidator.java
new file mode 100755
index 0000000..558a343
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/dicts/DictPatternValidator.java
@@ -0,0 +1,55 @@
+package org.dromara.common.core.validate.dicts;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+
+/**
+ * 鑷畾涔夊瓧鍏稿�兼牎楠屽櫒
+ *
+ * @author AprilWind
+ */
+public class DictPatternValidator implements ConstraintValidator<DictPattern, String> {
+
+ /**
+ * 瀛楀吀绫诲瀷
+ */
+ private String dictType;
+
+ /**
+ * 鍒嗛殧绗�
+ */
+ private String separator = ",";
+
+ /**
+ * 鍒濆鍖栨牎楠屽櫒锛屾彁鍙栨敞瑙d笂鐨勫瓧鍏哥被鍨�
+ *
+ * @param annotation 娉ㄨВ瀹炰緥
+ */
+ @Override
+ public void initialize(DictPattern annotation) {
+ this.dictType = annotation.dictType();
+ if (StringUtils.isNotBlank(annotation.separator())) {
+ this.separator = annotation.separator();
+ }
+ }
+
+ /**
+ * 鏍¢獙瀛楁鍊兼槸鍚︿负鎸囧畾瀛楀吀绫诲瀷涓殑鍚堟硶鍊�
+ *
+ * @param value 琚牎楠岀殑瀛楁鍊�
+ * @param context 鏍¢獙涓婁笅鏂囷紙鍙敤浜庢瀯寤洪敊璇俊鎭級
+ * @return true 琛ㄧず鏍¢獙閫氳繃锛堝悎娉曞瓧鍏稿�硷級锛宖alse 琛ㄧず涓嶉�氳繃
+ */
+ @Override
+ public boolean isValid(String value, ConstraintValidatorContext context) {
+ if (StringUtils.isBlank(dictType) || StringUtils.isBlank(value)) {
+ return false;
+ }
+ String dictLabel = SpringUtils.getBean(DictService.class).getDictLabel(dictType, value, separator);
+ return StringUtils.isNotBlank(dictLabel);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPattern.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPattern.java
new file mode 100755
index 0000000..d4f1c3d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPattern.java
@@ -0,0 +1,48 @@
+package org.dromara.common.core.validate.enumd;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.*;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * 鑷畾涔夋灇涓炬牎楠�
+ *
+ * @author 绉嬭緸鏈瘨
+ * @date 2024-12-09
+ */
+@Documented
+@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
+@Retention(RUNTIME)
+@Repeatable(EnumPattern.List.class) // 鍏佽鍦ㄥ悓涓�鍏冪礌涓婂娆′娇鐢ㄨ娉ㄨВ
+@Constraint(validatedBy = {EnumPatternValidator.class})
+public @interface EnumPattern {
+
+ /**
+ * 闇�瑕佹牎楠岀殑鏋氫妇绫诲瀷
+ */
+ Class<? extends Enum<?>> type();
+
+ /**
+ * 鏋氫妇绫诲瀷鏍¢獙鍊煎瓧娈靛悕绉�
+ * 闇�纭繚璇ュ瓧娈靛疄鐜颁簡 getter 鏂规硶
+ */
+ String fieldName();
+
+ String message() default "杈撳叆鍊间笉鍦ㄦ灇涓捐寖鍥村唴";
+
+ Class<?>[] groups() default {};
+
+ Class<? extends Payload>[] payload() default {};
+
+ @Documented
+ @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
+ @Retention(RUNTIME)
+ @interface List {
+ EnumPattern[] value();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPatternValidator.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPatternValidator.java
new file mode 100755
index 0000000..e63f44a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/enumd/EnumPatternValidator.java
@@ -0,0 +1,37 @@
+package org.dromara.common.core.validate.enumd;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+
+/**
+ * 鑷畾涔夋灇涓炬牎楠屾敞瑙e疄鐜�
+ *
+ * @author 绉嬭緸鏈瘨
+ * @date 2024-12-09
+ */
+public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
+
+ private EnumPattern annotation;
+
+ @Override
+ public void initialize(EnumPattern annotation) {
+ ConstraintValidator.super.initialize(annotation);
+ this.annotation = annotation;
+ }
+
+ @Override
+ public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
+ if (StringUtils.isNotBlank(value)) {
+ String fieldName = annotation.fieldName();
+ for (Object e : annotation.type().getEnumConstants()) {
+ if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/Xss.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/Xss.java
new file mode 100755
index 0000000..eed495f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/Xss.java
@@ -0,0 +1,26 @@
+package org.dromara.common.core.xss;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 鑷畾涔墄ss鏍¢獙娉ㄨВ
+ *
+ * @author Lion Li
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
+@Constraint(validatedBy = {XssValidator.class})
+public @interface Xss {
+
+ String message() default "涓嶅厑璁镐换浣曡剼鏈繍琛�";
+
+ Class<?>[] groups() default {};
+
+ Class<? extends Payload>[] payload() default {};
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/XssValidator.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/XssValidator.java
new file mode 100755
index 0000000..9c32563
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/XssValidator.java
@@ -0,0 +1,21 @@
+package org.dromara.common.core.xss;
+
+import cn.hutool.core.util.ReUtil;
+import cn.hutool.http.HtmlUtil;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+/**
+ * 鑷畾涔墄ss鏍¢獙娉ㄨВ瀹炵幇
+ *
+ * @author Lion Li
+ */
+public class XssValidator implements ConstraintValidator<Xss, String> {
+
+ @Override
+ public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
+ return !ReUtil.contains(HtmlUtil.RE_HTML_MARK, value);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..5c3db08
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,4 @@
+org.dromara.common.core.config.ApplicationConfig
+org.dromara.common.core.config.ThreadPoolConfig
+org.dromara.common.core.config.ValidatorConfig
+org.dromara.common.core.utils.SpringUtils
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/.flattened-pom.xml
new file mode 100644
index 0000000..cc0be24
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/.flattened-pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-doc 绯荤粺鎺ュ彛</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.github.therapi</groupId>
+ <artifactId>therapi-runtime-javadoc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.module</groupId>
+ <artifactId>jackson-module-kotlin</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/pom.xml
new file mode 100755
index 0000000..c6199a1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-doc</artifactId>
+
+ <description>
+ ruoyi-common-doc 绯荤粺鎺ュ彛
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.github.therapi</groupId>
+ <artifactId>therapi-runtime-javadoc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.module</groupId>
+ <artifactId>jackson-module-kotlin</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java
new file mode 100755
index 0000000..35b6ce9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java
@@ -0,0 +1,127 @@
+package org.dromara.common.doc.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.doc.config.properties.SpringDocProperties;
+import org.dromara.common.doc.handler.OpenApiHandler;
+import org.springdoc.core.configuration.SpringDocConfiguration;
+import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
+import org.springdoc.core.customizers.OpenApiCustomizer;
+import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
+import org.springdoc.core.properties.SpringDocConfigProperties;
+import org.springdoc.core.providers.JavadocProvider;
+import org.springdoc.core.service.OpenAPIService;
+import org.springdoc.core.service.SecurityService;
+import org.springdoc.core.utils.PropertyResolverUtils;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * 鎺ュ彛鏂囨。閰嶇疆
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@AutoConfiguration(before = SpringDocConfiguration.class)
+@EnableConfigurationProperties(SpringDocProperties.class)
+@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
+public class SpringDocConfig {
+
+ private final ServerProperties serverProperties;
+
+ @Bean
+ @ConditionalOnMissingBean(OpenAPI.class)
+ public OpenAPI openApi(SpringDocProperties properties) {
+ OpenAPI openApi = new OpenAPI();
+ // 鏂囨。鍩烘湰淇℃伅
+ SpringDocProperties.InfoProperties infoProperties = properties.getInfo();
+ Info info = convertInfo(infoProperties);
+ openApi.info(info);
+ // 鎵╁睍鏂囨。淇℃伅
+ openApi.externalDocs(properties.getExternalDocs());
+ openApi.tags(properties.getTags());
+ openApi.paths(properties.getPaths());
+ if (properties.getComponents() != null) {
+ openApi.components(properties.getComponents());
+ Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
+ List<SecurityRequirement> list = new ArrayList<>();
+ SecurityRequirement securityRequirement = new SecurityRequirement();
+ keySet.forEach(securityRequirement::addList);
+ list.add(securityRequirement);
+ openApi.security(list);
+ }
+ return openApi;
+ }
+
+ private Info convertInfo(SpringDocProperties.InfoProperties infoProperties) {
+ Info info = new Info();
+ info.setTitle(infoProperties.getTitle());
+ info.setDescription(infoProperties.getDescription());
+ info.setContact(infoProperties.getContact());
+ info.setLicense(infoProperties.getLicense());
+ info.setVersion(infoProperties.getVersion());
+ return info;
+ }
+
+ /**
+ * 鑷畾涔� openapi 澶勭悊鍣�
+ */
+ @Bean
+ public OpenAPIService openApiBuilder(Optional<OpenAPI> openAPI,
+ SecurityService securityParser,
+ SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
+ Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
+ Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers, Optional<JavadocProvider> javadocProvider) {
+ return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider);
+ }
+
+ /**
+ * 瀵瑰凡缁忕敓鎴愬ソ鐨� OpenApi 杩涜鑷畾涔夋搷浣�
+ */
+ @Bean
+ public OpenApiCustomizer openApiCustomizer() {
+ String contextPath = serverProperties.getServlet().getContextPath();
+ String finalContextPath;
+ if (StringUtils.isBlank(contextPath) || "/".equals(contextPath)) {
+ finalContextPath = "";
+ } else {
+ finalContextPath = contextPath;
+ }
+ // 瀵规墍鏈夎矾寰勫鍔犲墠缃笂涓嬫枃璺緞
+ return openApi -> {
+ Paths oldPaths = openApi.getPaths();
+ if (oldPaths instanceof PlusPaths) {
+ return;
+ }
+ PlusPaths newPaths = new PlusPaths();
+ oldPaths.forEach((k, v) -> newPaths.addPathItem(finalContextPath + k, v));
+ openApi.setPaths(newPaths);
+ };
+ }
+
+ /**
+ * 鍗曠嫭浣跨敤涓�涓被渚夸簬鍒ゆ柇 瑙e喅springdoc璺緞鎷兼帴閲嶅闂
+ *
+ * @author Lion Li
+ */
+ static class PlusPaths extends Paths {
+
+ public PlusPaths() {
+ super();
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/properties/SpringDocProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/properties/SpringDocProperties.java
new file mode 100755
index 0000000..eae3b4c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/properties/SpringDocProperties.java
@@ -0,0 +1,94 @@
+package org.dromara.common.doc.config.properties;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.ExternalDocumentation;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.tags.Tag;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+
+import java.util.List;
+
+/**
+ * swagger 閰嶇疆灞炴��
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "springdoc")
+public class SpringDocProperties {
+
+ /**
+ * 鏂囨。鍩烘湰淇℃伅
+ */
+ @NestedConfigurationProperty
+ private InfoProperties info = new InfoProperties();
+
+ /**
+ * 鎵╁睍鏂囨。鍦板潃
+ */
+ @NestedConfigurationProperty
+ private ExternalDocumentation externalDocs;
+
+ /**
+ * 鏍囩
+ */
+ private List<Tag> tags = null;
+
+ /**
+ * 璺緞
+ */
+ @NestedConfigurationProperty
+ private Paths paths = null;
+
+ /**
+ * 缁勪欢
+ */
+ @NestedConfigurationProperty
+ private Components components = null;
+
+ /**
+ * <p>
+ * 鏂囨。鐨勫熀纭�灞炴�т俊鎭�
+ * </p>
+ *
+ * @see io.swagger.v3.oas.models.info.Info
+ *
+ * 涓轰簡 springboot 鑷姩鐢熶骇閰嶇疆鎻愮ず淇℃伅锛屾墍浠ヨ繖閲屽鍒朵竴涓被鍑烘潵
+ */
+ @Data
+ public static class InfoProperties {
+
+ /**
+ * 鏍囬
+ */
+ private String title = null;
+
+ /**
+ * 鎻忚堪
+ */
+ private String description = null;
+
+ /**
+ * 鑱旂郴浜轰俊鎭�
+ */
+ @NestedConfigurationProperty
+ private Contact contact = null;
+
+ /**
+ * 璁稿彲璇�
+ */
+ @NestedConfigurationProperty
+ private License license = null;
+
+ /**
+ * 鐗堟湰
+ */
+ private String version = null;
+
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java
new file mode 100755
index 0000000..56b7369
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java
@@ -0,0 +1,253 @@
+package org.dromara.common.doc.handler;
+
+import cn.hutool.core.io.IoUtil;
+import io.swagger.v3.core.jackson.TypeNameResolver;
+import io.swagger.v3.core.util.AnnotationsUtils;
+import io.swagger.v3.oas.annotations.tags.Tags;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
+import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
+import org.springdoc.core.properties.SpringDocConfigProperties;
+import org.springdoc.core.providers.JavadocProvider;
+import org.springdoc.core.service.OpenAPIService;
+import org.springdoc.core.service.SecurityService;
+import org.springdoc.core.utils.PropertyResolverUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.method.HandlerMethod;
+
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 鑷畾涔� openapi 澶勭悊鍣�
+ * 瀵规簮鐮佸姛鑳借繘琛屼慨鏀� 澧炲己浣跨敤
+ */
+@Slf4j
+@SuppressWarnings("all")
+public class OpenApiHandler extends OpenAPIService {
+
+ /**
+ * The Basic error controller.
+ */
+ private static Class<?> basicErrorController;
+
+ /**
+ * The Security parser.
+ */
+ private final SecurityService securityParser;
+
+ /**
+ * The Mappings map.
+ */
+ private final Map<String, Object> mappingsMap = new HashMap<>();
+
+ /**
+ * The Springdoc tags.
+ */
+ private final Map<HandlerMethod, Tag> springdocTags = new HashMap<>();
+
+ /**
+ * The Open api builder customisers.
+ */
+ private final Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers;
+
+ /**
+ * The server base URL customisers.
+ */
+ private final Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers;
+
+ /**
+ * The Spring doc config properties.
+ */
+ private final SpringDocConfigProperties springDocConfigProperties;
+
+ /**
+ * The Cached open api map.
+ */
+ private final Map<String, OpenAPI> cachedOpenAPI = new HashMap<>();
+
+ /**
+ * The Property resolver utils.
+ */
+ private final PropertyResolverUtils propertyResolverUtils;
+
+ /**
+ * The javadoc provider.
+ */
+ private final Optional<JavadocProvider> javadocProvider;
+
+ /**
+ * The Context.
+ */
+ private ApplicationContext context;
+
+ /**
+ * The Open api.
+ */
+ private OpenAPI openAPI;
+
+ /**
+ * The Is servers present.
+ */
+ private boolean isServersPresent;
+
+ /**
+ * The Server base url.
+ */
+ private String serverBaseUrl;
+
+ /**
+ * Instantiates a new Open api builder.
+ *
+ * @param openAPI the open api
+ * @param securityParser the security parser
+ * @param springDocConfigProperties the spring doc config properties
+ * @param propertyResolverUtils the property resolver utils
+ * @param openApiBuilderCustomizers the open api builder customisers
+ * @param serverBaseUrlCustomizers the server base url customizers
+ * @param javadocProvider the javadoc provider
+ */
+ public OpenApiHandler(Optional<OpenAPI> openAPI, SecurityService securityParser,
+ SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
+ Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
+ Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
+ Optional<JavadocProvider> javadocProvider) {
+ super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
+ if (openAPI.isPresent()) {
+ this.openAPI = openAPI.get();
+ if (this.openAPI.getComponents() == null)
+ this.openAPI.setComponents(new Components());
+ if (this.openAPI.getPaths() == null)
+ this.openAPI.setPaths(new Paths());
+ if (!CollectionUtils.isEmpty(this.openAPI.getServers()))
+ this.isServersPresent = true;
+ }
+ this.propertyResolverUtils = propertyResolverUtils;
+ this.securityParser = securityParser;
+ this.springDocConfigProperties = springDocConfigProperties;
+ this.openApiBuilderCustomisers = openApiBuilderCustomizers;
+ this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
+ this.javadocProvider = javadocProvider;
+ if (springDocConfigProperties.isUseFqn())
+ TypeNameResolver.std.setUseFqn(true);
+ }
+
+ @Override
+ public Operation buildTags(HandlerMethod handlerMethod, Operation operation, OpenAPI openAPI, Locale locale) {
+
+ Set<Tag> tags = new HashSet<>();
+ Set<String> tagsStr = new HashSet<>();
+
+ buildTagsFromMethod(handlerMethod.getMethod(), tags, tagsStr, locale);
+ buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale);
+
+ if (!CollectionUtils.isEmpty(tagsStr))
+ tagsStr = tagsStr.stream()
+ .map(str -> propertyResolverUtils.resolve(str, locale))
+ .collect(Collectors.toSet());
+
+ if (springdocTags.containsKey(handlerMethod)) {
+ io.swagger.v3.oas.models.tags.Tag tag = springdocTags.get(handlerMethod);
+ tagsStr.add(tag.getName());
+ if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
+ openAPI.addTagsItem(tag);
+ }
+ }
+
+ if (!CollectionUtils.isEmpty(tagsStr)) {
+ if (CollectionUtils.isEmpty(operation.getTags()))
+ operation.setTags(new ArrayList<>(tagsStr));
+ else {
+ Set<String> operationTagsSet = new HashSet<>(operation.getTags());
+ operationTagsSet.addAll(tagsStr);
+ operation.getTags().clear();
+ operation.getTags().addAll(operationTagsSet);
+ }
+ }
+
+ if (isAutoTagClasses(operation)) {
+
+
+ if (javadocProvider.isPresent()) {
+ String description = javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType());
+ if (StringUtils.isNotBlank(description)) {
+ io.swagger.v3.oas.models.tags.Tag tag = new io.swagger.v3.oas.models.tags.Tag();
+
+ // 鑷畾涔夐儴鍒� 淇敼浣跨敤java娉ㄩ噴褰搕ag鍚�
+ List<String> list = IoUtil.readLines(new StringReader(description), new ArrayList<>());
+ // tag.setName(tagAutoName);
+ tag.setName(list.get(0));
+ operation.addTagsItem(list.get(0));
+
+ tag.setDescription(description);
+ if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
+ openAPI.addTagsItem(tag);
+ }
+ }
+ } else {
+ String tagAutoName = splitCamelCase(handlerMethod.getBeanType().getSimpleName());
+ operation.addTagsItem(tagAutoName);
+ }
+ }
+
+ if (!CollectionUtils.isEmpty(tags)) {
+ // Existing tags
+ List<io.swagger.v3.oas.models.tags.Tag> openApiTags = openAPI.getTags();
+ if (!CollectionUtils.isEmpty(openApiTags))
+ tags.addAll(openApiTags);
+ openAPI.setTags(new ArrayList<>(tags));
+ }
+
+ // Handle SecurityRequirement at operation level
+ io.swagger.v3.oas.annotations.security.SecurityRequirement[] securityRequirements = securityParser
+ .getSecurityRequirements(handlerMethod);
+ if (securityRequirements != null) {
+ if (securityRequirements.length == 0)
+ operation.setSecurity(Collections.emptyList());
+ else
+ securityParser.buildSecurityRequirement(securityRequirements, operation);
+ }
+
+ return operation;
+ }
+
+ private void buildTagsFromMethod(Method method, Set<io.swagger.v3.oas.models.tags.Tag> tags, Set<String> tagsStr, Locale locale) {
+ // method tags
+ Set<Tags> tagsSet = AnnotatedElementUtils
+ .findAllMergedAnnotations(method, Tags.class);
+ Set<io.swagger.v3.oas.annotations.tags.Tag> methodTags = tagsSet.stream()
+ .flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
+ methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
+ if (!CollectionUtils.isEmpty(methodTags)) {
+ tagsStr.addAll(StreamUtils.toSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale)));
+ List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
+ addTags(allTags, tags, locale);
+ }
+ }
+
+ private void addTags(List<io.swagger.v3.oas.annotations.tags.Tag> sourceTags, Set<io.swagger.v3.oas.models.tags.Tag> tags, Locale locale) {
+ Optional<Set<io.swagger.v3.oas.models.tags.Tag>> optionalTagSet = AnnotationsUtils
+ .getTags(sourceTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true);
+ optionalTagSet.ifPresent(tagsSet -> {
+ tagsSet.forEach(tag -> {
+ tag.name(propertyResolverUtils.resolve(tag.getName(), locale));
+ tag.description(propertyResolverUtils.resolve(tag.getDescription(), locale));
+ if (tags.stream().noneMatch(t -> t.getName().equals(tag.getName())))
+ tags.add(tag);
+ });
+ });
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..fe11e76
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.doc.config.SpringDocConfig
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/.flattened-pom.xml
new file mode 100644
index 0000000..064fc5f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/.flattened-pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-encrypt</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-encrypt 鏁版嵁鍔犺В瀵嗘ā鍧�</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15to18</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-crypto</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webmvc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.mybatis</groupId>
+ <artifactId>mybatis-spring</artifactId>
+ </exclusion>
+ </exclusions>
+ <optional>true</optional>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/pom.xml
new file mode 100755
index 0000000..ed4910e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/pom.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-encrypt</artifactId>
+
+ <description>
+ ruoyi-common-encrypt 鏁版嵁鍔犺В瀵嗘ā鍧�
+ </description>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15to18</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-crypto</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webmvc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+ <optional>true</optional>
+ <exclusions>
+ <exclusion>
+ <groupId>org.mybatis</groupId>
+ <artifactId>mybatis-spring</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java
new file mode 100755
index 0000000..7f52de8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java
@@ -0,0 +1,20 @@
+package org.dromara.common.encrypt.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 寮哄埗鍔犲瘑娉ㄨВ
+ *
+ * @author Michelle.Chung
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ApiEncrypt {
+
+ /**
+ * 鍝嶅簲鍔犲瘑蹇界暐锛岄粯璁や笉鍔犲瘑锛屼负 true 鏃跺姞瀵�
+ */
+ boolean response() default false;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/EncryptField.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/EncryptField.java
new file mode 100755
index 0000000..d357d72
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/EncryptField.java
@@ -0,0 +1,44 @@
+package org.dromara.common.encrypt.annotation;
+
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+
+import java.lang.annotation.*;
+
+/**
+ * 瀛楁鍔犲瘑娉ㄨВ
+ *
+ * @author 鑰侀┈
+ */
+@Documented
+@Inherited
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EncryptField {
+
+ /**
+ * 鍔犲瘑绠楁硶
+ */
+ AlgorithmType algorithm() default AlgorithmType.DEFAULT;
+
+ /**
+ * 绉橀挜銆侫ES銆丼M4闇�瑕�
+ */
+ String password() default "";
+
+ /**
+ * 鍏挜銆俁SA銆丼M2闇�瑕�
+ */
+ String publicKey() default "";
+
+ /**
+ * 绉侀挜銆俁SA銆丼M2闇�瑕�
+ */
+ String privateKey() default "";
+
+ /**
+ * 缂栫爜鏂瑰紡銆傚鍔犲瘑绠楁硶涓築ASE64鐨勪笉璧蜂綔鐢�
+ */
+ EncodeType encode() default EncodeType.DEFAULT;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java
new file mode 100755
index 0000000..38b22f3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java
@@ -0,0 +1,34 @@
+package org.dromara.common.encrypt.config;
+
+import jakarta.servlet.DispatcherType;
+import org.dromara.common.encrypt.filter.CryptoFilter;
+import org.dromara.common.encrypt.properties.ApiDecryptProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistration;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * api 瑙e瘑鑷姩閰嶇疆
+ *
+ * @author wdhcr
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(ApiDecryptProperties.class)
+@ConditionalOnProperty(value = "api-decrypt.enabled", havingValue = "true")
+public class ApiDecryptAutoConfiguration {
+
+ @Bean
+ @FilterRegistration(
+ name = "cryptoFilter",
+ urlPatterns = "/*",
+ order = FilterRegistrationBean.HIGHEST_PRECEDENCE,
+ dispatcherTypes = DispatcherType.REQUEST
+ )
+ public CryptoFilter cryptoFilter(ApiDecryptProperties properties) {
+ return new CryptoFilter(properties);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java
new file mode 100755
index 0000000..fbc4e52
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java
@@ -0,0 +1,49 @@
+package org.dromara.common.encrypt.config;
+
+import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
+import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.encrypt.core.EncryptorManager;
+import org.dromara.common.encrypt.interceptor.MybatisDecryptInterceptor;
+import org.dromara.common.encrypt.interceptor.MybatisEncryptInterceptor;
+import org.dromara.common.encrypt.properties.EncryptorProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * 鍔犺В瀵嗛厤缃�
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@AutoConfiguration(after = MybatisPlusAutoConfiguration.class)
+@EnableConfigurationProperties(EncryptorProperties.class)
+@ConditionalOnProperty(value = "mybatis-encryptor.enable", havingValue = "true")
+@Slf4j
+public class EncryptorAutoConfiguration {
+
+ @Autowired
+ private EncryptorProperties properties;
+
+ @Bean
+ public EncryptorManager encryptorManager(MybatisPlusProperties mybatisPlusProperties) {
+ return new EncryptorManager(mybatisPlusProperties.getTypeAliasesPackage());
+ }
+
+ @Bean
+ public MybatisEncryptInterceptor mybatisEncryptInterceptor(EncryptorManager encryptorManager) {
+ return new MybatisEncryptInterceptor(encryptorManager, properties);
+ }
+
+ @Bean
+ public MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptorManager encryptorManager) {
+ return new MybatisDecryptInterceptor(encryptorManager, properties);
+ }
+
+}
+
+
+
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContext.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContext.java
new file mode 100755
index 0000000..2f02eaf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContext.java
@@ -0,0 +1,41 @@
+package org.dromara.common.encrypt.core;
+
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import lombok.Data;
+
+/**
+ * 鍔犲瘑涓婁笅鏂� 鐢ㄤ簬encryptor浼犻�掑繀瑕佺殑鍙傛暟銆�
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@Data
+public class EncryptContext {
+
+ /**
+ * 榛樿绠楁硶
+ */
+ private AlgorithmType algorithm;
+
+ /**
+ * 瀹夊叏绉橀挜
+ */
+ private String password;
+
+ /**
+ * 鍏挜
+ */
+ private String publicKey;
+
+ /**
+ * 绉侀挜
+ */
+ private String privateKey;
+
+ /**
+ * 缂栫爜鏂瑰紡锛宐ase64/hex
+ */
+ private EncodeType encode;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java
new file mode 100755
index 0000000..5e2c731
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java
@@ -0,0 +1,168 @@
+package org.dromara.common.encrypt.core;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ReflectUtil;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.io.Resources;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.utils.ObjectUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.annotation.EncryptField;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.type.ClassMetadata;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.util.ClassUtils;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * 鍔犲瘑绠$悊绫�
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@Slf4j
+@NoArgsConstructor
+public class EncryptorManager {
+
+ /**
+ * 缂撳瓨鍔犲瘑鍣�
+ */
+ Map<Integer, IEncryptor> encryptorMap = new ConcurrentHashMap<>();
+
+ /**
+ * 绫诲姞瀵嗗瓧娈电紦瀛�
+ */
+ Map<Class<?>, Set<Field>> fieldCache = new ConcurrentHashMap<>();
+
+ /**
+ * 鏋勯�犳柟娉曚紶鍏ョ被鍔犲瘑瀛楁缂撳瓨
+ *
+ * @param typeAliasesPackage 瀹炰綋绫诲寘
+ */
+ public EncryptorManager(String typeAliasesPackage) {
+ scanEncryptClasses(typeAliasesPackage);
+ }
+
+
+ /**
+ * 鑾峰彇绫诲姞瀵嗗瓧娈电紦瀛�
+ */
+ public Set<Field> getFieldCache(Class<?> sourceClazz) {
+ return ObjectUtils.notNullGetter(fieldCache, f -> f.get(sourceClazz));
+ }
+
+ /**
+ * 娉ㄥ唽鍔犲瘑鎵ц鑰呭埌缂撳瓨
+ *
+ * @param encryptContext 鍔犲瘑鎵ц鑰呴渶瑕佺殑鐩稿叧閰嶇疆鍙傛暟
+ */
+ public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) {
+ int key = encryptContext.hashCode();
+ if (encryptorMap.containsKey(key)) {
+ return encryptorMap.get(key);
+ }
+ IEncryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext);
+ encryptorMap.put(key, encryptor);
+ return encryptor;
+ }
+
+ /**
+ * 绉婚櫎缂撳瓨涓殑鍔犲瘑鎵ц鑰�
+ *
+ * @param encryptContext 鍔犲瘑鎵ц鑰呴渶瑕佺殑鐩稿叧閰嶇疆鍙傛暟
+ */
+ public void removeEncryptor(EncryptContext encryptContext) {
+ this.encryptorMap.remove(encryptContext.hashCode());
+ }
+
+ /**
+ * 鏍规嵁閰嶇疆杩涜鍔犲瘑銆備細杩涜鏈湴缂撳瓨瀵瑰簲鐨勭畻娉曞拰瀵瑰簲鐨勭閽ヤ俊鎭��
+ *
+ * @param value 寰呭姞瀵嗙殑鍊�
+ * @param encryptContext 鍔犲瘑鐩稿叧鐨勯厤缃俊鎭�
+ */
+ public String encrypt(String value, EncryptContext encryptContext) {
+ if (StringUtils.startsWith(value, Constants.ENCRYPT_HEADER)) {
+ return value;
+ }
+ IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
+ String encrypt = encryptor.encrypt(value, encryptContext.getEncode());
+ return Constants.ENCRYPT_HEADER + encrypt;
+ }
+
+ /**
+ * 鏍规嵁閰嶇疆杩涜瑙e瘑
+ *
+ * @param value 寰呰В瀵嗙殑鍊�
+ * @param encryptContext 鍔犲瘑鐩稿叧鐨勯厤缃俊鎭�
+ */
+ public String decrypt(String value, EncryptContext encryptContext) {
+ if (!StringUtils.startsWith(value, Constants.ENCRYPT_HEADER)) {
+ return value;
+ }
+ IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
+ String str = StringUtils.removeStart(value, Constants.ENCRYPT_HEADER);
+ return encryptor.decrypt(str);
+ }
+
+ /**
+ * 閫氳繃 typeAliasesPackage 璁剧疆鐨勬壂鎻忓寘 鎵弿缂撳瓨瀹炰綋
+ */
+ private void scanEncryptClasses(String typeAliasesPackage) {
+ PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+ CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
+ String[] packagePatternArray = StringUtils.splitPreserveAllTokens(typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
+ String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
+ try {
+ for (String packagePattern : packagePatternArray) {
+ String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
+ Resource[] resources = resolver.getResources(classpath + path + "/*.class");
+ for (Resource resource : resources) {
+ ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
+ Class<?> clazz = Resources.classForName(classMetadata.getClassName());
+ Set<Field> encryptFieldSet = getEncryptFieldSetFromClazz(clazz);
+ if (CollUtil.isNotEmpty(encryptFieldSet)) {
+ fieldCache.put(clazz, encryptFieldSet);
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.error("鍒濆鍖栨暟鎹畨鍏ㄧ紦瀛樻椂鍑洪敊:{}", e.getMessage());
+ }
+ }
+
+ /**
+ * 鑾峰緱涓�涓被鐨勫姞瀵嗗瓧娈甸泦鍚�
+ */
+ private Set<Field> getEncryptFieldSetFromClazz(Class<?> clazz) {
+ Set<Field> fieldSet = new HashSet<>();
+ // 鍒ゆ柇clazz濡傛灉鏄帴鍙�,鍐呴儴绫�,鍖垮悕绫诲氨鐩存帴杩斿洖
+ if (clazz.isInterface() || clazz.isMemberClass() || clazz.isAnonymousClass()) {
+ return fieldSet;
+ }
+ while (clazz != null) {
+ Field[] fields = clazz.getDeclaredFields();
+ fieldSet.addAll(Arrays.asList(fields));
+ clazz = clazz.getSuperclass();
+ }
+ fieldSet = fieldSet.stream().filter(field ->
+ field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class)
+ .collect(Collectors.toSet());
+ for (Field field : fieldSet) {
+ field.setAccessible(true);
+ }
+ return fieldSet;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/IEncryptor.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/IEncryptor.java
new file mode 100755
index 0000000..dbc4420
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/IEncryptor.java
@@ -0,0 +1,35 @@
+package org.dromara.common.encrypt.core;
+
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+
+/**
+ * 鍔犺В鑰�
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public interface IEncryptor {
+
+ /**
+ * 鑾峰緱褰撳墠绠楁硶
+ */
+ AlgorithmType algorithm();
+
+ /**
+ * 鍔犲瘑
+ *
+ * @param value 寰呭姞瀵嗗瓧绗︿覆
+ * @param encodeType 鍔犲瘑鍚庣殑缂栫爜鏍煎紡
+ * @return 鍔犲瘑鍚庣殑瀛楃涓�
+ */
+ String encrypt(String value, EncodeType encodeType);
+
+ /**
+ * 瑙e瘑
+ *
+ * @param value 寰呭姞瀵嗗瓧绗︿覆
+ * @return 瑙e瘑鍚庣殑瀛楃涓�
+ */
+ String decrypt(String value);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AbstractEncryptor.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AbstractEncryptor.java
new file mode 100755
index 0000000..858d229
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AbstractEncryptor.java
@@ -0,0 +1,18 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.core.IEncryptor;
+
+/**
+ * 鎵�鏈夊姞瀵嗘墽琛岃�呯殑鍩虹被
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public abstract class AbstractEncryptor implements IEncryptor {
+
+ public AbstractEncryptor(EncryptContext context) {
+ // 鐢ㄦ埛閰嶇疆鏍¢獙涓庨厤缃敞鍏�
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AesEncryptor.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AesEncryptor.java
new file mode 100755
index 0000000..e4dc597
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AesEncryptor.java
@@ -0,0 +1,55 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+/**
+ * AES绠楁硶瀹炵幇
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public class AesEncryptor extends AbstractEncryptor {
+
+ private final EncryptContext context;
+
+ public AesEncryptor(EncryptContext context) {
+ super(context);
+ this.context = context;
+ }
+
+ /**
+ * 鑾峰緱褰撳墠绠楁硶
+ */
+ @Override
+ public AlgorithmType algorithm() {
+ return AlgorithmType.AES;
+ }
+
+ /**
+ * 鍔犲瘑
+ *
+ * @param value 寰呭姞瀵嗗瓧绗︿覆
+ * @param encodeType 鍔犲瘑鍚庣殑缂栫爜鏍煎紡
+ */
+ @Override
+ public String encrypt(String value, EncodeType encodeType) {
+ if (encodeType == EncodeType.HEX) {
+ return EncryptUtils.encryptByAesHex(value, context.getPassword());
+ } else {
+ return EncryptUtils.encryptByAes(value, context.getPassword());
+ }
+ }
+
+ /**
+ * 瑙e瘑
+ *
+ * @param value 寰呭姞瀵嗗瓧绗︿覆
+ */
+ @Override
+ public String decrypt(String value) {
+ return EncryptUtils.decryptByAes(value, context.getPassword());
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Base64Encryptor.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Base64Encryptor.java
new file mode 100755
index 0000000..0028548
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Base64Encryptor.java
@@ -0,0 +1,48 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+/**
+ * Base64绠楁硶瀹炵幇
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public class Base64Encryptor extends AbstractEncryptor {
+
+ public Base64Encryptor(EncryptContext context) {
+ super(context);
+ }
+
+ /**
+ * 鑾峰緱褰撳墠绠楁硶
+ */
+ @Override
+ public AlgorithmType algorithm() {
+ return AlgorithmType.BASE64;
+ }
+
+ /**
+ * 鍔犲瘑
+ *
+ * @param value 寰呭姞瀵嗗瓧绗︿覆
+ * @param encodeType 鍔犲瘑鍚庣殑缂栫爜鏍煎紡
+ */
+ @Override
+ public String encrypt(String value, EncodeType encodeType) {
+ return EncryptUtils.encryptByBase64(value);
+ }
+
+ /**
+ * 瑙e瘑
+ *
+ * @param value 寰呭姞瀵嗗瓧绗︿覆
+ */
+ @Override
+ public String decrypt(String value) {
+ return EncryptUtils.decryptByBase64(value);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/RsaEncryptor.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/RsaEncryptor.java
new file mode 100755
index 0000000..5f03a4b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/RsaEncryptor.java
@@ -0,0 +1,62 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+
+/**
+ * RSA绠楁硶瀹炵幇
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public class RsaEncryptor extends AbstractEncryptor {
+
+ private final EncryptContext context;
+
+ public RsaEncryptor(EncryptContext context) {
+ super(context);
+ String privateKey = context.getPrivateKey();
+ String publicKey = context.getPublicKey();
+ if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
+ throw new IllegalArgumentException("RSA鍏閽ュ潎闇�瑕佹彁渚涳紝鍏挜鍔犲瘑锛岀閽ヨВ瀵嗐��");
+ }
+ this.context = context;
+ }
+
+ /**
+ * 鑾峰緱褰撳墠绠楁硶
+ */
+ @Override
+ public AlgorithmType algorithm() {
+ return AlgorithmType.RSA;
+ }
+
+ /**
+ * 鍔犲瘑
+ *
+ * @param value 寰呭姞瀵嗗瓧绗︿覆
+ * @param encodeType 鍔犲瘑鍚庣殑缂栫爜鏍煎紡
+ */
+ @Override
+ public String encrypt(String value, EncodeType encodeType) {
+ if (encodeType == EncodeType.HEX) {
+ return EncryptUtils.encryptByRsaHex(value, context.getPublicKey());
+ } else {
+ return EncryptUtils.encryptByRsa(value, context.getPublicKey());
+ }
+ }
+
+ /**
+ * 瑙e瘑
+ *
+ * @param value 寰呭姞瀵嗗瓧绗︿覆
+ */
+ @Override
+ public String decrypt(String value) {
+ return EncryptUtils.decryptByRsa(value, context.getPrivateKey());
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm2Encryptor.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm2Encryptor.java
new file mode 100755
index 0000000..aec5d82
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm2Encryptor.java
@@ -0,0 +1,61 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+/**
+ * sm2绠楁硶瀹炵幇
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public class Sm2Encryptor extends AbstractEncryptor {
+
+ private final EncryptContext context;
+
+ public Sm2Encryptor(EncryptContext context) {
+ super(context);
+ String privateKey = context.getPrivateKey();
+ String publicKey = context.getPublicKey();
+ if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
+ throw new IllegalArgumentException("SM2鍏閽ュ潎闇�瑕佹彁渚涳紝鍏挜鍔犲瘑锛岀閽ヨВ瀵嗐��");
+ }
+ this.context = context;
+ }
+
+ /**
+ * 鑾峰緱褰撳墠绠楁硶
+ */
+ @Override
+ public AlgorithmType algorithm() {
+ return AlgorithmType.SM2;
+ }
+
+ /**
+ * 鍔犲瘑
+ *
+ * @param value 寰呭姞瀵嗗瓧绗︿覆
+ * @param encodeType 鍔犲瘑鍚庣殑缂栫爜鏍煎紡
+ */
+ @Override
+ public String encrypt(String value, EncodeType encodeType) {
+ if (encodeType == EncodeType.HEX) {
+ return EncryptUtils.encryptBySm2Hex(value, context.getPublicKey());
+ } else {
+ return EncryptUtils.encryptBySm2(value, context.getPublicKey());
+ }
+ }
+
+ /**
+ * 瑙e瘑
+ *
+ * @param value 寰呭姞瀵嗗瓧绗︿覆
+ */
+ @Override
+ public String decrypt(String value) {
+ return EncryptUtils.decryptBySm2(value, context.getPrivateKey());
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm4Encryptor.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm4Encryptor.java
new file mode 100755
index 0000000..adaf674
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm4Encryptor.java
@@ -0,0 +1,55 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+/**
+ * sm4绠楁硶瀹炵幇
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public class Sm4Encryptor extends AbstractEncryptor {
+
+ private final EncryptContext context;
+
+ public Sm4Encryptor(EncryptContext context) {
+ super(context);
+ this.context = context;
+ }
+
+ /**
+ * 鑾峰緱褰撳墠绠楁硶
+ */
+ @Override
+ public AlgorithmType algorithm() {
+ return AlgorithmType.SM4;
+ }
+
+ /**
+ * 鍔犲瘑
+ *
+ * @param value 寰呭姞瀵嗗瓧绗︿覆
+ * @param encodeType 鍔犲瘑鍚庣殑缂栫爜鏍煎紡
+ */
+ @Override
+ public String encrypt(String value, EncodeType encodeType) {
+ if (encodeType == EncodeType.HEX) {
+ return EncryptUtils.encryptBySm4Hex(value, context.getPassword());
+ } else {
+ return EncryptUtils.encryptBySm4(value, context.getPassword());
+ }
+ }
+
+ /**
+ * 瑙e瘑
+ *
+ * @param value 寰呭姞瀵嗗瓧绗︿覆
+ */
+ @Override
+ public String decrypt(String value) {
+ return EncryptUtils.decryptBySm4(value, context.getPassword());
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/AlgorithmType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/AlgorithmType.java
new file mode 100755
index 0000000..26ee1ee
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/AlgorithmType.java
@@ -0,0 +1,48 @@
+package org.dromara.common.encrypt.enumd;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.dromara.common.encrypt.core.encryptor.*;
+
+/**
+ * 绠楁硶鍚嶇О
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@Getter
+@AllArgsConstructor
+public enum AlgorithmType {
+
+ /**
+ * 榛樿璧皔ml閰嶇疆
+ */
+ DEFAULT(null),
+
+ /**
+ * base64
+ */
+ BASE64(Base64Encryptor.class),
+
+ /**
+ * aes
+ */
+ AES(AesEncryptor.class),
+
+ /**
+ * rsa
+ */
+ RSA(RsaEncryptor.class),
+
+ /**
+ * sm2
+ */
+ SM2(Sm2Encryptor.class),
+
+ /**
+ * sm4
+ */
+ SM4(Sm4Encryptor.class);
+
+ private final Class<? extends AbstractEncryptor> clazz;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/EncodeType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/EncodeType.java
new file mode 100755
index 0000000..f471221
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/EncodeType.java
@@ -0,0 +1,26 @@
+package org.dromara.common.encrypt.enumd;
+
+/**
+ * 缂栫爜绫诲瀷
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public enum EncodeType {
+
+ /**
+ * 榛樿浣跨敤yml閰嶇疆
+ */
+ DEFAULT,
+
+ /**
+ * base64缂栫爜
+ */
+ BASE64,
+
+ /**
+ * 16杩涘埗缂栫爜
+ */
+ HEX;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java
new file mode 100755
index 0000000..79d58da
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java
@@ -0,0 +1,110 @@
+package org.dromara.common.encrypt.filter;
+
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.*;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.dromara.common.core.constant.HttpStatus;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.annotation.ApiEncrypt;
+import org.dromara.common.encrypt.properties.ApiDecryptProperties;
+import org.springframework.http.HttpMethod;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerExceptionResolver;
+import org.springframework.web.servlet.HandlerExecutionChain;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.io.IOException;
+
+
+/**
+ * Crypto 杩囨护鍣�
+ *
+ * @author wdhcr
+ */
+public class CryptoFilter implements Filter {
+ private final ApiDecryptProperties properties;
+
+ public CryptoFilter(ApiDecryptProperties properties) {
+ this.properties = properties;
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest servletRequest = (HttpServletRequest) request;
+ HttpServletResponse servletResponse = (HttpServletResponse) response;
+ // 鑾峰彇鍔犲瘑娉ㄨВ
+ ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest);
+ boolean responseFlag = apiEncrypt != null && apiEncrypt.response();
+ ServletRequest requestWrapper = null;
+ ServletResponse responseWrapper = null;
+ EncryptResponseBodyWrapper responseBodyWrapper = null;
+
+ // 鏄惁涓� put 鎴栬�� post 璇锋眰
+ if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) {
+ // 鏄惁瀛樺湪鍔犲瘑鏍囧ご
+ String headerValue = servletRequest.getHeader(properties.getHeaderFlag());
+ if (StringUtils.isNotBlank(headerValue)) {
+ // 璇锋眰瑙e瘑
+ requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag());
+ } else {
+ // 鏄惁鏈夋敞瑙o紝鏈夊氨鎶ラ敊锛屾病鏈夋斁琛�
+ if (ObjectUtil.isNotNull(apiEncrypt)) {
+ HandlerExceptionResolver exceptionResolver = SpringUtils.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);
+ exceptionResolver.resolveException(
+ servletRequest, servletResponse, null,
+ new ServiceException("娌℃湁璁块棶鏉冮檺锛岃鑱旂郴绠$悊鍛樻巿鏉�", HttpStatus.FORBIDDEN));
+ return;
+ }
+ }
+ }
+
+ // 鍒ゆ柇鏄惁鍝嶅簲鍔犲瘑
+ if (responseFlag) {
+ responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);
+ responseWrapper = responseBodyWrapper;
+ }
+
+ chain.doFilter(
+ ObjectUtil.defaultIfNull(requestWrapper, request),
+ ObjectUtil.defaultIfNull(responseWrapper, response));
+
+ if (responseFlag) {
+ servletResponse.reset();
+ // 瀵瑰師濮嬪唴瀹瑰姞瀵�
+ String encryptContent = responseBodyWrapper.getEncryptContent(
+ servletResponse, properties.getPublicKey(), properties.getHeaderFlag());
+ // 瀵瑰姞瀵嗗悗鐨勫唴瀹瑰啓鍑�
+ servletResponse.getWriter().write(encryptContent);
+ }
+ }
+
+ /**
+ * 鑾峰彇 ApiEncrypt 娉ㄨВ
+ */
+ private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest) {
+ RequestMappingHandlerMapping handlerMapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
+ // 鑾峰彇娉ㄨВ
+ try {
+ HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest);
+ if (ObjectUtil.isNotNull(mappingHandler)) {
+ Object handler = mappingHandler.getHandler();
+ if (ObjectUtil.isNotNull(handler)) {
+ // 浠巋andler鑾峰彇娉ㄨВ
+ if (handler instanceof HandlerMethod handlerMethod) {
+ return handlerMethod.getMethodAnnotation(ApiEncrypt.class);
+ }
+ }
+ }
+ } catch (Exception e) {
+ return null;
+ }
+ return null;
+ }
+
+ @Override
+ public void destroy() {
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java
new file mode 100755
index 0000000..98f4bc7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java
@@ -0,0 +1,94 @@
+package org.dromara.common.encrypt.filter;
+
+import cn.hutool.core.io.IoUtil;
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+import org.springframework.http.MediaType;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 瑙e瘑璇锋眰鍙傛暟宸ュ叿绫�
+ *
+ * @author wdhcr
+ */
+public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper {
+
+ private final byte[] body;
+
+ public DecryptRequestBodyWrapper(HttpServletRequest request, String privateKey, String headerFlag) throws IOException {
+ super(request);
+ // 鑾峰彇 AES 瀵嗙爜 閲囩敤 RSA 鍔犲瘑
+ String headerRsa = request.getHeader(headerFlag);
+ String decryptAes = EncryptUtils.decryptByRsa(headerRsa, privateKey);
+ // 瑙e瘑 AES 瀵嗙爜
+ String aesPassword = EncryptUtils.decryptByBase64(decryptAes);
+ request.setCharacterEncoding(Constants.UTF8);
+ byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false);
+ String requestBody = new String(readBytes, StandardCharsets.UTF_8);
+ // 瑙e瘑 body 閲囩敤 AES 鍔犲瘑
+ String decryptBody = EncryptUtils.decryptByAes(requestBody, aesPassword);
+ body = decryptBody.getBytes(StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public BufferedReader getReader() {
+ return new BufferedReader(new InputStreamReader(getInputStream()));
+ }
+
+
+ @Override
+ public int getContentLength() {
+ return body.length;
+ }
+
+ @Override
+ public long getContentLengthLong() {
+ return body.length;
+ }
+
+ @Override
+ public String getContentType() {
+ return MediaType.APPLICATION_JSON_VALUE;
+ }
+
+
+ @Override
+ public ServletInputStream getInputStream() {
+ final ByteArrayInputStream bais = new ByteArrayInputStream(body);
+ return new ServletInputStream() {
+ @Override
+ public int read() {
+ return bais.read();
+ }
+
+ @Override
+ public int available() {
+ return body.length;
+ }
+
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return false;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+
+ }
+ };
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java
new file mode 100755
index 0000000..78e06ff
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java
@@ -0,0 +1,123 @@
+package org.dromara.common.encrypt.filter;
+
+import cn.hutool.core.util.RandomUtil;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 鍔犲瘑鍝嶅簲鍙傛暟鍖呰绫�
+ *
+ * @author Michelle.Chung
+ */
+public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper {
+
+ private final ByteArrayOutputStream byteArrayOutputStream;
+ private final ServletOutputStream servletOutputStream;
+ private final PrintWriter printWriter;
+
+ public EncryptResponseBodyWrapper(HttpServletResponse response) throws IOException {
+ super(response);
+ this.byteArrayOutputStream = new ByteArrayOutputStream();
+ this.servletOutputStream = this.getOutputStream();
+ this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream));
+ }
+
+ @Override
+ public PrintWriter getWriter() {
+ return printWriter;
+ }
+
+ @Override
+ public void flushBuffer() throws IOException {
+ if (servletOutputStream != null) {
+ servletOutputStream.flush();
+ }
+ if (printWriter != null) {
+ printWriter.flush();
+ }
+ }
+
+ @Override
+ public void reset() {
+ byteArrayOutputStream.reset();
+ }
+
+ public byte[] getResponseData() throws IOException {
+ flushBuffer();
+ return byteArrayOutputStream.toByteArray();
+ }
+
+ public String getContent() throws IOException {
+ flushBuffer();
+ return byteArrayOutputStream.toString();
+ }
+
+ /**
+ * 鑾峰彇鍔犲瘑鍐呭
+ *
+ * @param servletResponse response
+ * @param publicKey RSA鍏挜 (鐢ㄤ簬鍔犲瘑 AES 绉橀挜)
+ * @param headerFlag 璇锋眰澶存爣蹇�
+ * @return 鍔犲瘑鍐呭
+ * @throws IOException
+ */
+ public String getEncryptContent(HttpServletResponse servletResponse, String publicKey, String headerFlag) throws IOException {
+ // 鐢熸垚绉橀挜
+ String aesPassword = RandomUtil.randomString(32);
+ // 绉橀挜浣跨敤 Base64 缂栫爜
+ String encryptAes = EncryptUtils.encryptByBase64(aesPassword);
+ // Rsa 鍏挜鍔犲瘑 Base64 缂栫爜
+ String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey);
+
+ // 璁剧疆鍝嶅簲澶�
+ // vue鐗堟湰闇�瑕佽缃�
+ servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag);
+ servletResponse.setHeader("Access-Control-Allow-Origin", "*");
+ servletResponse.setHeader("Access-Control-Allow-Methods", "*");
+ servletResponse.setHeader(headerFlag, encryptPassword);
+ servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());
+
+
+ // 鑾峰彇鍘熷鍐呭
+ String originalBody = this.getContent();
+ // 瀵瑰唴瀹硅繘琛屽姞瀵�
+ return EncryptUtils.encryptByAes(originalBody, aesPassword);
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException {
+ return new ServletOutputStream() {
+ @Override
+ public boolean isReady() {
+ return false;
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener) {
+
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ byteArrayOutputStream.write(b);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ byteArrayOutputStream.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ byteArrayOutputStream.write(b, off, len);
+ }
+ };
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java
new file mode 100755
index 0000000..d5faae7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java
@@ -0,0 +1,132 @@
+package org.dromara.common.encrypt.interceptor;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.executor.parameter.ParameterHandler;
+import org.apache.ibatis.executor.resultset.ResultSetHandler;
+import org.apache.ibatis.plugin.*;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.annotation.EncryptField;
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.core.EncryptorManager;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.properties.EncryptorProperties;
+
+import java.lang.reflect.Field;
+import java.sql.Statement;
+import java.util.*;
+
+/**
+ * 鍑哄弬瑙e瘑鎷︽埅鍣�
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@Slf4j
+@Intercepts({@Signature(
+ type = ResultSetHandler.class,
+ method = "handleResultSets",
+ args = {Statement.class})
+})
+@AllArgsConstructor
+public class MybatisDecryptInterceptor implements Interceptor {
+
+ private final EncryptorManager encryptorManager;
+ private final EncryptorProperties defaultProperties;
+
+ @Override
+ public Object intercept(Invocation invocation) throws Throwable {
+ // 寮�濮嬭繘琛屽弬鏁拌В瀵�
+ ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();
+ Field parameterHandlerField = resultSetHandler.getClass().getDeclaredField("parameterHandler");
+ parameterHandlerField.setAccessible(true);
+ Object target = parameterHandlerField.get(resultSetHandler);
+ if (target instanceof ParameterHandler parameterHandler) {
+ Object parameterObject = parameterHandler.getParameterObject();
+ if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
+ this.decryptHandler(parameterObject);
+ }
+ }
+ // 鑾峰彇鎵цmysql鎵ц缁撴灉
+ Object result = invocation.proceed();
+ if (result == null) {
+ return null;
+ }
+ this.decryptHandler(result);
+ return result;
+ }
+
+ /**
+ * 瑙e瘑瀵硅薄
+ *
+ * @param sourceObject 寰呭姞瀵嗗璞�
+ */
+ private void decryptHandler(Object sourceObject) {
+ if (ObjectUtil.isNull(sourceObject)) {
+ return;
+ }
+ if (sourceObject instanceof Map<?, ?> map) {
+ new HashSet<>(map.values()).forEach(this::decryptHandler);
+ return;
+ }
+ if (sourceObject instanceof List<?> list) {
+ if(CollUtil.isEmpty(list)) {
+ return;
+ }
+ // 鍒ゆ柇绗竴涓厓绱犳槸鍚﹀惈鏈夋敞瑙c�傚鏋滄病鏈夌洿鎺ヨ繑鍥烇紝鎻愰珮鏁堢巼
+ Object firstItem = list.get(0);
+ if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
+ return;
+ }
+ list.forEach(this::decryptHandler);
+ return;
+ }
+ // 涓嶅湪缂撳瓨涓殑绫�,灏辨槸娌℃湁鍔犲瘑娉ㄨВ鐨勭被(褰撶劧涔熸湁鍙兘鏄痶ypeAliasesPackage鍐欓敊)
+ Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
+ if(ObjectUtil.isNull(fields)){
+ return;
+ }
+ try {
+ for (Field field : fields) {
+ field.set(sourceObject, this.decryptField(Convert.toStr(field.get(sourceObject)), field));
+ }
+ } catch (Exception e) {
+ log.error("澶勭悊瑙e瘑瀛楁鏃跺嚭閿�", e);
+ }
+ }
+
+ /**
+ * 瀛楁鍊艰繘琛屽姞瀵嗐�傞�氳繃瀛楁鐨勬壒娉ㄦ敞鍐屾柊鐨勫姞瀵嗙畻娉�
+ *
+ * @param value 寰呭姞瀵嗙殑鍊�
+ * @param field 寰呭姞瀵嗗瓧娈�
+ * @return 鍔犲瘑鍚庣粨鏋�
+ */
+ private String decryptField(String value, Field field) {
+ if (ObjectUtil.isNull(value)) {
+ return null;
+ }
+ EncryptField encryptField = field.getAnnotation(EncryptField.class);
+ EncryptContext encryptContext = new EncryptContext();
+ encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
+ encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());
+ encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());
+ encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());
+ encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());
+ return this.encryptorManager.decrypt(value, encryptContext);
+ }
+
+ @Override
+ public Object plugin(Object target) {
+ return Plugin.wrap(target, this);
+ }
+
+ @Override
+ public void setProperties(Properties properties) {
+
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java
new file mode 100755
index 0000000..bcc2f4c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java
@@ -0,0 +1,124 @@
+package org.dromara.common.encrypt.interceptor;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.executor.parameter.ParameterHandler;
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.plugin.Intercepts;
+import org.apache.ibatis.plugin.Invocation;
+import org.apache.ibatis.plugin.Signature;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.annotation.EncryptField;
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.core.EncryptorManager;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.properties.EncryptorProperties;
+
+import java.lang.reflect.Field;
+import java.sql.PreparedStatement;
+import java.util.*;
+
+/**
+ * 鍏ュ弬鍔犲瘑鎷︽埅鍣�
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@Slf4j
+@Intercepts({@Signature(
+ type = ParameterHandler.class,
+ method = "setParameters",
+ args = {PreparedStatement.class})
+})
+@AllArgsConstructor
+public class MybatisEncryptInterceptor implements Interceptor {
+
+ private final EncryptorManager encryptorManager;
+ private final EncryptorProperties defaultProperties;
+
+ @Override
+ public Object intercept(Invocation invocation) throws Throwable {
+ return invocation;
+ }
+
+ @Override
+ public Object plugin(Object target) {
+ if (target instanceof ParameterHandler parameterHandler) {
+ // 杩涜鍔犲瘑鎿嶄綔
+ Object parameterObject = parameterHandler.getParameterObject();
+ if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
+ this.encryptHandler(parameterObject);
+ }
+ }
+ return target;
+ }
+
+ /**
+ * 鍔犲瘑瀵硅薄
+ *
+ * @param sourceObject 寰呭姞瀵嗗璞�
+ */
+ private void encryptHandler(Object sourceObject) {
+ if (ObjectUtil.isNull(sourceObject)) {
+ return;
+ }
+ if (sourceObject instanceof Map<?, ?> map) {
+ new HashSet<>(map.values()).forEach(this::encryptHandler);
+ return;
+ }
+ if (sourceObject instanceof List<?> list) {
+ if(CollUtil.isEmpty(list)) {
+ return;
+ }
+ // 鍒ゆ柇绗竴涓厓绱犳槸鍚﹀惈鏈夋敞瑙c�傚鏋滄病鏈夌洿鎺ヨ繑鍥烇紝鎻愰珮鏁堢巼
+ Object firstItem = list.get(0);
+ if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
+ return;
+ }
+ list.forEach(this::encryptHandler);
+ return;
+ }
+ // 涓嶅湪缂撳瓨涓殑绫�,灏辨槸娌℃湁鍔犲瘑娉ㄨВ鐨勭被(褰撶劧涔熸湁鍙兘鏄痶ypeAliasesPackage鍐欓敊)
+ Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
+ if(ObjectUtil.isNull(fields)){
+ return;
+ }
+ try {
+ for (Field field : fields) {
+ field.set(sourceObject, this.encryptField(Convert.toStr(field.get(sourceObject)), field));
+ }
+ } catch (Exception e) {
+ log.error("澶勭悊鍔犲瘑瀛楁鏃跺嚭閿�", e);
+ }
+ }
+
+ /**
+ * 瀛楁鍊艰繘琛屽姞瀵嗐�傞�氳繃瀛楁鐨勬壒娉ㄦ敞鍐屾柊鐨勫姞瀵嗙畻娉�
+ *
+ * @param value 寰呭姞瀵嗙殑鍊�
+ * @param field 寰呭姞瀵嗗瓧娈�
+ * @return 鍔犲瘑鍚庣粨鏋�
+ */
+ private String encryptField(String value, Field field) {
+ if (ObjectUtil.isNull(value)) {
+ return null;
+ }
+ EncryptField encryptField = field.getAnnotation(EncryptField.class);
+ EncryptContext encryptContext = new EncryptContext();
+ encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
+ encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());
+ encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());
+ encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());
+ encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());
+ return this.encryptorManager.encrypt(value, encryptContext);
+ }
+
+
+ @Override
+ public void setProperties(Properties properties) {
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java
new file mode 100755
index 0000000..6aadb3e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java
@@ -0,0 +1,34 @@
+package org.dromara.common.encrypt.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * api瑙e瘑灞炴�ч厤缃被
+ * @author wdhcr
+ */
+@Data
+@ConfigurationProperties(prefix = "api-decrypt")
+public class ApiDecryptProperties {
+
+ /**
+ * 鍔犲瘑寮�鍏�
+ */
+ private Boolean enabled;
+
+ /**
+ * 澶撮儴鏍囪瘑
+ */
+ private String headerFlag;
+
+ /**
+ * 鍝嶅簲鍔犲瘑鍏挜
+ */
+ private String publicKey;
+
+ /**
+ * 璇锋眰瑙e瘑绉侀挜
+ */
+ private String privateKey;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java
new file mode 100755
index 0000000..ba445c1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java
@@ -0,0 +1,48 @@
+package org.dromara.common.encrypt.properties;
+
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 鍔犺В瀵嗗睘鎬ч厤缃被
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@Data
+@ConfigurationProperties(prefix = "mybatis-encryptor")
+public class EncryptorProperties {
+
+ /**
+ * 杩囨护寮�鍏�
+ */
+ private Boolean enable;
+
+ /**
+ * 榛樿绠楁硶
+ */
+ private AlgorithmType algorithm;
+
+ /**
+ * 瀹夊叏绉橀挜
+ */
+ private String password;
+
+ /**
+ * 鍏挜
+ */
+ private String publicKey;
+
+ /**
+ * 绉侀挜
+ */
+ private String privateKey;
+
+ /**
+ * 缂栫爜鏂瑰紡锛宐ase64/hex
+ */
+ private EncodeType encode;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java
new file mode 100755
index 0000000..ff0fbc8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java
@@ -0,0 +1,313 @@
+package org.dromara.common.encrypt.utils;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.SmUtil;
+import cn.hutool.crypto.asymmetric.KeyType;
+import cn.hutool.crypto.asymmetric.RSA;
+import cn.hutool.crypto.asymmetric.SM2;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 瀹夊叏鐩稿叧宸ュ叿绫�
+ *
+ * @author 鑰侀┈
+ */
+public class EncryptUtils {
+
+ /**
+ * 鍏挜
+ */
+ public static final String PUBLIC_KEY = "publicKey";
+
+ /**
+ * 绉侀挜
+ */
+ public static final String PRIVATE_KEY = "privateKey";
+
+ /**
+ * Base64鍔犲瘑
+ *
+ * @param data 寰呭姞瀵嗘暟鎹�
+ * @return 鍔犲瘑鍚庡瓧绗︿覆
+ */
+ public static String encryptByBase64(String data) {
+ return Base64.encode(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Base64瑙e瘑
+ *
+ * @param data 寰呰В瀵嗘暟鎹�
+ * @return 瑙e瘑鍚庡瓧绗︿覆
+ */
+ public static String decryptByBase64(String data) {
+ return Base64.decodeStr(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * AES鍔犲瘑
+ *
+ * @param data 寰呭姞瀵嗘暟鎹�
+ * @param password 绉橀挜瀛楃涓�
+ * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Base64缂栫爜
+ */
+ public static String encryptByAes(String data, String password) {
+ if (StrUtil.isBlank(password)) {
+ throw new IllegalArgumentException("AES闇�瑕佷紶鍏ョ閽ヤ俊鎭�");
+ }
+ // aes绠楁硶鐨勭閽ヨ姹傛槸16浣嶃��24浣嶃��32浣�
+ int[] array = {16, 24, 32};
+ if (!ArrayUtil.contains(array, password.length())) {
+ throw new IllegalArgumentException("AES绉橀挜闀垮害瑕佹眰涓�16浣嶃��24浣嶃��32浣�");
+ }
+ return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * AES鍔犲瘑
+ *
+ * @param data 寰呭姞瀵嗘暟鎹�
+ * @param password 绉橀挜瀛楃涓�
+ * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Hex缂栫爜
+ */
+ public static String encryptByAesHex(String data, String password) {
+ if (StrUtil.isBlank(password)) {
+ throw new IllegalArgumentException("AES闇�瑕佷紶鍏ョ閽ヤ俊鎭�");
+ }
+ // aes绠楁硶鐨勭閽ヨ姹傛槸16浣嶃��24浣嶃��32浣�
+ int[] array = {16, 24, 32};
+ if (!ArrayUtil.contains(array, password.length())) {
+ throw new IllegalArgumentException("AES绉橀挜闀垮害瑕佹眰涓�16浣嶃��24浣嶃��32浣�");
+ }
+ return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * AES瑙e瘑
+ *
+ * @param data 寰呰В瀵嗘暟鎹�
+ * @param password 绉橀挜瀛楃涓�
+ * @return 瑙e瘑鍚庡瓧绗︿覆
+ */
+ public static String decryptByAes(String data, String password) {
+ if (StrUtil.isBlank(password)) {
+ throw new IllegalArgumentException("AES闇�瑕佷紶鍏ョ閽ヤ俊鎭�");
+ }
+ // aes绠楁硶鐨勭閽ヨ姹傛槸16浣嶃��24浣嶃��32浣�
+ int[] array = {16, 24, 32};
+ if (!ArrayUtil.contains(array, password.length())) {
+ throw new IllegalArgumentException("AES绉橀挜闀垮害瑕佹眰涓�16浣嶃��24浣嶃��32浣�");
+ }
+ return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * SM4鍔犲瘑锛圔ase64缂栫爜锛�
+ *
+ * @param data 寰呭姞瀵嗘暟鎹�
+ * @param password 绉橀挜瀛楃涓�
+ * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Base64缂栫爜
+ */
+ public static String encryptBySm4(String data, String password) {
+ if (StrUtil.isBlank(password)) {
+ throw new IllegalArgumentException("SM4闇�瑕佷紶鍏ョ閽ヤ俊鎭�");
+ }
+ // sm4绠楁硶鐨勭閽ヨ姹傛槸16浣嶉暱搴�
+ int sm4PasswordLength = 16;
+ if (sm4PasswordLength != password.length()) {
+ throw new IllegalArgumentException("SM4绉橀挜闀垮害瑕佹眰涓�16浣�");
+ }
+ return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * SM4鍔犲瘑锛圚ex缂栫爜锛�
+ *
+ * @param data 寰呭姞瀵嗘暟鎹�
+ * @param password 绉橀挜瀛楃涓�
+ * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Hex缂栫爜
+ */
+ public static String encryptBySm4Hex(String data, String password) {
+ if (StrUtil.isBlank(password)) {
+ throw new IllegalArgumentException("SM4闇�瑕佷紶鍏ョ閽ヤ俊鎭�");
+ }
+ // sm4绠楁硶鐨勭閽ヨ姹傛槸16浣嶉暱搴�
+ int sm4PasswordLength = 16;
+ if (sm4PasswordLength != password.length()) {
+ throw new IllegalArgumentException("SM4绉橀挜闀垮害瑕佹眰涓�16浣�");
+ }
+ return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * sm4瑙e瘑
+ *
+ * @param data 寰呰В瀵嗘暟鎹紙鍙互鏄疊ase64鎴朒ex缂栫爜锛�
+ * @param password 绉橀挜瀛楃涓�
+ * @return 瑙e瘑鍚庡瓧绗︿覆
+ */
+ public static String decryptBySm4(String data, String password) {
+ if (StrUtil.isBlank(password)) {
+ throw new IllegalArgumentException("SM4闇�瑕佷紶鍏ョ閽ヤ俊鎭�");
+ }
+ // sm4绠楁硶鐨勭閽ヨ姹傛槸16浣嶉暱搴�
+ int sm4PasswordLength = 16;
+ if (sm4PasswordLength != password.length()) {
+ throw new IllegalArgumentException("SM4绉橀挜闀垮害瑕佹眰涓�16浣�");
+ }
+ return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * 浜х敓sm2鍔犺В瀵嗛渶瑕佺殑鍏挜鍜岀閽�
+ *
+ * @return 鍏閽ap
+ */
+ public static Map<String, String> generateSm2Key() {
+ Map<String, String> keyMap = new HashMap<>(2);
+ SM2 sm2 = SmUtil.sm2();
+ keyMap.put(PRIVATE_KEY, sm2.getPrivateKeyBase64());
+ keyMap.put(PUBLIC_KEY, sm2.getPublicKeyBase64());
+ return keyMap;
+ }
+
+ /**
+ * sm2鍏挜鍔犲瘑
+ *
+ * @param data 寰呭姞瀵嗘暟鎹�
+ * @param publicKey 鍏挜
+ * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Base64缂栫爜
+ */
+ public static String encryptBySm2(String data, String publicKey) {
+ if (StrUtil.isBlank(publicKey)) {
+ throw new IllegalArgumentException("SM2闇�瑕佷紶鍏ュ叕閽ヨ繘琛屽姞瀵�");
+ }
+ SM2 sm2 = SmUtil.sm2(null, publicKey);
+ return sm2.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
+ }
+
+ /**
+ * sm2鍏挜鍔犲瘑
+ *
+ * @param data 寰呭姞瀵嗘暟鎹�
+ * @param publicKey 鍏挜
+ * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Hex缂栫爜
+ */
+ public static String encryptBySm2Hex(String data, String publicKey) {
+ if (StrUtil.isBlank(publicKey)) {
+ throw new IllegalArgumentException("SM2闇�瑕佷紶鍏ュ叕閽ヨ繘琛屽姞瀵�");
+ }
+ SM2 sm2 = SmUtil.sm2(null, publicKey);
+ return sm2.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey);
+ }
+
+ /**
+ * sm2绉侀挜瑙e瘑
+ *
+ * @param data 寰呰В瀵嗘暟鎹�
+ * @param privateKey 绉侀挜
+ * @return 瑙e瘑鍚庡瓧绗︿覆
+ */
+ public static String decryptBySm2(String data, String privateKey) {
+ if (StrUtil.isBlank(privateKey)) {
+ throw new IllegalArgumentException("SM2闇�瑕佷紶鍏ョ閽ヨ繘琛岃В瀵�");
+ }
+ SM2 sm2 = SmUtil.sm2(privateKey, null);
+ return sm2.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * 浜х敓RSA鍔犺В瀵嗛渶瑕佺殑鍏挜鍜岀閽�
+ *
+ * @return 鍏閽ap
+ */
+ public static Map<String, String> generateRsaKey() {
+ Map<String, String> keyMap = new HashMap<>(2);
+ RSA rsa = SecureUtil.rsa();
+ keyMap.put(PRIVATE_KEY, rsa.getPrivateKeyBase64());
+ keyMap.put(PUBLIC_KEY, rsa.getPublicKeyBase64());
+ return keyMap;
+ }
+
+ /**
+ * rsa鍏挜鍔犲瘑
+ *
+ * @param data 寰呭姞瀵嗘暟鎹�
+ * @param publicKey 鍏挜
+ * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Base64缂栫爜
+ */
+ public static String encryptByRsa(String data, String publicKey) {
+ if (StrUtil.isBlank(publicKey)) {
+ throw new IllegalArgumentException("RSA闇�瑕佷紶鍏ュ叕閽ヨ繘琛屽姞瀵�");
+ }
+ RSA rsa = SecureUtil.rsa(null, publicKey);
+ return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
+ }
+
+ /**
+ * rsa鍏挜鍔犲瘑
+ *
+ * @param data 寰呭姞瀵嗘暟鎹�
+ * @param publicKey 鍏挜
+ * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Hex缂栫爜
+ */
+ public static String encryptByRsaHex(String data, String publicKey) {
+ if (StrUtil.isBlank(publicKey)) {
+ throw new IllegalArgumentException("RSA闇�瑕佷紶鍏ュ叕閽ヨ繘琛屽姞瀵�");
+ }
+ RSA rsa = SecureUtil.rsa(null, publicKey);
+ return rsa.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey);
+ }
+
+ /**
+ * rsa绉侀挜瑙e瘑
+ *
+ * @param data 寰呰В瀵嗘暟鎹�
+ * @param privateKey 绉侀挜
+ * @return 瑙e瘑鍚庡瓧绗︿覆
+ */
+ public static String decryptByRsa(String data, String privateKey) {
+ if (StrUtil.isBlank(privateKey)) {
+ throw new IllegalArgumentException("RSA闇�瑕佷紶鍏ョ閽ヨ繘琛岃В瀵�");
+ }
+ RSA rsa = SecureUtil.rsa(privateKey, null);
+ return rsa.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * md5鍔犲瘑
+ *
+ * @param data 寰呭姞瀵嗘暟鎹�
+ * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Hex缂栫爜
+ */
+ public static String encryptByMd5(String data) {
+ return SecureUtil.md5(data);
+ }
+
+ /**
+ * sha256鍔犲瘑
+ *
+ * @param data 寰呭姞瀵嗘暟鎹�
+ * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Hex缂栫爜
+ */
+ public static String encryptBySha256(String data) {
+ return SecureUtil.sha256(data);
+ }
+
+ /**
+ * sm3鍔犲瘑
+ *
+ * @param data 寰呭姞瀵嗘暟鎹�
+ * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Hex缂栫爜
+ */
+ public static String encryptBySm3(String data) {
+ return SmUtil.sm3(data);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..132cf29
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,3 @@
+org.dromara.common.encrypt.config.EncryptorAutoConfiguration
+org.dromara.common.encrypt.config.ApiDecryptAutoConfiguration
+
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/.flattened-pom.xml
new file mode 100644
index 0000000..8776ac2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/.flattened-pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-excel</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-excel</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>cn.idev.excel</groupId>
+ <artifactId>fastexcel</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/pom.xml
new file mode 100755
index 0000000..47ba528
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-excel</artifactId>
+
+ <description>
+ ruoyi-common-excel
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.idev.excel</groupId>
+ <artifactId>fastexcel</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/CellMerge.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/CellMerge.java
new file mode 100755
index 0000000..6b9211b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/CellMerge.java
@@ -0,0 +1,29 @@
+package org.dromara.common.excel.annotation;
+
+import org.dromara.common.excel.core.CellMergeStrategy;
+
+import java.lang.annotation.*;
+
+/**
+ * excel 鍒楀崟鍏冩牸鍚堝苟(鍚堝苟鍒楃浉鍚岄」)
+ *
+ * 闇�鎼厤 {@link CellMergeStrategy} 绛栫暐浣跨敤
+ *
+ * @author Lion Li
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface CellMerge {
+
+ /**
+ * col index
+ */
+ int index() default -1;
+
+ /**
+ * 鍚堝苟闇�瑕佷緷璧栫殑鍏朵粬瀛楁鍚嶇О
+ */
+ String[] mergeBy() default {};
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDictFormat.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDictFormat.java
new file mode 100755
index 0000000..5c51842
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDictFormat.java
@@ -0,0 +1,32 @@
+package org.dromara.common.excel.annotation;
+
+import org.dromara.common.core.utils.StringUtils;
+
+import java.lang.annotation.*;
+
+/**
+ * 瀛楀吀鏍煎紡鍖�
+ *
+ * @author Lion Li
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelDictFormat {
+
+ /**
+ * 濡傛灉鏄瓧鍏哥被鍨嬶紝璇疯缃瓧鍏哥殑type鍊� (濡�: sys_user_sex)
+ */
+ String dictType() default "";
+
+ /**
+ * 璇诲彇鍐呭杞〃杈惧紡 (濡�: 0=鐢�,1=濂�,2=鏈煡)
+ */
+ String readConverterExp() default "";
+
+ /**
+ * 鍒嗛殧绗︼紝璇诲彇瀛楃涓茬粍鍐呭
+ */
+ String separator() default StringUtils.SEPARATOR;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDynamicOptions.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDynamicOptions.java
new file mode 100755
index 0000000..c26bd68
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDynamicOptions.java
@@ -0,0 +1,23 @@
+package org.dromara.common.excel.annotation;
+
+import org.dromara.common.excel.core.ExcelOptionsProvider;
+
+import java.lang.annotation.*;
+
+/**
+ * Excel鍔ㄦ�佷笅鎷夐�夐」娉ㄨВ
+ *
+ * @author Angus
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelDynamicOptions {
+
+ /**
+ * 鎻愪緵鑰呯被鍏ㄩ檺瀹氬悕
+ * <p>
+ * {@link org.dromara.common.excel.core.ExcelOptionsProvider} 鎺ュ彛瀹炵幇绫� class
+ */
+ Class<? extends ExcelOptionsProvider> providerClass();
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelEnumFormat.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelEnumFormat.java
new file mode 100755
index 0000000..290379d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelEnumFormat.java
@@ -0,0 +1,30 @@
+package org.dromara.common.excel.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 鏋氫妇鏍煎紡鍖�
+ *
+ * @author Liang
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelEnumFormat {
+
+ /**
+ * 瀛楀吀鏋氫妇绫诲瀷
+ */
+ Class<? extends Enum<?>> enumClass();
+
+ /**
+ * 瀛楀吀鏋氫妇绫讳腑瀵瑰簲鐨刢ode灞炴�у悕绉帮紝榛樿涓篶ode
+ */
+ String codeField() default "code";
+
+ /**
+ * 瀛楀吀鏋氫妇绫讳腑瀵瑰簲鐨則ext灞炴�у悕绉帮紝榛樿涓簍ext
+ */
+ String textField() default "text";
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelNotation.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelNotation.java
new file mode 100755
index 0000000..ed42371
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelNotation.java
@@ -0,0 +1,20 @@
+package org.dromara.common.excel.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 鎵规敞 姝ゆ敞瑙d粎鐢ㄤ簬鍗曡〃澶� 涓嶆敮鎸佸灞傜骇琛ㄥご
+ * @author guzhouyanyu
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcelNotation {
+
+ /**
+ * 鎵规敞鍐呭
+ */
+ String value() default "";
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelRequired.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelRequired.java
new file mode 100755
index 0000000..ca8083b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelRequired.java
@@ -0,0 +1,22 @@
+package org.dromara.common.excel.annotation;
+
+import org.apache.poi.ss.usermodel.IndexedColors;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 鏄惁蹇呭~ 姝ゆ敞瑙d粎鐢ㄤ簬鍗曡〃澶� 涓嶆敮鎸佸灞傜骇琛ㄥご
+ * @author guzhouyanyu
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcelRequired {
+
+ /**
+ * 瀛椾綋棰滆壊
+ */
+ IndexedColors fontColor() default IndexedColors.RED;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelBigNumberConvert.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelBigNumberConvert.java
new file mode 100755
index 0000000..b88c3e4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelBigNumberConvert.java
@@ -0,0 +1,52 @@
+package org.dromara.common.excel.convert;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+
+/**
+ * 澶ф暟鍊艰浆鎹�
+ * Excel 鏁板�奸暱搴︿綅15浣� 澶т簬15浣嶇殑鏁板�艰浆鎹綅瀛楃涓�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class ExcelBigNumberConvert implements Converter<Long> {
+
+ @Override
+ public Class<Long> supportJavaTypeKey() {
+ return Long.class;
+ }
+
+ @Override
+ public CellDataTypeEnum supportExcelTypeKey() {
+ return null;
+ }
+
+ @Override
+ public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ return Convert.toLong(cellData.getData());
+ }
+
+ @Override
+ public WriteCellData<Object> convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ if (ObjectUtil.isNotNull(object)) {
+ String str = Convert.toStr(object);
+ if (str.length() > 15) {
+ return new WriteCellData<>(str);
+ }
+ }
+ WriteCellData<Object> cellData = new WriteCellData<>(new BigDecimal(object));
+ cellData.setType(CellDataTypeEnum.NUMBER);
+ return cellData;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java
new file mode 100755
index 0000000..c54816f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java
@@ -0,0 +1,73 @@
+package org.dromara.common.excel.convert;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.excel.utils.ExcelUtil;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+
+/**
+ * 瀛楀吀鏍煎紡鍖栬浆鎹㈠鐞�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class ExcelDictConvert implements Converter<Object> {
+
+ @Override
+ public Class<Object> supportJavaTypeKey() {
+ return Object.class;
+ }
+
+ @Override
+ public CellDataTypeEnum supportExcelTypeKey() {
+ return null;
+ }
+
+ @Override
+ public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ ExcelDictFormat anno = getAnnotation(contentProperty.getField());
+ String type = anno.dictType();
+ String label = cellData.getStringValue();
+ String value;
+ if (StringUtils.isBlank(type)) {
+ value = ExcelUtil.reverseByExp(label, anno.readConverterExp(), anno.separator());
+ } else {
+ value = SpringUtils.getBean(DictService.class).getDictValue(type, label, anno.separator());
+ }
+ return Convert.convert(contentProperty.getField().getType(), value);
+ }
+
+ @Override
+ public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ if (ObjectUtil.isNull(object)) {
+ return new WriteCellData<>("");
+ }
+ ExcelDictFormat anno = getAnnotation(contentProperty.getField());
+ String type = anno.dictType();
+ String value = Convert.toStr(object);
+ String label;
+ if (StringUtils.isBlank(type)) {
+ label = ExcelUtil.convertByExp(value, anno.readConverterExp(), anno.separator());
+ } else {
+ label = SpringUtils.getBean(DictService.class).getDictLabel(type, value, anno.separator());
+ }
+ return new WriteCellData<>(label);
+ }
+
+ private ExcelDictFormat getAnnotation(Field field) {
+ return AnnotationUtil.getAnnotation(field, ExcelDictFormat.class);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java
new file mode 100755
index 0000000..5723e61
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java
@@ -0,0 +1,87 @@
+package org.dromara.common.excel.convert;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import org.dromara.common.excel.annotation.ExcelEnumFormat;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 鏋氫妇鏍煎紡鍖栬浆鎹㈠鐞�
+ *
+ * @author Liang
+ */
+@Slf4j
+public class ExcelEnumConvert implements Converter<Object> {
+
+ @Override
+ public Class<Object> supportJavaTypeKey() {
+ return Object.class;
+ }
+
+ @Override
+ public CellDataTypeEnum supportExcelTypeKey() {
+ return null;
+ }
+
+ @Override
+ public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ cellData.checkEmpty();
+ // Excel涓~鍏ョ殑鏄灇涓句腑鎸囧畾鐨勬弿杩�
+ Object textValue = switch (cellData.getType()) {
+ case STRING, DIRECT_STRING, RICH_TEXT_STRING -> cellData.getStringValue();
+ case NUMBER -> cellData.getNumberValue();
+ case BOOLEAN -> cellData.getBooleanValue();
+ default -> throw new IllegalArgumentException("鍗曞厓鏍肩被鍨嬪紓甯�!");
+ };
+ // 濡傛灉鏄┖鍊�
+ if (ObjectUtil.isNull(textValue)) {
+ return null;
+ }
+ Map<Object, String> enumCodeToTextMap = beforeConvert(contentProperty);
+ // 浠嶫ava杈撳嚭鑷矱xcel鏄痗ode杞瑃ext
+ // 鍥犳浠嶦xcel杞琂ava搴旇灏唗ext涓巆ode瀵硅皟
+ Map<Object, Object> enumTextToCodeMap = new HashMap<>();
+ enumCodeToTextMap.forEach((key, value) -> enumTextToCodeMap.put(value, key));
+ // 搴旇浠巘ext -> code涓煡鎵�
+ Object codeValue = enumTextToCodeMap.get(textValue);
+ return Convert.convert(contentProperty.getField().getType(), codeValue);
+ }
+
+ @Override
+ public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ if (ObjectUtil.isNull(object)) {
+ return new WriteCellData<>("");
+ }
+ Map<Object, String> enumValueMap = beforeConvert(contentProperty);
+ String value = Convert.toStr(enumValueMap.get(object), "");
+ return new WriteCellData<>(value);
+ }
+
+ private Map<Object, String> beforeConvert(ExcelContentProperty contentProperty) {
+ ExcelEnumFormat anno = getAnnotation(contentProperty.getField());
+ Map<Object, String> enumValueMap = new HashMap<>();
+ Enum<?>[] enumConstants = anno.enumClass().getEnumConstants();
+ for (Enum<?> enumConstant : enumConstants) {
+ Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField());
+ String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField());
+ enumValueMap.put(codeValue, textValue);
+ }
+ return enumValueMap;
+ }
+
+ private ExcelEnumFormat getAnnotation(Field field) {
+ return AnnotationUtil.getAnnotation(field, ExcelEnumFormat.class);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java
new file mode 100755
index 0000000..6a1a3a7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java
@@ -0,0 +1,217 @@
+package org.dromara.common.excel.core;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.idev.excel.annotation.ExcelIgnore;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.SneakyThrows;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import org.dromara.common.excel.annotation.CellMerge;
+
+import java.lang.reflect.Field;
+import java.util.*;
+
+/**
+ * 鍗曞厓鏍煎悎骞跺鐞嗗櫒
+ *
+ * @author Lion Li
+ */
+public class CellMergeHandler {
+
+ private final boolean hasTitle;
+ private int rowIndex;
+
+ private CellMergeHandler(final boolean hasTitle) {
+ this.hasTitle = hasTitle;
+ // 琛屽悎骞跺紑濮嬩笅鏍�
+ this.rowIndex = hasTitle ? 1 : 0;
+ }
+ private CellMergeHandler(final boolean hasTitle, final int rowIndex) {
+ this.hasTitle = hasTitle;
+ this.rowIndex = hasTitle ? rowIndex : 0;
+ }
+ @SneakyThrows
+ public List<CellRangeAddress> handle(List<?> rows) {
+ // 濡傛灉鍏ュ弬涓虹┖闆嗗悎鍒欒繑鍥炵┖闆�
+ if (CollUtil.isEmpty(rows)) {
+ return Collections.emptyList();
+ }
+
+ // 鑾峰彇鏈夊悎骞舵敞瑙g殑瀛楁
+ Map<Field, FieldColumnIndex> mergeFields = getFieldColumnIndexMap(rows.get(0).getClass());
+ // 濡傛灉娌℃湁闇�瑕佸悎骞剁殑瀛楁鍒欒繑鍥炵┖闆�
+ if (CollUtil.isEmpty(mergeFields)) {
+ return Collections.emptyList();
+ }
+
+ // 缁撴灉闆�
+ List<CellRangeAddress> result = new ArrayList<>();
+
+ // 鐢熸垚涓や袱鍚堝苟鍗曞厓鏍�
+ Map<Field, RepeatCell> rowRepeatCellMap = new HashMap<>();
+ for (Map.Entry<Field, FieldColumnIndex> item : mergeFields.entrySet()) {
+ Field field = item.getKey();
+ FieldColumnIndex itemValue = item.getValue();
+ int colNum = itemValue.colIndex();
+ CellMerge cellMerge = itemValue.cellMerge();
+
+ for (int i = 0; i < rows.size(); i++) {
+ // 褰撳墠琛屾暟鎹�
+ Object currentRowObj = rows.get(i);
+ // 褰撳墠琛屾暟鎹瓧娈靛��
+ Object currentRowObjFieldVal = ReflectUtils.invokeGetter(currentRowObj, field.getName());
+
+ // 绌哄�艰烦杩囦笉澶勭悊
+ if (currentRowObjFieldVal == null || "".equals(currentRowObjFieldVal)) {
+ continue;
+ }
+
+ // 鍗曞厓鏍煎悎骞禡ap鏄惁瀛樺湪鏁版嵁锛屽鏋滀笉瀛樺湪鍒欐坊鍔犲綋鍓嶈鐨勫瓧娈靛��
+ if (!rowRepeatCellMap.containsKey(field)) {
+ rowRepeatCellMap.put(field, RepeatCell.of(currentRowObjFieldVal, i));
+ continue;
+ }
+
+ // 鑾峰彇 鍗曞厓鏍煎悎骞禡ap 涓瓧娈靛��
+ RepeatCell repeatCell = rowRepeatCellMap.get(field);
+ Object cellValue = repeatCell.value();
+ int current = repeatCell.current();
+
+ // 妫�鏌ユ槸鍚︽弧瓒冲悎骞舵潯浠�
+ // currentRowObj 褰撳墠琛屾暟鎹�
+ // rows.get(i - 1) 涓婁竴琛屾暟鎹� 娉細鐢变簬 if (!rowRepeatCellMap.containsKey(field)) 鏉′欢鐨勫瓨鍦紝鎵�浠ヨ i 蹇呬笉鍙兘灏忎簬1
+ // cellMerge 褰撳墠琛屽瓧娈靛悎骞舵敞瑙�
+ boolean merge = isMerge(currentRowObj, rows.get(i - 1), cellMerge);
+
+ // 鏄惁娣诲姞鍒扮粨鏋滈泦
+ boolean isAddResult = false;
+ // 鏈�鏂拌
+ int lastRow = i + rowIndex - 1;
+
+ // 濡傛灉褰撳墠琛屽瓧娈靛�煎拰缂撳瓨涓殑瀛楁鍊间笉鐩哥瓑锛屾垨涓嶆弧瓒冲悎骞舵潯浠讹紝鍒欐浛鎹�
+ if (!currentRowObjFieldVal.equals(cellValue) || !merge) {
+ rowRepeatCellMap.put(field, RepeatCell.of(currentRowObjFieldVal, i));
+ isAddResult = true;
+ }
+
+ // 濡傛灉鏈�鍚庝竴琛屼笉鑳藉悎骞讹紝妫�鏌ヤ箣鍓嶇殑鏁版嵁鏄惁闇�瑕佸悎骞讹紱濡傛灉鏈�鍚庝竴琛屽彲浠ュ悎骞讹紝鍒欑洿鎺ュ悎骞跺埌鏈�鍚�
+ if (i == rows.size() - 1) {
+ isAddResult = true;
+ if (i > current) {
+ lastRow = i + rowIndex;
+ }
+ }
+
+ if (isAddResult && i > current) {
+ //濡傛灉鏄悓涓�琛岋紝鍒欒烦杩囧悎骞�
+ if (current + rowIndex == lastRow) {
+ continue;
+ }
+ result.add(new CellRangeAddress(current + rowIndex, lastRow, colNum, colNum));
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 鑾峰彇甯︽湁鍚堝苟娉ㄨВ鐨勫瓧娈靛垪绱㈠紩鍜屽悎骞舵敞瑙d俊鎭疢ap闆�
+ */
+ private Map<Field, FieldColumnIndex> getFieldColumnIndexMap(Class<?> clazz) {
+ boolean annotationPresent = clazz.isAnnotationPresent(ExcelIgnoreUnannotated.class);
+ Field[] fields = ReflectUtils.getFields(clazz, field -> {
+ if ("serialVersionUID".equals(field.getName())) {
+ return false;
+ }
+ if (field.isAnnotationPresent(ExcelIgnore.class)) {
+ return false;
+ }
+ return !annotationPresent || field.isAnnotationPresent(ExcelProperty.class);
+ });
+
+ // 鏈夋敞瑙g殑瀛楁
+ Map<Field, FieldColumnIndex> mergeFields = new HashMap<>();
+ for (int i = 0; i < fields.length; i++) {
+ Field field = fields[i];
+ if (!field.isAnnotationPresent(CellMerge.class)) {
+ continue;
+ }
+ CellMerge cm = field.getAnnotation(CellMerge.class);
+ int index = cm.index() == -1 ? i : cm.index();
+ mergeFields.put(field, FieldColumnIndex.of(index, cm));
+
+ if (hasTitle) {
+ ExcelProperty property = field.getAnnotation(ExcelProperty.class);
+ rowIndex = Math.max(rowIndex, property.value().length);
+ }
+ }
+ return mergeFields;
+ }
+
+ private boolean isMerge(Object currentRow, Object preRow, CellMerge cellMerge) {
+ final String[] mergeBy = cellMerge.mergeBy();
+ if (StrUtil.isAllNotBlank(mergeBy)) {
+ // 姣斿褰撳墠琛屽拰涓婁竴琛岀殑鍚勪釜灞炴�у�间竴涓�姣斿 濡傛灉鍏ㄤ负鐪� 鍒欎负鐪�
+ for (String fieldName : mergeBy) {
+ final Object valCurrent = ReflectUtil.getFieldValue(currentRow, fieldName);
+ final Object valPre = ReflectUtil.getFieldValue(preRow, fieldName);
+ if (!Objects.equals(valPre, valCurrent)) {
+ // 渚濊禆瀛楁濡傛湁浠讳竴涓嶇瓑鍊�,鍒欐爣璁颁负涓嶅彲鍚堝苟
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 鍗曞厓鏍煎悎骞�
+ */
+ record RepeatCell(Object value, int current) {
+ static RepeatCell of(Object value, int current) {
+ return new RepeatCell(value, current);
+ }
+ }
+
+ /**
+ * 瀛楁鍒楃储寮曞拰鍚堝苟娉ㄨВ淇℃伅
+ */
+ record FieldColumnIndex(int colIndex, CellMerge cellMerge) {
+ static FieldColumnIndex of(int colIndex, CellMerge cellMerge) {
+ return new FieldColumnIndex(colIndex, cellMerge);
+ }
+ }
+ /**
+ * 鍒涘缓涓�涓崟鍏冩牸鍚堝苟澶勭悊鍣ㄥ疄渚�
+ *
+ * @param hasTitle 鏄惁鍚堝苟鏍囬
+ * @param rowIndex 琛岀储寮�
+ * @return 鍗曞厓鏍煎悎骞跺鐞嗗櫒
+ */
+ public static CellMergeHandler of(final boolean hasTitle, final int rowIndex) {
+ return new CellMergeHandler(hasTitle, rowIndex);
+ }
+
+ /**
+ * 鍒涘缓涓�涓崟鍏冩牸鍚堝苟澶勭悊鍣ㄥ疄渚�
+ *
+ * @param hasTitle 鏄惁鍚堝苟鏍囬
+ * @return 鍗曞厓鏍煎悎骞跺鐞嗗櫒
+ */
+ public static CellMergeHandler of(final boolean hasTitle) {
+ return new CellMergeHandler(hasTitle);
+ }
+
+ /**
+ * 鍒涘缓涓�涓崟鍏冩牸鍚堝苟澶勭悊鍣ㄥ疄渚嬶紙榛樿涓嶅悎骞舵爣棰橈級
+ *
+ * @return 鍗曞厓鏍煎悎骞跺鐞嗗櫒
+ */
+ public static CellMergeHandler of() {
+ return new CellMergeHandler(false);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java
new file mode 100755
index 0000000..64d8c77
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java
@@ -0,0 +1,65 @@
+package org.dromara.common.excel.core;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.idev.excel.metadata.Head;
+import cn.idev.excel.write.handler.SheetWriteHandler;
+import cn.idev.excel.write.merge.AbstractMergeStrategy;
+import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
+import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.util.CellRangeAddress;
+
+import java.util.List;
+
+/**
+ * 鍒楀�奸噸澶嶅悎骞剁瓥鐣�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class CellMergeStrategy extends AbstractMergeStrategy implements SheetWriteHandler {
+
+ private final List<CellRangeAddress> cellList;
+
+ public CellMergeStrategy(List<CellRangeAddress> cellList) {
+ this.cellList = cellList;
+ }
+
+ public CellMergeStrategy(List<?> list, boolean hasTitle) {
+ this.cellList = CellMergeHandler.of(hasTitle).handle(list);
+ }
+
+ public CellMergeStrategy(List<?> list, boolean hasTitle, int rowIndex) {
+ this.cellList = CellMergeHandler.of(hasTitle, rowIndex).handle(list);
+ }
+
+ @Override
+ protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
+ if (CollUtil.isEmpty(cellList)) {
+ return;
+ }
+ // 鍗曞厓鏍煎啓鍏ヤ簡,閬嶅巻鍚堝苟鍖哄煙,濡傛灉璇ell鍦ㄥ尯鍩熷唴,浣嗛潪棣栬,鍒欐竻绌�
+ final int rowIndex = cell.getRowIndex();
+ for (CellRangeAddress cellAddresses : cellList) {
+ final int firstRow = cellAddresses.getFirstRow();
+ if (cellAddresses.isInRange(cell) && rowIndex != firstRow) {
+ cell.setBlank();
+ }
+ }
+ }
+
+ @Override
+ public void afterSheetCreate(final WriteWorkbookHolder writeWorkbookHolder, final WriteSheetHolder writeSheetHolder) {
+ if (CollUtil.isEmpty(cellList)) {
+ return;
+ }
+ // 鍦� Sheet 鍒涘缓鏃舵彁鍓嶅啓鍏ュ悎骞跺尯鍩燂紱鍚庣画鍐欏叆鍙細褰卞搷棣栨牸锛屼笉浼氱Щ闄ゅ悎骞�
+ final Sheet sheet = writeSheetHolder.getSheet();
+ for (CellRangeAddress item : cellList) {
+ sheet.addMergedRegion(item);
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java
new file mode 100755
index 0000000..e715c5f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java
@@ -0,0 +1,104 @@
+package org.dromara.common.excel.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.idev.excel.context.AnalysisContext;
+import cn.idev.excel.event.AnalysisEventListener;
+import cn.idev.excel.exception.ExcelAnalysisException;
+import cn.idev.excel.exception.ExcelDataConvertException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Excel 瀵煎叆鐩戝惉
+ *
+ * @author Yjoioooo
+ * @author Lion Li
+ */
+@Slf4j
+@NoArgsConstructor
+public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements ExcelListener<T> {
+
+ /**
+ * 鏄惁Validator妫�楠岋紝榛樿涓烘槸
+ */
+ private Boolean isValidate = Boolean.TRUE;
+
+ /**
+ * excel 琛ㄥご鏁版嵁
+ */
+ private Map<Integer, String> headMap;
+
+ /**
+ * 瀵煎叆鍥炴墽
+ */
+ private ExcelResult<T> excelResult;
+
+ public DefaultExcelListener(boolean isValidate) {
+ this.excelResult = new DefaultExcelResult<>();
+ this.isValidate = isValidate;
+ }
+
+ /**
+ * 澶勭悊寮傚父
+ *
+ * @param exception ExcelDataConvertException
+ * @param context Excel 涓婁笅鏂�
+ */
+ @Override
+ public void onException(Exception exception, AnalysisContext context) throws Exception {
+ String errMsg = null;
+ if (exception instanceof ExcelDataConvertException excelDataConvertException) {
+ // 濡傛灉鏄煇涓�涓崟鍏冩牸鐨勮浆鎹㈠紓甯� 鑳借幏鍙栧埌鍏蜂綋琛屽彿
+ Integer rowIndex = excelDataConvertException.getRowIndex();
+ Integer columnIndex = excelDataConvertException.getColumnIndex();
+ errMsg = StrUtil.format("绗瑊}琛�-绗瑊}鍒�-琛ㄥご{}: 瑙f瀽寮傚父<br/>",
+ rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));
+ if (log.isDebugEnabled()) {
+ log.error(errMsg);
+ }
+ }
+ if (exception instanceof ConstraintViolationException constraintViolationException) {
+ Set<ConstraintViolation<?>> constraintViolations = constraintViolationException.getConstraintViolations();
+ String constraintViolationsMsg = StreamUtils.join(constraintViolations, ConstraintViolation::getMessage, ", ");
+ errMsg = StrUtil.format("绗瑊}琛屾暟鎹牎楠屽紓甯�: {}", context.readRowHolder().getRowIndex() + 1, constraintViolationsMsg);
+ if (log.isDebugEnabled()) {
+ log.error(errMsg);
+ }
+ }
+ excelResult.getErrorList().add(errMsg);
+ throw new ExcelAnalysisException(errMsg);
+ }
+
+ @Override
+ public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
+ this.headMap = headMap;
+ log.debug("瑙f瀽鍒颁竴鏉¤〃澶存暟鎹�: {}", JsonUtils.toJsonString(headMap));
+ }
+
+ @Override
+ public void invoke(T data, AnalysisContext context) {
+ if (isValidate) {
+ ValidatorUtils.validate(data);
+ }
+ excelResult.getList().add(data);
+ }
+
+ @Override
+ public void doAfterAllAnalysed(AnalysisContext context) {
+ log.debug("鎵�鏈夋暟鎹В鏋愬畬鎴愶紒");
+ }
+
+ @Override
+ public ExcelResult<T> getExcelResult() {
+ return excelResult;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelResult.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelResult.java
new file mode 100755
index 0000000..7373e12
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelResult.java
@@ -0,0 +1,73 @@
+package org.dromara.common.excel.core;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 榛樿excel杩斿洖瀵硅薄
+ *
+ * @author Yjoioooo
+ * @author Lion Li
+ */
+public class DefaultExcelResult<T> implements ExcelResult<T> {
+
+ /**
+ * 鏁版嵁瀵硅薄list
+ */
+ @Setter
+ private List<T> list;
+
+ /**
+ * 閿欒淇℃伅鍒楄〃
+ */
+ @Setter
+ private List<String> errorList;
+
+ public DefaultExcelResult() {
+ this.list = new ArrayList<>();
+ this.errorList = new ArrayList<>();
+ }
+
+ public DefaultExcelResult(List<T> list, List<String> errorList) {
+ this.list = list;
+ this.errorList = errorList;
+ }
+
+ public DefaultExcelResult(ExcelResult<T> excelResult) {
+ this.list = excelResult.getList();
+ this.errorList = excelResult.getErrorList();
+ }
+
+ @Override
+ public List<T> getList() {
+ return list;
+ }
+
+ @Override
+ public List<String> getErrorList() {
+ return errorList;
+ }
+
+ /**
+ * 鑾峰彇瀵煎叆鍥炴墽
+ *
+ * @return 瀵煎叆鍥炴墽
+ */
+ @Override
+ public String getAnalysis() {
+ int successCount = list.size();
+ int errorCount = errorList.size();
+ if (successCount == 0) {
+ return "璇诲彇澶辫触锛屾湭瑙f瀽鍒版暟鎹�";
+ } else {
+ if (errorCount == 0) {
+ return StrUtil.format("鎭枩鎮紝鍏ㄩ儴璇诲彇鎴愬姛锛佸叡{}鏉�", successCount);
+ } else {
+ return "";
+ }
+ }
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java
new file mode 100755
index 0000000..7cdb5c5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java
@@ -0,0 +1,150 @@
+package org.dromara.common.excel.core;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.exception.ServiceException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * <h1>Excel涓嬫媺鍙�夐」</h1>
+ * 娉ㄦ剰锛氫负纭繚涓嬫媺妗嗚В鏋愭纭紝浼犲�煎姟蹇呬娇鐢╟reateOptionValue()鍋氫负鍊肩殑鎷兼帴
+ *
+ * @author Emil.Zhang
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@SuppressWarnings("unused")
+public class DropDownOptions {
+ /**
+ * 涓�绾т笅鎷夋墍鍦ㄥ垪index锛屼粠0寮�濮嬬畻
+ */
+ private int index = 0;
+ /**
+ * 浜岀骇涓嬫媺鎵�鍦ㄧ殑index锛屼粠0寮�濮嬬畻锛屼笉鑳戒笌涓�绾х浉鍚�
+ */
+ private int nextIndex = 0;
+ /**
+ * 涓�绾т笅鎷夋墍鍖呭惈鐨勬暟鎹�
+ */
+ private List<String> options = new ArrayList<>();
+ /**
+ * 浜岀骇涓嬫媺鎵�鍖呭惈鐨勬暟鎹甅ap
+ * <p>浠ユ瘡涓�涓竴绾ч�夐」鍊间负Key锛屾瘡涓竴绾ч�夐」瀵瑰簲鐨勪簩绾ф暟鎹负Value</p>
+ */
+ private Map<String, List<String>> nextOptions = new HashMap<>();
+ /**
+ * 鍒嗛殧绗�
+ */
+ private static final String DELIMITER = "_";
+
+ /**
+ * 鍒涘缓鍙湁涓�绾х殑涓嬫媺閫�
+ */
+ public DropDownOptions(int index, List<String> options) {
+ this.index = index;
+ this.options = options;
+ }
+
+ /**
+ * <h2>鍒涘缓姣忎釜閫夐」鍙�夊��</h2>
+ * <p>娉ㄦ剰锛氫笉鑳戒互鏁板瓧锛岀壒娈婄鍙峰紑澶达紝閫夐」涓笉鍙互鍖呭惈浠讳綍杩愮畻绗﹀彿</p>
+ *
+ * @param vars 鍙�夊�煎唴鍖呭惈鐨勫弬鏁�
+ * @return 鍚堣鐨勫彲閫夊��
+ */
+ public static String createOptionValue(Object... vars) {
+ StringBuilder stringBuffer = new StringBuilder();
+ String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
+ for (int i = 0; i < vars.length; i++) {
+ String var = StrUtil.trimToEmpty(Convert.toStr(vars[i]));
+ if (!var.matches(regex)) {
+ throw new ServiceException("閫夐」鏁版嵁涓嶇鍚堣鍒欙紝浠呭厑璁镐娇鐢ㄤ腑鑻辨枃瀛楃浠ュ強鏁板瓧");
+ }
+ stringBuffer.append(var);
+ if (i < vars.length - 1) {
+ // 鐩磋嚦鏈�鍚庝竴涓墠锛岄兘浠浣滀负鍒囧壊绾�
+ stringBuffer.append(DELIMITER);
+ }
+ }
+ if (stringBuffer.toString().matches("^\\d_*$")) {
+ throw new ServiceException("绂佹浠ユ暟瀛楀紑澶�");
+ }
+ return stringBuffer.toString();
+ }
+
+ /**
+ * 灏嗗鐞嗗悗鍚堢悊鐨勫彲閫夊�艰В鏋愪负鍘熷鐨勫弬鏁�
+ *
+ * @param option 缁忚繃澶勭悊鍚庣殑鍚堢悊鐨勫彲閫夐」
+ * @return 鍘熷鐨勫弬鏁�
+ */
+ public static List<String> analyzeOptionValue(String option) {
+ return StrUtil.split(option, DELIMITER, true, true);
+ }
+
+ /**
+ * 鍒涘缓绾ц仈涓嬫媺閫夐」
+ *
+ * @param parentList 鐖跺疄浣撳彲閫夐」鍘熷鏁版嵁
+ * @param parentIndex 鐖朵笅鎷夐�変綅缃�
+ * @param sonList 瀛愬疄浣撳彲閫夐」鍘熷鏁版嵁
+ * @param sonIndex 瀛愪笅鎷夐�変綅缃�
+ * @param parentHowToGetIdFunction 鐖剁被濡備綍鑾峰彇鍞竴鏍囪瘑
+ * @param sonHowToGetParentIdFunction 瀛愮被濡備綍鑾峰彇鐖剁被鐨勫敮涓�鏍囪瘑
+ * @param howToBuildEveryOption 濡備綍鐢熸垚涓嬫媺閫夊唴瀹�
+ * @return 绾ц仈涓嬫媺閫夐」
+ */
+ public static <T> DropDownOptions buildLinkedOptions(List<T> parentList,
+ int parentIndex,
+ List<T> sonList,
+ int sonIndex,
+ Function<T, Number> parentHowToGetIdFunction,
+ Function<T, Number> sonHowToGetParentIdFunction,
+ Function<T, String> howToBuildEveryOption) {
+ DropDownOptions parentLinkSonOptions = new DropDownOptions();
+ // 鍏堝垱寤虹埗绫荤殑涓嬫媺
+ parentLinkSonOptions.setIndex(parentIndex);
+ parentLinkSonOptions.setOptions(
+ parentList.stream()
+ .map(howToBuildEveryOption)
+ .collect(Collectors.toList())
+ );
+ // 鎻愬彇鐖�-瀛愮骇鑱斾笅鎷�
+ Map<String, List<String>> sonOptions = new HashMap<>();
+ // 鐖剁骇渚濇嵁鑷繁鐨処D鍒嗙粍
+ Map<Number, List<T>> parentGroupByIdMap =
+ parentList.stream().collect(Collectors.groupingBy(parentHowToGetIdFunction));
+ // 閬嶅巻姣忎釜瀛愰泦锛屾彁鍙栧埌Map涓�
+ sonList.forEach(everySon -> {
+ if (parentGroupByIdMap.containsKey(sonHowToGetParentIdFunction.apply(everySon))) {
+ // 鎵惧埌瀵瑰簲鐨勪笂绾�
+ T parentObj = parentGroupByIdMap.get(sonHowToGetParentIdFunction.apply(everySon)).get(0);
+ // 鎻愬彇鍚嶇О鍜孖D浣滀负Key
+ String key = howToBuildEveryOption.apply(parentObj);
+ // Key瀵瑰簲鐨刅alue
+ List<String> thisParentSonOptionList;
+ if (sonOptions.containsKey(key)) {
+ thisParentSonOptionList = sonOptions.get(key);
+ } else {
+ thisParentSonOptionList = new ArrayList<>();
+ sonOptions.put(key, thisParentSonOptionList);
+ }
+ // 寰�Value涓坊鍔犲綋鍓嶅瓙闆嗛�夐」
+ thisParentSonOptionList.add(howToBuildEveryOption.apply(everySon));
+ }
+ });
+ parentLinkSonOptions.setNextIndex(sonIndex);
+ parentLinkSonOptions.setNextOptions(sonOptions);
+ return parentLinkSonOptions;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java
new file mode 100755
index 0000000..05c79c4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java
@@ -0,0 +1,412 @@
+package org.dromara.common.excel.core;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.EnumUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.idev.excel.metadata.FieldCache;
+import cn.idev.excel.metadata.FieldWrapper;
+import cn.idev.excel.util.ClassUtils;
+import cn.idev.excel.write.handler.SheetWriteHandler;
+import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
+import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.ss.util.WorkbookUtil;
+import org.apache.poi.xssf.usermodel.XSSFDataValidation;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.annotation.ExcelDynamicOptions;
+import org.dromara.common.excel.annotation.ExcelEnumFormat;
+
+import java.lang.reflect.Field;
+import java.util.*;
+
+/**
+ * <h1>Excel琛ㄦ牸涓嬫媺閫夋搷浣�</h1>
+ * 鑰冭檻鍒颁笅鎷夐�夎繃澶氬彲鑳藉鑷碋xcel鎵撳紑缂撴參鐨勯棶棰橈紝鍙牎楠屽墠1000琛�
+ * <p>
+ * 鍗冲彧鏈夊墠1000琛岀殑鏁版嵁鍙互鐢ㄤ笅鎷夋锛岃秴鍑虹殑鑷閫氳繃闄愬埗鏁版嵁閲忕殑褰㈠紡锛岀浜屾杈撳嚭
+ *
+ * @author Emil.Zhang
+ */
+@Slf4j
+public class ExcelDownHandler implements SheetWriteHandler {
+
+ /**
+ * Excel琛ㄦ牸涓殑鍒楀悕鑻辨枃
+ * 浠呬负浜嗚В鏋愬垪鑻辨枃锛岀姝慨鏀�
+ */
+ private static final String EXCEL_COLUMN_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ /**
+ * 鍗曢�夋暟鎹甋heet鍚�
+ */
+ private static final String OPTIONS_SHEET_NAME = "options";
+ /**
+ * 鑱斿姩閫夋嫨鏁版嵁Sheet鍚嶇殑澶�
+ */
+ private static final String LINKED_OPTIONS_SHEET_NAME = "linkedOptions";
+ /**
+ * 涓嬫媺鍙�夐」
+ */
+ private final List<DropDownOptions> dropDownOptions;
+ private final DictService dictService;
+ /**
+ * 褰撳墠鍗曢�夎繘搴�
+ */
+ private int currentOptionsColumnIndex;
+ /**
+ * 褰撳墠鑱斿姩閫夋嫨杩涘害
+ */
+ private int currentLinkedOptionsSheetIndex;
+
+ public ExcelDownHandler(List<DropDownOptions> options) {
+ this.dropDownOptions = options;
+ this.currentOptionsColumnIndex = 0;
+ this.currentLinkedOptionsSheetIndex = 0;
+ this.dictService = SpringUtils.getBean(DictService.class);
+ }
+
+ /**
+ * <h2>寮�濮嬪垱寤轰笅鎷夋暟鎹�</h2>
+ * 1.閫氳繃瑙f瀽浼犲叆鐨凘ExcelProperty鍚岀骇鏄惁鏍囨敞鏈堾DropDown閫夐」
+ * 濡傛灉鏈変笖璁剧疆浜唙alue鍊硷紝鍒欏皢鍏剁洿鎺ョ疆涓轰笅鎷夊彲閫夐」
+ * <p>
+ * 2.鎴栬�呭湪璋冪敤ExcelUtil鏃舵寚瀹氫簡鍙�夐」锛屽皢渚濇嵁浼犲叆鐨勫彲閫夐」鍋氫笅鎷�
+ * <p>
+ * 3.浜岃�呭苟瀛橈紝娉ㄦ剰璋冪敤鏂瑰紡
+ */
+ @Override
+ public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
+ Sheet sheet = writeSheetHolder.getSheet();
+ // 寮�濮嬭缃笅鎷夋 HSSFWorkbook
+ DataValidationHelper helper = sheet.getDataValidationHelper();
+ Workbook workbook = writeWorkbookHolder.getWorkbook();
+ FieldCache fieldCache = ClassUtils.declaredFields(writeWorkbookHolder.getClazz(), writeWorkbookHolder);
+ for (Map.Entry<Integer, FieldWrapper> entry : fieldCache.getSortedFieldMap().entrySet()) {
+ Integer index = entry.getKey();
+ FieldWrapper wrapper = entry.getValue();
+ Field field = wrapper.getField();
+ // 寰幆瀹炰綋涓殑姣忎釜灞炴��
+ // 鍙�夌殑涓嬫媺鍊�
+ List<String> options = new ArrayList<>();
+ if (field.isAnnotationPresent(ExcelDictFormat.class)) {
+ // 濡傛灉鎸囧畾浜咢ExcelDictFormat锛屽垯浣跨敤瀛楀吀鐨勯�昏緫
+ ExcelDictFormat format = field.getDeclaredAnnotation(ExcelDictFormat.class);
+ String dictType = format.dictType();
+ String converterExp = format.readConverterExp();
+ if (StringUtils.isNotBlank(dictType)) {
+ // 濡傛灉浼犻�掍簡瀛楀吀鍚嶏紝鍒欎緷鎹瓧鍏稿缓绔嬩笅鎷�
+ Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
+ .orElseThrow(() -> new ServiceException("瀛楀吀 {} 涓嶅瓨鍦�", dictType))
+ .values();
+ options = new ArrayList<>(values);
+ } else if (StringUtils.isNotBlank(converterExp)) {
+ // 濡傛灉鎸囧畾浜嗙‘鍒囩殑鍊硷紝鍒欑洿鎺ヨВ鏋愮‘鍒囩殑鍊�
+ List<String> strList = StringUtils.splitList(converterExp, format.separator());
+ options = StreamUtils.toList(strList, s -> StringUtils.split(s, "=")[1]);
+ }
+ } else if (field.isAnnotationPresent(ExcelEnumFormat.class)) {
+ // 鍚﹀垯濡傛灉鎸囧畾浜咢ExcelEnumFormat锛屽垯浣跨敤鏋氫妇鐨勯�昏緫
+ ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
+ List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
+ options = StreamUtils.toList(values, Convert::toStr);
+ } else if (field.isAnnotationPresent(ExcelDynamicOptions.class)) {
+ // 澶勭悊鍔ㄦ�佷笅鎷夐�夐」
+ ExcelDynamicOptions dynamicOptions = field.getDeclaredAnnotation(ExcelDynamicOptions.class);
+ // 鑾峰彇鎻愪緵鑰呭疄渚�
+ ExcelOptionsProvider provider = SpringUtils.getBean(dynamicOptions.providerClass());
+ Set<String> providerOptions = provider.getOptions();
+ if (CollUtil.isNotEmpty(providerOptions)) {
+ options = new ArrayList<>(providerOptions);
+ }
+ }
+ if (ObjectUtil.isNotEmpty(options)) {
+ // 浠呭綋涓嬫媺鍙�夐」涓嶄负绌烘椂鎵ц
+ if (options.size() > 20) {
+ // 杩欓噷闄愬埗濡傛灉鍙�夐」澶т簬20锛屽垯浣跨敤棰濆琛ㄥ舰寮�
+ dropDownWithSheet(helper, workbook, sheet, index, options);
+ } else {
+ // 鍚﹀垯浣跨敤鍥哄畾鍊煎舰寮�
+ dropDownWithSimple(helper, sheet, index, options);
+ }
+ }
+ }
+ if (CollUtil.isEmpty(dropDownOptions)) {
+ return;
+ }
+ dropDownOptions.forEach(everyOptions -> {
+ // 濡傛灉浼犻�掍簡涓嬫媺妗嗛�夋嫨鍣ㄥ弬鏁�
+ if (!everyOptions.getNextOptions().isEmpty()) {
+ // 褰撲簩绾ч�夐」涓嶄负绌烘椂锛屼娇鐢ㄩ澶栧叧鑱旇〃鐨勫舰寮�
+ dropDownLinkedOptions(helper, workbook, sheet, everyOptions);
+ } else if (everyOptions.getOptions().size() > 10) {
+ // 褰撲竴绾ч�夐」鍙傛暟涓暟澶т簬10锛屼娇鐢ㄩ澶栬〃鐨勫舰寮�
+ dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions());
+ } else {
+ // 鍚﹀垯浣跨敤榛樿褰㈠紡
+ dropDownWithSimple(helper, sheet, everyOptions.getIndex(), everyOptions.getOptions());
+ }
+ });
+ }
+
+ /**
+ * <h2>绠�鍗曚笅鎷夋</h2>
+ * 鐩存帴灏嗗彲閫夐」鎷兼帴涓烘寚瀹氬垪鐨勬暟鎹牎楠屽��
+ *
+ * @param celIndex 鍒梚ndex
+ * @param value 涓嬫媺閫夊彲閫夊��
+ */
+ private void dropDownWithSimple(DataValidationHelper helper, Sheet sheet, Integer celIndex, List<String> value) {
+ if (ObjectUtil.isEmpty(value)) {
+ return;
+ }
+ this.markOptionsToSheet(helper, sheet, celIndex, helper.createExplicitListConstraint(ArrayUtil.toArray(value, String.class)));
+ }
+
+ /**
+ * <h2>棰濆琛ㄦ牸褰㈠紡鐨勭骇鑱斾笅鎷夋</h2>
+ *
+ * @param options 棰濆琛ㄦ牸褰㈠紡瀛樺偍鐨勪笅鎷夊彲閫夐」
+ */
+ private void dropDownLinkedOptions(DataValidationHelper helper, Workbook workbook, Sheet sheet, DropDownOptions options) {
+ String linkedOptionsSheetName = String.format("%s_%d", LINKED_OPTIONS_SHEET_NAME, currentLinkedOptionsSheetIndex);
+ // 鍒涘缓鑱斿姩涓嬫媺鏁版嵁琛�
+ Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName));
+ // 灏嗕笅鎷夎〃闅愯棌
+ workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
+ // 閫夐」鏁版嵁
+ List<String> firstOptions = options.getOptions();
+ Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
+
+ // 閲囩敤鎸夎濉厖鏁版嵁鐨勬柟寮忥紝閬垮厤鍑虹幇鏁版嵁鏃犳硶鍐欏叆鐨勯棶棰�
+ // Attempting to write a row in the range that is already written to disk
+
+ // 浣跨敤ArrayList璁拌浇鏁版嵁锛岄槻姝贡搴�
+ List<String> columnNames = new ArrayList<>();
+ // 鍐欏叆绗竴琛岋紝鍗崇涓�绾х殑鏁版嵁
+ Row firstRow = linkedOptionsDataSheet.createRow(0);
+ for (int columnIndex = 0; columnIndex < firstOptions.size(); columnIndex++) {
+ String columnName = firstOptions.get(columnIndex);
+ firstRow.createCell(columnIndex)
+ .setCellValue(columnName);
+ columnNames.add(columnName);
+ }
+
+ // 鍒涘缓鍚嶇О绠$悊鍣�
+ Name name = workbook.createName();
+ // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑鍒悕
+ name.setNameName(linkedOptionsSheetName);
+ // 浠ユí鍚戠涓�琛屽垱寤轰竴绾т笅鎷夋嫾鎺ュ紩鐢ㄤ綅缃�
+ String firstOptionsFunction = String.format("%s!$%s$1:$%s$1",
+ linkedOptionsSheetName,
+ getExcelColumnName(0),
+ getExcelColumnName(firstOptions.size())
+ );
+ // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑寮曠敤浣嶇疆
+ name.setRefersToFormula(firstOptionsFunction);
+ // 璁剧疆鏁版嵁鏍¢獙涓哄簭鍒楁ā寮忥紝寮曠敤鐨勬槸鍚嶇О绠$悊鍣ㄤ腑鐨勫埆鍚�
+ this.markOptionsToSheet(helper, sheet, options.getIndex(), helper.createFormulaListConstraint(linkedOptionsSheetName));
+
+ // 鍒涘缓浜岀骇閫夐」鐨勫悕绉扮鐞嗗櫒
+ for (int columIndex = 0; columIndex < columnNames.size(); columIndex++) {
+ // 鍒楀悕
+ String firstOptionsColumnName = getExcelColumnName(columIndex);
+ // 瀵瑰簲鐨勪竴绾у��
+ String thisFirstOptionsValue = columnNames.get(columIndex);
+
+ // 浠ヨ涓�绾ч�夐」鍊煎垱寤哄瓙鍚嶇О绠$悊鍣�
+ Name sonName = workbook.createName();
+ // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑鍒悕
+ sonName.setNameName(thisFirstOptionsValue);
+ // 浠ョ浜岃璇ュ垪鏁版嵁鎷兼帴寮曠敤浣嶇疆
+ String sonFunction = String.format("%s!$%s$2:$%s$%d",
+ linkedOptionsSheetName,
+ firstOptionsColumnName,
+ firstOptionsColumnName,
+ // 浜岀骇閫夐」瀛樺湪鍒欒缃负(閫夐」涓暟+1)琛岋紝鍚﹀垯璁剧疆涓�2琛�
+ Math.max(Optional.ofNullable(secoundOptionsMap.get(thisFirstOptionsValue))
+ .orElseGet(ArrayList::new).size(), 1) + 1
+ );
+ // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑寮曠敤浣嶇疆
+ sonName.setRefersToFormula(sonFunction);
+ // 鏁版嵁楠岃瘉涓哄簭鍒楁ā寮忥紝寮曠敤鍒版瘡涓�涓富琛ㄤ腑鐨勪簩绾ч�夐」浣嶇疆
+ // 鍒涘缓瀛愰」鐨勫悕绉扮鐞嗗櫒锛屽彧鏄负浜嗕娇寰桬xcel鍙互璇嗗埆鍒版暟鎹�
+ String mainSheetFirstOptionsColumnName = getExcelColumnName(options.getIndex());
+ for (int i = 0; i < 100; i++) {
+ // 浠ヤ竴绾ч�夐」瀵瑰簲鐨勪富浣撴墍鍦ㄤ綅缃垱寤轰簩绾т笅鎷�
+ String secondOptionsFunction = String.format("=INDIRECT(%s%d)", mainSheetFirstOptionsColumnName, i + 1);
+ // 浜岀骇鍙兘涓昏〃姣忎竴琛岀殑姣忎竴鍒楁坊鍔犱簩绾ф牎楠�
+ markLinkedOptionsToSheet(helper, sheet, i, options.getNextIndex(), helper.createFormulaListConstraint(secondOptionsFunction));
+ }
+ }
+
+ // 灏嗕簩绾ф暟鎹鐞嗕负鎸夎鍖哄垎
+ Map<Integer, List<String>> columnValueMap = new HashMap<>();
+ int currentRow = 1;
+ while (currentRow >= 0) {
+ boolean flag = false;
+ List<String> rowData = new ArrayList<>();
+ for (String columnName : columnNames) {
+ List<String> data = secoundOptionsMap.get(columnName);
+ if (CollUtil.isEmpty(data)) {
+ // 娣诲姞绌哄瓧绗︿覆濉厖浣嶇疆
+ rowData.add(" ");
+ continue;
+ }
+ // 鍙栫涓�涓�
+ String str = data.get(0);
+ rowData.add(str);
+ // 閫氳繃绉婚櫎鐨勬柟寮忛伩鍏嶉噸澶�
+ data.remove(0);
+ // 璁剧疆鍙互缁х画
+ flag = true;
+ }
+ columnValueMap.put(currentRow, rowData);
+ // 鍙互缁х画锛屽垯澧炲姞琛屾暟锛屽惁鍒欑疆涓鸿礋鏁拌烦鍑哄惊鐜�
+ if (flag) {
+ currentRow++;
+ } else {
+ currentRow = -1;
+ }
+ }
+
+ // 濉厖绗簩绾ч�夐」鏁版嵁
+ columnValueMap.forEach((rowIndex, rowValues) -> {
+ Row row = linkedOptionsDataSheet.createRow(rowIndex);
+ for (int columnIndex = 0; columnIndex < rowValues.size(); columnIndex++) {
+ String rowValue = rowValues.get(columnIndex);
+ // 濉厖浣嶇疆鐨勯儴鍒嗕笉娓叉煋
+ if (StrUtil.isNotBlank(rowValue)) {
+ row.createCell(columnIndex)
+ .setCellValue(rowValue);
+ }
+ }
+ });
+
+ currentLinkedOptionsSheetIndex++;
+ }
+
+ /**
+ * <h2>棰濆琛ㄦ牸褰㈠紡鐨勬櫘閫氫笅鎷夋</h2>
+ * 鐢变簬涓嬫媺妗嗗彲閫夊�兼暟閲忚繃澶氾紝涓烘彁鍗嘐xcel鎵撳紑鏁堢巼锛屼娇鐢ㄩ澶栬〃鏍煎舰寮忓仛涓嬫媺
+ *
+ * @param celIndex 涓嬫媺閫�
+ * @param value 涓嬫媺閫夊彲閫夊��
+ */
+ private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
+ //鐢变簬poi鐨勫啓鍑虹浉鍏抽棶棰橈紝瓒呰繃100涓細琚复鏃跺啓杩涚‖鐩橈紝瀵艰嚧鍚庣画鍐呭瓨鍚堝苟浼氬嚭Attempting to write a row[] in the range [] that is already written to disk
+ String tmpOptionsSheetName = OPTIONS_SHEET_NAME + "_" + currentOptionsColumnIndex;
+ // 鍒涘缓涓嬫媺鏁版嵁琛�
+ Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)))
+ .orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)));
+ // 灏嗕笅鎷夎〃闅愯棌
+ workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
+ // 瀹屽杽绾靛悜鐨勪竴绾ч�夐」鏁版嵁琛�
+ for (int i = 0; i < value.size(); i++) {
+ int finalI = i;
+ // 鑾峰彇姣忎竴閫夐」琛岋紝濡傛灉娌℃湁鍒欏垱寤�
+ Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
+ .orElseGet(() -> simpleDataSheet.createRow(finalI));
+ // 鑾峰彇鏈骇閫夐」瀵瑰簲鐨勯�夐」鍒楋紝濡傛灉娌℃湁鍒欏垱寤恒�備笂杩伴噰鐢ㄥ涓猻heet,榛樿绱㈠紩涓�1鍒�
+ Cell cell = Optional.ofNullable(row.getCell(0))
+ .orElseGet(() -> row.createCell(0));
+ // 璁剧疆鍊�
+ cell.setCellValue(value.get(i));
+ }
+
+ // 鍒涘缓鍚嶇О绠$悊鍣�
+ Name name = workbook.createName();
+ // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑鍒悕
+ String nameName = String.format("%s_%d", tmpOptionsSheetName, celIndex);
+ name.setNameName(nameName);
+ // 浠ョ旱鍚戠涓�鍒楀垱寤轰竴绾т笅鎷夋嫾鎺ュ紩鐢ㄤ綅缃�
+ String function = String.format("%s!$%s$1:$%s$%d",
+ tmpOptionsSheetName,
+ getExcelColumnName(0),
+ getExcelColumnName(0),
+ value.size());
+ // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑寮曠敤浣嶇疆
+ name.setRefersToFormula(function);
+ // 璁剧疆鏁版嵁鏍¢獙涓哄簭鍒楁ā寮忥紝寮曠敤鐨勬槸鍚嶇О绠$悊鍣ㄤ腑鐨勫埆鍚�
+ this.markOptionsToSheet(helper, sheet, celIndex, helper.createFormulaListConstraint(nameName));
+ currentOptionsColumnIndex++;
+ }
+
+ /**
+ * 鎸傝浇涓嬫媺鐨勫垪锛屼粎闄愪竴绾ч�夐」
+ */
+ private void markOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer celIndex,
+ DataValidationConstraint constraint) {
+ // 璁剧疆鏁版嵁鏈夋晥鎬у姞杞藉湪鍝釜鍗曞厓鏍间笂,鍥涗釜鍙傛暟鍒嗗埆鏄細璧峰琛屻�佺粓姝㈣銆佽捣濮嬪垪銆佺粓姝㈠垪
+ CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, celIndex, celIndex);
+ markDataValidationToSheet(helper, sheet, constraint, addressList);
+ }
+
+ /**
+ * 鎸傝浇涓嬫媺鐨勫垪锛屼粎闄愪簩绾ч�夐」
+ */
+ private void markLinkedOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer rowIndex,
+ Integer celIndex, DataValidationConstraint constraint) {
+ // 璁剧疆鏁版嵁鏈夋晥鎬у姞杞藉湪鍝釜鍗曞厓鏍间笂,鍥涗釜鍙傛暟鍒嗗埆鏄細璧峰琛屻�佺粓姝㈣銆佽捣濮嬪垪銆佺粓姝㈠垪
+ CellRangeAddressList addressList = new CellRangeAddressList(rowIndex, rowIndex, celIndex, celIndex);
+ markDataValidationToSheet(helper, sheet, constraint, addressList);
+ }
+
+ /**
+ * 搴旂敤鏁版嵁鏍¢獙
+ */
+ private void markDataValidationToSheet(DataValidationHelper helper, Sheet sheet,
+ DataValidationConstraint constraint, CellRangeAddressList addressList) {
+ // 鏁版嵁鏈夋晥鎬у璞�
+ DataValidation dataValidation = helper.createValidation(constraint, addressList);
+ // 澶勭悊Excel鍏煎鎬ч棶棰�
+ if (dataValidation instanceof XSSFDataValidation) {
+ //鏁版嵁鏍¢獙
+ dataValidation.setSuppressDropDownArrow(true);
+ //閿欒鎻愮ず
+ dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
+ dataValidation.createErrorBox("鎻愮ず", "姝ゅ�间笌鍗曞厓鏍煎畾涔夋暟鎹笉涓�鑷�");
+ dataValidation.setShowErrorBox(true);
+ //閫夊畾鎻愮ず
+ dataValidation.createPromptBox("濉啓璇存槑锛�", "濉啓鍐呭鍙兘涓轰笅鎷変腑鏁版嵁锛屽叾浠栨暟鎹皢瀵艰嚧瀵煎叆澶辫触");
+ dataValidation.setShowPromptBox(true);
+ sheet.addValidationData(dataValidation);
+ } else {
+ dataValidation.setSuppressDropDownArrow(false);
+ }
+ sheet.addValidationData(dataValidation);
+ }
+
+ /**
+ * <h2>渚濇嵁鍒梚ndex鑾峰彇鍒楀悕鑻辨枃</h2>
+ * 渚濇嵁鍒梚ndex杞崲涓篍xcel涓殑鍒楀悕鑻辨枃
+ * <p>渚嬪绗�1鍒楋紝index涓�0锛岃В鏋愬嚭鏉ヤ负A鍒�</p>
+ * 绗�27鍒楋紝index涓�26锛岃В鏋愪负AA鍒�
+ * <p>绗�28鍒楋紝index涓�27锛岃В鏋愪负AB鍒�</p>
+ *
+ * @param columnIndex 鍒梚ndex
+ * @return 鍒梚ndex鎵�鍦ㄥ緱鑻辨枃鍚�
+ */
+ private String getExcelColumnName(int columnIndex) {
+ // 26涓�寰幆鐨勬鏁�
+ int columnCircleCount = columnIndex / 26;
+ // 26涓�寰幆鍐呯殑浣嶇疆
+ int thisCircleColumnIndex = columnIndex % 26;
+ // 26涓�寰幆鐨勬鏁板ぇ浜�0锛屽垯瑙嗕负鏍忓悕鑷冲皯涓や綅
+ String columnPrefix = columnCircleCount == 0
+ ? StrUtil.EMPTY
+ : StrUtil.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount - 1, 1);
+ // 浠�26涓�寰幆鍐呭彇瀵瑰簲鐨勬爮浣嶅悕
+ String columnNext = StrUtil.subWithLength(EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1);
+ // 灏嗕簩鑰呮嫾鎺ュ嵆涓烘渶缁堢殑鏍忎綅鍚�
+ return columnPrefix + columnNext;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelListener.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelListener.java
new file mode 100755
index 0000000..957b307
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelListener.java
@@ -0,0 +1,14 @@
+package org.dromara.common.excel.core;
+
+import cn.idev.excel.read.listener.ReadListener;
+
+/**
+ * Excel 瀵煎叆鐩戝惉
+ *
+ * @author Lion Li
+ */
+public interface ExcelListener<T> extends ReadListener<T> {
+
+ ExcelResult<T> getExcelResult();
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelOptionsProvider.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelOptionsProvider.java
new file mode 100755
index 0000000..85f939a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelOptionsProvider.java
@@ -0,0 +1,19 @@
+package org.dromara.common.excel.core;
+
+import java.util.Set;
+
+/**
+ * Excel涓嬫媺閫夐」鏁版嵁鎻愪緵鎺ュ彛
+ *
+ * @author Angus
+ */
+public interface ExcelOptionsProvider {
+
+ /**
+ * 鑾峰彇涓嬫媺閫夐」鏁版嵁
+ *
+ * @return 涓嬫媺閫夐」鍒楄〃
+ */
+ Set<String> getOptions();
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelResult.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelResult.java
new file mode 100755
index 0000000..0c2a418
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelResult.java
@@ -0,0 +1,26 @@
+package org.dromara.common.excel.core;
+
+import java.util.List;
+
+/**
+ * excel杩斿洖瀵硅薄
+ *
+ * @author Lion Li
+ */
+public interface ExcelResult<T> {
+
+ /**
+ * 瀵硅薄鍒楄〃
+ */
+ List<T> getList();
+
+ /**
+ * 閿欒鍒楄〃
+ */
+ List<String> getErrorList();
+
+ /**
+ * 瀵煎叆鍥炴墽
+ */
+ String getAnalysis();
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/handler/DataWriteHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/handler/DataWriteHandler.java
new file mode 100755
index 0000000..3770f80
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/handler/DataWriteHandler.java
@@ -0,0 +1,123 @@
+package org.dromara.common.excel.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.metadata.data.DataFormatData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.util.StyleUtil;
+import cn.idev.excel.write.handler.CellWriteHandler;
+import cn.idev.excel.write.handler.SheetWriteHandler;
+import cn.idev.excel.write.handler.context.CellWriteHandlerContext;
+import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
+import cn.idev.excel.write.metadata.style.WriteCellStyle;
+import cn.idev.excel.write.metadata.style.WriteFont;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
+import org.apache.poi.xssf.usermodel.XSSFRichTextString;
+import org.dromara.common.excel.annotation.ExcelNotation;
+import org.dromara.common.excel.annotation.ExcelRequired;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 鎵规敞銆佸繀濉�
+ *
+ * @author guzhouyanyu
+ */
+public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
+
+ /**
+ * 鎵规敞
+ */
+ private final Map<String, String> notationMap;
+
+ /**
+ * 澶村垪瀛椾綋棰滆壊
+ */
+ private final Map<String, Short> headColumnMap;
+
+
+ public DataWriteHandler(Class<?> clazz) {
+ notationMap = getNotationMap(clazz);
+ headColumnMap = getRequiredMap(clazz);
+ }
+
+ @Override
+ public void afterCellDispose(CellWriteHandlerContext context) {
+ if (CollUtil.isEmpty(notationMap) && CollUtil.isEmpty(headColumnMap)) {
+ return;
+ }
+ // 绗竴琛�
+ WriteCellData<?> cellData = context.getFirstCellData();
+ // 绗竴涓牸瀛�
+ WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
+
+ if (context.getHead()) {
+ DataFormatData dataFormatData = new DataFormatData();
+ // 鍗曞厓鏍艰缃负鏂囨湰鏍煎紡
+ dataFormatData.setIndex((short) 49);
+ writeCellStyle.setDataFormatData(dataFormatData);
+ Cell cell = context.getCell();
+ WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder();
+ Sheet sheet = writeSheetHolder.getSheet();
+ Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
+ Drawing<?> drawing = sheet.createDrawingPatriarch();
+ // 璁剧疆鏍囬瀛椾綋鏍峰紡
+ WriteFont headWriteFont = new WriteFont();
+ // 鍔犵矖
+ headWriteFont.setBold(true);
+ if (CollUtil.isNotEmpty(headColumnMap) && headColumnMap.containsKey(cell.getStringCellValue())) {
+ // 璁剧疆瀛椾綋棰滆壊
+ headWriteFont.setColor(headColumnMap.get(cell.getStringCellValue()));
+ }
+ writeCellStyle.setWriteFont(headWriteFont);
+ CellStyle cellStyle = StyleUtil.buildCellStyle(workbook, null, writeCellStyle);
+ cell.setCellStyle(cellStyle);
+
+ if (CollUtil.isNotEmpty(notationMap) && notationMap.containsKey(cell.getStringCellValue())) {
+ // 鎵规敞鍐呭
+ String notationContext = notationMap.get(cell.getStringCellValue());
+ // 鍒涘缓缁樺浘瀵硅薄
+ Comment comment = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), 0, (short) 5, 5));
+ comment.setString(new XSSFRichTextString(notationContext));
+ cell.setCellComment(comment);
+ }
+ }
+ }
+
+ /**
+ * 鑾峰彇蹇呭~鍒�
+ */
+ private static Map<String, Short> getRequiredMap(Class<?> clazz) {
+ Map<String, Short> requiredMap = new HashMap<>();
+ Field[] fields = clazz.getDeclaredFields();
+ for (Field field : fields) {
+ if (!field.isAnnotationPresent(ExcelRequired.class)) {
+ continue;
+ }
+ ExcelRequired excelRequired = field.getAnnotation(ExcelRequired.class);
+ ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
+ requiredMap.put(excelProperty.value()[0], excelRequired.fontColor().getIndex());
+ }
+ return requiredMap;
+ }
+
+ /**
+ * 鑾峰彇鎵规敞
+ */
+ private static Map<String, String> getNotationMap(Class<?> clazz) {
+ Map<String, String> notationMap = new HashMap<>();
+ Field[] fields = clazz.getDeclaredFields();
+ for (Field field : fields) {
+ if (!field.isAnnotationPresent(ExcelNotation.class)) {
+ continue;
+ }
+ ExcelNotation excelNotation = field.getAnnotation(ExcelNotation.class);
+ ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
+ notationMap.put(excelProperty.value()[0], excelNotation.value());
+ }
+ return notationMap;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java
new file mode 100755
index 0000000..74dbccb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java
@@ -0,0 +1,479 @@
+package org.dromara.common.excel.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.io.resource.ClassPathResource;
+import cn.hutool.core.util.IdUtil;
+import cn.idev.excel.FastExcel;
+import cn.idev.excel.ExcelWriter;
+import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
+import cn.idev.excel.write.metadata.WriteSheet;
+import cn.idev.excel.write.metadata.fill.FillConfig;
+import cn.idev.excel.write.metadata.fill.FillWrapper;
+import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.file.FileUtils;
+import org.dromara.common.excel.convert.ExcelBigNumberConvert;
+import org.dromara.common.excel.core.*;
+import org.dromara.common.excel.handler.DataWriteHandler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Excel鐩稿叧澶勭悊
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ExcelUtil {
+
+ /**
+ * 鍚屾瀵煎叆(閫傜敤浜庡皬鏁版嵁閲�)
+ *
+ * @param is 杈撳叆娴�
+ * @return 杞崲鍚庨泦鍚�
+ */
+ public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
+ return FastExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
+ }
+
+
+ /**
+ * 浣跨敤鏍¢獙鐩戝惉鍣� 寮傛瀵煎叆 鍚屾杩斿洖
+ *
+ * @param is 杈撳叆娴�
+ * @param clazz 瀵硅薄绫诲瀷
+ * @param isValidate 鏄惁 Validator 妫�楠� 榛樿涓烘槸
+ * @return 杞崲鍚庨泦鍚�
+ */
+ public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
+ DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
+ FastExcel.read(is, clazz, listener).sheet().doRead();
+ return listener.getExcelResult();
+ }
+
+ /**
+ * 浣跨敤鑷畾涔夌洃鍚櫒 寮傛瀵煎叆 鑷畾涔夎繑鍥�
+ *
+ * @param is 杈撳叆娴�
+ * @param clazz 瀵硅薄绫诲瀷
+ * @param listener 鑷畾涔夌洃鍚櫒
+ * @return 杞崲鍚庨泦鍚�
+ */
+ public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
+ FastExcel.read(is, clazz, listener).sheet().doRead();
+ return listener.getExcelResult();
+ }
+
+ /**
+ * 瀵煎嚭excel
+ *
+ * @param list 瀵煎嚭鏁版嵁闆嗗悎
+ * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+ * @param clazz 瀹炰綋绫�
+ * @param response 鍝嶅簲浣�
+ */
+ public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
+ try {
+ resetResponse(sheetName, response);
+ ServletOutputStream os = response.getOutputStream();
+ exportExcel(list, sheetName, clazz, false, os, null);
+ } catch (IOException e) {
+ throw new RuntimeException("瀵煎嚭Excel寮傚父");
+ }
+ }
+
+ /**
+ * 瀵煎嚭excel
+ *
+ * @param list 瀵煎嚭鏁版嵁闆嗗悎
+ * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+ * @param clazz 瀹炰綋绫�
+ * @param response 鍝嶅簲浣�
+ * @param options 绾ц仈涓嬫媺閫�
+ */
+ public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response, List<DropDownOptions> options) {
+ try {
+ resetResponse(sheetName, response);
+ ServletOutputStream os = response.getOutputStream();
+ exportExcel(list, sheetName, clazz, false, os, options);
+ } catch (IOException e) {
+ throw new RuntimeException("瀵煎嚭Excel寮傚父");
+ }
+ }
+
+ /**
+ * 瀵煎嚭excel
+ *
+ * @param list 瀵煎嚭鏁版嵁闆嗗悎
+ * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+ * @param clazz 瀹炰綋绫�
+ * @param merge 鏄惁鍚堝苟鍗曞厓鏍�
+ * @param response 鍝嶅簲浣�
+ */
+ public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, HttpServletResponse response) {
+ try {
+ resetResponse(sheetName, response);
+ ServletOutputStream os = response.getOutputStream();
+ exportExcel(list, sheetName, clazz, merge, os, null);
+ } catch (IOException e) {
+ throw new RuntimeException("瀵煎嚭Excel寮傚父");
+ }
+ }
+
+ /**
+ * 瀵煎嚭excel
+ *
+ * @param list 瀵煎嚭鏁版嵁闆嗗悎
+ * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+ * @param clazz 瀹炰綋绫�
+ * @param merge 鏄惁鍚堝苟鍗曞厓鏍�
+ * @param response 鍝嶅簲浣�
+ * @param options 绾ц仈涓嬫媺閫�
+ */
+ public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, HttpServletResponse response, List<DropDownOptions> options) {
+ try {
+ resetResponse(sheetName, response);
+ ServletOutputStream os = response.getOutputStream();
+ exportExcel(list, sheetName, clazz, merge, os, options);
+ } catch (IOException e) {
+ throw new RuntimeException("瀵煎嚭Excel寮傚父");
+ }
+ }
+
+ /**
+ * 瀵煎嚭excel
+ *
+ * @param list 瀵煎嚭鏁版嵁闆嗗悎
+ * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+ * @param clazz 瀹炰綋绫�
+ * @param os 杈撳嚭娴�
+ */
+ public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os) {
+ exportExcel(list, sheetName, clazz, false, os, null);
+ }
+
+ /**
+ * 瀵煎嚭excel
+ *
+ * @param list 瀵煎嚭鏁版嵁闆嗗悎
+ * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+ * @param clazz 瀹炰綋绫�
+ * @param os 杈撳嚭娴�
+ * @param options 绾ц仈涓嬫媺閫夊唴瀹�
+ */
+ public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os, List<DropDownOptions> options) {
+ exportExcel(list, sheetName, clazz, false, os, options);
+ }
+
+ /**
+ * 瀵煎嚭excel
+ *
+ * @param list 瀵煎嚭鏁版嵁闆嗗悎
+ * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+ * @param clazz 瀹炰綋绫�
+ * @param merge 鏄惁鍚堝苟鍗曞厓鏍�
+ * @param os 杈撳嚭娴�
+ */
+ public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
+ OutputStream os, List<DropDownOptions> options) {
+ ExcelWriterSheetBuilder builder = FastExcel.write(os, clazz)
+ .autoCloseStream(false)
+ // 鑷姩閫傞厤
+ .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+ // 澶ф暟鍊艰嚜鍔ㄨ浆鎹� 闃叉澶辩湡
+ .registerConverter(new ExcelBigNumberConvert())
+ .registerWriteHandler(new DataWriteHandler(clazz))
+ .sheet(sheetName);
+ if (merge) {
+ // 鍚堝苟澶勭悊鍣�
+ builder.registerWriteHandler(new CellMergeStrategy(list, true));
+ }
+ // 娣诲姞涓嬫媺妗嗘搷浣�
+ builder.registerWriteHandler(new ExcelDownHandler(options));
+ builder.doWrite(list);
+ }
+
+ /**
+ * 瀵煎嚭excel
+ *
+ * @param headType 甯xcel娉ㄨВ鐨勭被鍨�
+ * @param os 杈撳嚭娴�
+ * @param options Excel涓嬫媺鍙�夐」
+ * @param consumer 瀵煎嚭鍔╂墜娑堣垂鍑芥暟
+ */
+ public static <T> void exportExcel(Class<T> headType, OutputStream os, List<DropDownOptions> options, Consumer<ExcelWriterWrapper<T>> consumer) {
+ try (ExcelWriter writer = FastExcel.write(os, headType)
+ .autoCloseStream(false)
+ // 鑷姩閫傞厤
+ .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+ // 澶ф暟鍊艰嚜鍔ㄨ浆鎹� 闃叉澶辩湡
+ .registerConverter(new ExcelBigNumberConvert())
+ // 鎵规敞蹇呭~椤瑰鐞�
+ .registerWriteHandler(new DataWriteHandler(headType))
+ // 娣诲姞涓嬫媺妗嗘搷浣�
+ .registerWriteHandler(new ExcelDownHandler(options))
+ .build()) {
+ // 鎵ц娑堣垂鍑芥暟
+ consumer.accept(ExcelWriterWrapper.of(writer));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 瀵煎嚭excel
+ *
+ * @param headType 甯xcel娉ㄨВ鐨勭被鍨�
+ * @param os 杈撳嚭娴�
+ * @param consumer 瀵煎嚭鍔╂墜娑堣垂鍑芥暟
+ */
+ public static <T> void exportExcel(Class<T> headType, OutputStream os, Consumer<ExcelWriterWrapper<T>> consumer) {
+ exportExcel(headType, os, null, consumer);
+ }
+
+ /**
+ * 鍗曡〃澶氭暟鎹ā鏉垮鍑� 妯℃澘鏍煎紡涓� {.灞炴�
+ *
+ * @param filename 鏂囦欢鍚�
+ * @param templatePath 妯℃澘璺緞 resource 鐩綍涓嬬殑璺緞鍖呮嫭妯℃澘鏂囦欢鍚�
+ * 渚嬪: excel/temp.xlsx
+ * 閲嶇偣: 妯℃澘鏂囦欢蹇呴』鏀剧疆鍒板惎鍔ㄧ被瀵瑰簲鐨� resource 鐩綍涓�
+ * @param data 妯℃澘闇�瑕佺殑鏁版嵁
+ * @param response 鍝嶅簲浣�
+ */
+ public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
+ try {
+ if (CollUtil.isEmpty(data)) {
+ throw new IllegalArgumentException("鏁版嵁涓虹┖");
+ }
+ resetResponse(filename, response);
+ ServletOutputStream os = response.getOutputStream();
+ exportTemplate(data, templatePath, os);
+ } catch (IOException e) {
+ throw new RuntimeException("瀵煎嚭Excel寮傚父");
+ }
+ }
+
+ /**
+ * 鍗曡〃澶氭暟鎹ā鏉垮鍑� 妯℃澘鏍煎紡涓� {.灞炴�
+ *
+ * @param templatePath 妯℃澘璺緞 resource 鐩綍涓嬬殑璺緞鍖呮嫭妯℃澘鏂囦欢鍚�
+ * 渚嬪: excel/temp.xlsx
+ * 閲嶇偣: 妯℃澘鏂囦欢蹇呴』鏀剧疆鍒板惎鍔ㄧ被瀵瑰簲鐨� resource 鐩綍涓�
+ * @param data 妯℃澘闇�瑕佺殑鏁版嵁
+ * @param os 杈撳嚭娴�
+ */
+ public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
+ ClassPathResource templateResource = new ClassPathResource(templatePath);
+ ExcelWriter excelWriter = FastExcel.write(os)
+ .withTemplate(templateResource.getStream())
+ .autoCloseStream(false)
+ // 澶ф暟鍊艰嚜鍔ㄨ浆鎹� 闃叉澶辩湡
+ .registerConverter(new ExcelBigNumberConvert())
+ .registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
+ .build();
+ WriteSheet writeSheet = FastExcel.writerSheet().build();
+ FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
+ // 鍗曡〃澶氭暟鎹鍑� 妯℃澘鏍煎紡涓� {.灞炴�
+ for (T d : data) {
+ excelWriter.fill(d, fillConfig, writeSheet);
+ }
+ excelWriter.finish();
+ }
+
+ /**
+ * 澶氳〃澶氭暟鎹ā鏉垮鍑� 妯℃澘鏍煎紡涓� {key.灞炴�
+ *
+ * @param filename 鏂囦欢鍚�
+ * @param templatePath 妯℃澘璺緞 resource 鐩綍涓嬬殑璺緞鍖呮嫭妯℃澘鏂囦欢鍚�
+ * 渚嬪: excel/temp.xlsx
+ * 閲嶇偣: 妯℃澘鏂囦欢蹇呴』鏀剧疆鍒板惎鍔ㄧ被瀵瑰簲鐨� resource 鐩綍涓�
+ * @param data 妯℃澘闇�瑕佺殑鏁版嵁
+ * @param response 鍝嶅簲浣�
+ */
+ public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
+ try {
+ if (CollUtil.isEmpty(data)) {
+ throw new IllegalArgumentException("鏁版嵁涓虹┖");
+ }
+ resetResponse(filename, response);
+ ServletOutputStream os = response.getOutputStream();
+ exportTemplateMultiList(data, templatePath, os);
+ } catch (IOException e) {
+ throw new RuntimeException("瀵煎嚭Excel寮傚父");
+ }
+ }
+
+ /**
+ * 澶歴heet妯℃澘瀵煎嚭 妯℃澘鏍煎紡涓� {key.灞炴�
+ *
+ * @param filename 鏂囦欢鍚�
+ * @param templatePath 妯℃澘璺緞 resource 鐩綍涓嬬殑璺緞鍖呮嫭妯℃澘鏂囦欢鍚�
+ * 渚嬪: excel/temp.xlsx
+ * 閲嶇偣: 妯℃澘鏂囦欢蹇呴』鏀剧疆鍒板惎鍔ㄧ被瀵瑰簲鐨� resource 鐩綍涓�
+ * @param data 妯℃澘闇�瑕佺殑鏁版嵁
+ * @param response 鍝嶅簲浣�
+ */
+ public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
+ try {
+ if (CollUtil.isEmpty(data)) {
+ throw new IllegalArgumentException("鏁版嵁涓虹┖");
+ }
+ resetResponse(filename, response);
+ ServletOutputStream os = response.getOutputStream();
+ exportTemplateMultiSheet(data, templatePath, os);
+ } catch (IOException e) {
+ throw new RuntimeException("瀵煎嚭Excel寮傚父");
+ }
+ }
+
+ /**
+ * 澶氳〃澶氭暟鎹ā鏉垮鍑� 妯℃澘鏍煎紡涓� {key.灞炴�
+ *
+ * @param templatePath 妯℃澘璺緞 resource 鐩綍涓嬬殑璺緞鍖呮嫭妯℃澘鏂囦欢鍚�
+ * 渚嬪: excel/temp.xlsx
+ * 閲嶇偣: 妯℃澘鏂囦欢蹇呴』鏀剧疆鍒板惎鍔ㄧ被瀵瑰簲鐨� resource 鐩綍涓�
+ * @param data 妯℃澘闇�瑕佺殑鏁版嵁
+ * @param os 杈撳嚭娴�
+ */
+ public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
+ ClassPathResource templateResource = new ClassPathResource(templatePath);
+ ExcelWriter excelWriter = FastExcel.write(os)
+ .withTemplate(templateResource.getStream())
+ .autoCloseStream(false)
+ // 澶ф暟鍊艰嚜鍔ㄨ浆鎹� 闃叉澶辩湡
+ .registerConverter(new ExcelBigNumberConvert())
+ .build();
+ WriteSheet writeSheet = FastExcel.writerSheet().build();
+ for (Map.Entry<String, Object> map : data.entrySet()) {
+ // 璁剧疆鍒楄〃鍚庣画杩樻湁鏁版嵁
+ FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
+ if (map.getValue() instanceof Collection) {
+ // 澶氳〃瀵煎嚭蹇呴』浣跨敤 FillWrapper
+ excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
+ } else {
+ excelWriter.fill(map.getValue(), fillConfig, writeSheet);
+ }
+ }
+ excelWriter.finish();
+ }
+
+ /**
+ * 澶歴heet妯℃澘瀵煎嚭 妯℃澘鏍煎紡涓� {key.灞炴�
+ *
+ * @param templatePath 妯℃澘璺緞 resource 鐩綍涓嬬殑璺緞鍖呮嫭妯℃澘鏂囦欢鍚�
+ * 渚嬪: excel/temp.xlsx
+ * 閲嶇偣: 妯℃澘鏂囦欢蹇呴』鏀剧疆鍒板惎鍔ㄧ被瀵瑰簲鐨� resource 鐩綍涓�
+ * @param data 妯℃澘闇�瑕佺殑鏁版嵁
+ * @param os 杈撳嚭娴�
+ */
+ public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
+ ClassPathResource templateResource = new ClassPathResource(templatePath);
+ ExcelWriter excelWriter = FastExcel.write(os)
+ .withTemplate(templateResource.getStream())
+ .autoCloseStream(false)
+ // 澶ф暟鍊艰嚜鍔ㄨ浆鎹� 闃叉澶辩湡
+ .registerConverter(new ExcelBigNumberConvert())
+ .build();
+ for (int i = 0; i < data.size(); i++) {
+ WriteSheet writeSheet = FastExcel.writerSheet(i).build();
+ for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
+ // 璁剧疆鍒楄〃鍚庣画杩樻湁鏁版嵁
+ FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
+ if (map.getValue() instanceof Collection) {
+ // 澶氳〃瀵煎嚭蹇呴』浣跨敤 FillWrapper
+ excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
+ } else {
+ excelWriter.fill(map.getValue(), writeSheet);
+ }
+ }
+ }
+ excelWriter.finish();
+ }
+
+ /**
+ * 閲嶇疆鍝嶅簲浣�
+ */
+ private static void resetResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException {
+ String filename = encodingFilename(sheetName);
+ FileUtils.setAttachmentResponseHeader(response, filename);
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
+ }
+
+ /**
+ * 瑙f瀽瀵煎嚭鍊� 0=鐢�,1=濂�,2=鏈煡
+ *
+ * @param propertyValue 鍙傛暟鍊�
+ * @param converterExp 缈昏瘧娉ㄨВ
+ * @param separator 鍒嗛殧绗�
+ * @return 瑙f瀽鍚庡��
+ */
+ public static String convertByExp(String propertyValue, String converterExp, String separator) {
+ StringBuilder propertyString = new StringBuilder();
+ String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
+ for (String item : convertSource) {
+ String[] itemArray = item.split("=");
+ if (StringUtils.containsAny(propertyValue, separator)) {
+ for (String value : propertyValue.split(separator)) {
+ if (itemArray[0].equals(value)) {
+ propertyString.append(itemArray[1] + separator);
+ break;
+ }
+ }
+ } else {
+ if (itemArray[0].equals(propertyValue)) {
+ return itemArray[1];
+ }
+ }
+ }
+ return StringUtils.stripEnd(propertyString.toString(), separator);
+ }
+
+ /**
+ * 鍙嶅悜瑙f瀽鍊� 鐢�=0,濂�=1,鏈煡=2
+ *
+ * @param propertyValue 鍙傛暟鍊�
+ * @param converterExp 缈昏瘧娉ㄨВ
+ * @param separator 鍒嗛殧绗�
+ * @return 瑙f瀽鍚庡��
+ */
+ public static String reverseByExp(String propertyValue, String converterExp, String separator) {
+ StringBuilder propertyString = new StringBuilder();
+ String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
+ for (String item : convertSource) {
+ String[] itemArray = item.split("=");
+ if (StringUtils.containsAny(propertyValue, separator)) {
+ for (String value : propertyValue.split(separator)) {
+ if (itemArray[1].equals(value)) {
+ propertyString.append(itemArray[0] + separator);
+ break;
+ }
+ }
+ } else {
+ if (itemArray[1].equals(propertyValue)) {
+ return itemArray[0];
+ }
+ }
+ }
+ return StringUtils.stripEnd(propertyString.toString(), separator);
+ }
+
+ /**
+ * 缂栫爜鏂囦欢鍚�
+ */
+ public static String encodingFilename(String filename) {
+ return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelWriterWrapper.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelWriterWrapper.java
new file mode 100755
index 0000000..396f371
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelWriterWrapper.java
@@ -0,0 +1,127 @@
+package org.dromara.common.excel.utils;
+
+import cn.idev.excel.ExcelWriter;
+import cn.idev.excel.FastExcel;
+import cn.idev.excel.context.WriteContext;
+import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
+import cn.idev.excel.write.builder.ExcelWriterTableBuilder;
+import cn.idev.excel.write.metadata.WriteSheet;
+import cn.idev.excel.write.metadata.WriteTable;
+import cn.idev.excel.write.metadata.fill.FillConfig;
+
+import java.util.Collection;
+import java.util.function.Supplier;
+
+/**
+ * ExcelWriterWrapper Excel鍐欏嚭鍖呰鍣�
+ * <br>
+ * 鎻愪緵浜嗕竴缁勪笌 ExcelWriter 涓�涓�瀵瑰簲鐨勫啓鍑烘柟娉曪紝閬垮厤鐩存帴鎻愪緵 ExcelWriter 鑰屽鑷寸殑涓�浜涗笉鍙帶闂锛堟瘮濡傛彁鍓嶅叧闂簡IO娴佺瓑锛�
+ *
+ * @author 绉嬭緸鏈瘨
+ * @see ExcelWriter
+ */
+public record ExcelWriterWrapper<T>(ExcelWriter excelWriter) {
+
+ public void write(Collection<T> data, WriteSheet writeSheet) {
+ excelWriter.write(data, writeSheet);
+ }
+
+ public void write(Supplier<Collection<T>> supplier, WriteSheet writeSheet) {
+ excelWriter.write(supplier.get(), writeSheet);
+ }
+
+ public void write(Collection<T> data, WriteSheet writeSheet, WriteTable writeTable) {
+ excelWriter.write(data, writeSheet, writeTable);
+ }
+
+ public void write(Supplier<Collection<T>> supplier, WriteSheet writeSheet, WriteTable writeTable) {
+ excelWriter.write(supplier.get(), writeSheet, writeTable);
+ }
+
+ public void fill(Object data, WriteSheet writeSheet) {
+ excelWriter.fill(data, writeSheet);
+ }
+
+ public void fill(Object data, FillConfig fillConfig, WriteSheet writeSheet) {
+ excelWriter.fill(data, fillConfig, writeSheet);
+ }
+
+ public void fill(Supplier<Object> supplier, WriteSheet writeSheet) {
+ excelWriter.fill(supplier, writeSheet);
+ }
+
+ public void fill(Supplier<Object> supplier, FillConfig fillConfig, WriteSheet writeSheet) {
+ excelWriter.fill(supplier, fillConfig, writeSheet);
+ }
+
+ public WriteContext writeContext() {
+ return excelWriter.writeContext();
+ }
+
+ /**
+ * 鍒涘缓涓�涓� ExcelWriterWrapper
+ *
+ * @param excelWriter ExcelWriter
+ * @return ExcelWriterWrapper
+ */
+ public static <T> ExcelWriterWrapper<T> of(ExcelWriter excelWriter) {
+ return new ExcelWriterWrapper<>(excelWriter);
+ }
+
+ // -------------------------------- sheet start
+
+ public static WriteSheet buildSheet(Integer sheetNo, String sheetName) {
+ return sheetBuilder(sheetNo, sheetName).build();
+ }
+
+ public static WriteSheet buildSheet(Integer sheetNo) {
+ return sheetBuilder(sheetNo).build();
+ }
+
+ public static WriteSheet buildSheet(String sheetName) {
+ return sheetBuilder(sheetName).build();
+ }
+
+ public static WriteSheet buildSheet() {
+ return sheetBuilder().build();
+ }
+
+ public static ExcelWriterSheetBuilder sheetBuilder(Integer sheetNo, String sheetName) {
+ return FastExcel.writerSheet(sheetNo, sheetName);
+ }
+
+ public static ExcelWriterSheetBuilder sheetBuilder(Integer sheetNo) {
+ return FastExcel.writerSheet(sheetNo);
+ }
+
+ public static ExcelWriterSheetBuilder sheetBuilder(String sheetName) {
+ return FastExcel.writerSheet(sheetName);
+ }
+
+ public static ExcelWriterSheetBuilder sheetBuilder() {
+ return FastExcel.writerSheet();
+ }
+
+ // -------------------------------- sheet end
+
+ // -------------------------------- table start
+
+ public static WriteTable buildTable(Integer tableNo) {
+ return tableBuilder(tableNo).build();
+ }
+
+ public static WriteTable buildTable() {
+ return tableBuilder().build();
+ }
+
+ public static ExcelWriterTableBuilder tableBuilder(Integer tableNo) {
+ return FastExcel.writerTable(tableNo);
+ }
+
+ public static ExcelWriterTableBuilder tableBuilder() {
+ return FastExcel.writerTable();
+ }
+
+ // -------------------------------- table end
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/.flattened-pom.xml
new file mode 100644
index 0000000..6a84967
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/.flattened-pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-idempotent</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-idempotent 骞傜瓑鍔熻兘</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-crypto</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-core</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/pom.xml
new file mode 100755
index 0000000..64418b4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-idempotent</artifactId>
+
+ <description>
+ ruoyi-common-idempotent 骞傜瓑鍔熻兘
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-crypto</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-core</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/annotation/RepeatSubmit.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/annotation/RepeatSubmit.java
new file mode 100755
index 0000000..42ae802
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/annotation/RepeatSubmit.java
@@ -0,0 +1,29 @@
+package org.dromara.common.idempotent.annotation;
+
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 鑷畾涔夋敞瑙i槻姝㈣〃鍗曢噸澶嶆彁浜�
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RepeatSubmit {
+
+ /**
+ * 闂撮殧鏃堕棿(ms)锛屽皬浜庢鏃堕棿瑙嗕负閲嶅鎻愪氦
+ */
+ int interval() default 5000;
+
+ TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
+
+ /**
+ * 鎻愮ず娑堟伅 鏀寔鍥介檯鍖� 鏍煎紡涓� {code}
+ */
+ String message() default "{repeat.submit.message}";
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/aspectj/RepeatSubmitAspect.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/aspectj/RepeatSubmitAspect.java
new file mode 100755
index 0000000..5a27e91
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/aspectj/RepeatSubmitAspect.java
@@ -0,0 +1,146 @@
+package org.dromara.common.idempotent.aspectj;
+
+import cn.dev33.satoken.SaManager;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.crypto.SecureUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Map;
+import java.util.StringJoiner;
+
+/**
+ * 闃叉閲嶅鎻愪氦(鍙傝�冪編鍥TIS闃查噸绯荤粺)
+ *
+ * @author Lion Li
+ */
+@Aspect
+public class RepeatSubmitAspect {
+
+ private static final ThreadLocal<String> KEY_CACHE = new ThreadLocal<>();
+
+ @Before("@annotation(repeatSubmit)")
+ public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
+ // 濡傛灉娉ㄨВ涓嶄负0 鍒欎娇鐢ㄦ敞瑙f暟鍊�
+ long interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
+
+ if (interval < 1000) {
+ throw new ServiceException("閲嶅鎻愪氦闂撮殧鏃堕棿涓嶈兘灏忎簬'1'绉�");
+ }
+ HttpServletRequest request = ServletUtils.getRequest();
+ String nowParams = argsArrayToString(point.getArgs());
+
+ // 璇锋眰鍦板潃锛堜綔涓哄瓨鏀綾ache鐨刱ey鍊硷級
+ String url = request.getRequestURI();
+
+ // 鍞竴鍊硷紙娌℃湁娑堟伅澶村垯浣跨敤璇锋眰鍦板潃锛�
+ String submitKey = StringUtils.trimToEmpty(request.getHeader(SaManager.getConfig().getTokenName()));
+
+ submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
+ // 鍞竴鏍囪瘑锛堟寚瀹歬ey + url + 娑堟伅澶达級
+ String cacheRepeatKey = GlobalConstants.REPEAT_SUBMIT_KEY + url + submitKey;
+ if (RedisUtils.setObjectIfAbsent(cacheRepeatKey, "", Duration.ofMillis(interval))) {
+ KEY_CACHE.set(cacheRepeatKey);
+ } else {
+ String message = repeatSubmit.message();
+ if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
+ message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
+ }
+ throw new ServiceException(message);
+ }
+ }
+
+ /**
+ * 澶勭悊瀹岃姹傚悗鎵ц
+ *
+ * @param joinPoint 鍒囩偣
+ */
+ @AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "jsonResult")
+ public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) {
+ if (jsonResult instanceof R<?> r) {
+ try {
+ // 鎴愬姛鍒欎笉鍒犻櫎redis鏁版嵁 淇濊瘉鍦ㄦ湁鏁堟椂闂村唴鏃犳硶閲嶅鎻愪氦
+ if (r.getCode() == R.SUCCESS) {
+ return;
+ }
+ RedisUtils.deleteObject(KEY_CACHE.get());
+ } finally {
+ KEY_CACHE.remove();
+ }
+ }
+ }
+
+ /**
+ * 鎷︽埅寮傚父鎿嶄綔
+ *
+ * @param joinPoint 鍒囩偣
+ * @param e 寮傚父
+ */
+ @AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e")
+ public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) {
+ RedisUtils.deleteObject(KEY_CACHE.get());
+ KEY_CACHE.remove();
+ }
+
+ /**
+ * 鍙傛暟鎷艰
+ */
+ private String argsArrayToString(Object[] paramsArray) {
+ StringJoiner params = new StringJoiner(" ");
+ if (ArrayUtil.isEmpty(paramsArray)) {
+ return params.toString();
+ }
+ for (Object o : paramsArray) {
+ if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
+ params.add(JsonUtils.toJsonString(o));
+ }
+ }
+ return params.toString();
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁闇�瑕佽繃婊ょ殑瀵硅薄銆�
+ *
+ * @param o 瀵硅薄淇℃伅銆�
+ * @return 濡傛灉鏄渶瑕佽繃婊ょ殑瀵硅薄锛屽垯杩斿洖true锛涘惁鍒欒繑鍥瀎alse銆�
+ */
+ @SuppressWarnings("rawtypes")
+ public boolean isFilterObject(final Object o) {
+ Class<?> clazz = o.getClass();
+ if (clazz.isArray()) {
+ return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
+ } else if (Collection.class.isAssignableFrom(clazz)) {
+ Collection collection = (Collection) o;
+ for (Object value : collection) {
+ return value instanceof MultipartFile;
+ }
+ } else if (Map.class.isAssignableFrom(clazz)) {
+ Map map = (Map) o;
+ for (Object value : map.values()) {
+ return value instanceof MultipartFile;
+ }
+ }
+ return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
+ || o instanceof BindingResult;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/config/IdempotentConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/config/IdempotentConfig.java
new file mode 100755
index 0000000..fcb9d03
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/config/IdempotentConfig.java
@@ -0,0 +1,21 @@
+package org.dromara.common.idempotent.config;
+
+import org.dromara.common.idempotent.aspectj.RepeatSubmitAspect;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.redis.connection.RedisConfiguration;
+
+/**
+ * 骞傜瓑鍔熻兘閰嶇疆
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration(after = RedisConfiguration.class)
+public class IdempotentConfig {
+
+ @Bean
+ public RepeatSubmitAspect repeatSubmitAspect() {
+ return new RepeatSubmitAspect();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..f2fa958
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-idempotent/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.idempotent.config.IdempotentConfig
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/.flattened-pom.xml
new file mode 100644
index 0000000..80fa967
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/.flattened-pom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-job</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-job 瀹氭椂浠诲姟</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-autoconfigure</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.aizuda</groupId>
+ <artifactId>snail-job-client-starter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.aizuda</groupId>
+ <artifactId>snail-job-client-job-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/pom.xml
new file mode 100755
index 0000000..3a4a0cb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="http://maven.apache.org/POM/4.0.0"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-job</artifactId>
+
+ <description>
+ ruoyi-common-job 瀹氭椂浠诲姟
+ </description>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-autoconfigure</artifactId>
+ </dependency>
+
+ <!-- SnailJob client -->
+ <dependency>
+ <groupId>com.aizuda</groupId>
+ <artifactId>snail-job-client-starter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.aizuda</groupId>
+ <artifactId>snail-job-client-job-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/SnailJobConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/SnailJobConfig.java
new file mode 100755
index 0000000..cba3753
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/SnailJobConfig.java
@@ -0,0 +1,37 @@
+package org.dromara.common.job.config;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import com.aizuda.snailjob.client.common.appender.SnailLogbackAppender;
+import com.aizuda.snailjob.client.common.event.SnailClientStartingEvent;
+import com.aizuda.snailjob.client.starter.EnableSnailJob;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+/**
+ * 鍚姩瀹氭椂浠诲姟
+ *
+ * @author opensnail
+ * @date 2024-05-17
+ */
+@AutoConfiguration
+@ConditionalOnProperty(prefix = "snail-job", name = "enabled", havingValue = "true")
+@EnableScheduling
+@EnableSnailJob
+public class SnailJobConfig {
+
+ @EventListener(SnailClientStartingEvent.class)
+ public void onStarting(SnailClientStartingEvent event) {
+ LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+ SnailLogbackAppender<ILoggingEvent> ca = new SnailLogbackAppender<>();
+ ca.setName("snail_log_appender");
+ ca.start();
+ Logger rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
+ rootLogger.addAppender(ca);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..3aa1881
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.job.config.SnailJobConfig
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/.flattened-pom.xml
new file mode 100644
index 0000000..edf9126
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/.flattened-pom.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-json 搴忓垪鍖栨ā鍧�</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jsr310</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/pom.xml
new file mode 100755
index 0000000..870df5c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/pom.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-json</artifactId>
+
+ <description>
+ ruoyi-common-json 搴忓垪鍖栨ā鍧�
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+
+ <!-- JSON宸ュ叿绫� -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jsr310</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java
new file mode 100755
index 0000000..0392573
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java
@@ -0,0 +1,55 @@
+package org.dromara.common.json.config;
+
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.json.handler.BigNumberSerializer;
+import org.dromara.common.json.handler.CustomDateDeserializer;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * jackson 閰嶇疆
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@AutoConfiguration(before = JacksonAutoConfiguration.class)
+public class JacksonConfig {
+
+ @Bean
+ public Module registerJavaTimeModule() {
+ // 鍏ㄥ眬閰嶇疆搴忓垪鍖栬繑鍥� JSON 澶勭悊
+ JavaTimeModule javaTimeModule = new JavaTimeModule();
+ javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
+ javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
+ javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
+ javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
+ javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
+ javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer());
+ return javaTimeModule;
+ }
+
+ @Bean
+ public Jackson2ObjectMapperBuilderCustomizer customizer() {
+ return builder -> {
+ builder.timeZone(TimeZone.getDefault());
+ log.info("鍒濆鍖� jackson 閰嶇疆");
+ };
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/BigNumberSerializer.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/BigNumberSerializer.java
new file mode 100755
index 0000000..8752353
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/BigNumberSerializer.java
@@ -0,0 +1,42 @@
+package org.dromara.common.json.handler;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
+import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
+
+import java.io.IOException;
+
+/**
+ * 瓒呭嚭 JS 鏈�澶ф渶灏忓�� 澶勭悊
+ *
+ * @author Lion Li
+ */
+@JacksonStdImpl
+public class BigNumberSerializer extends NumberSerializer {
+
+ /**
+ * 鏍规嵁 JS Number.MAX_SAFE_INTEGER 涓� Number.MIN_SAFE_INTEGER 寰楁潵
+ */
+ private static final long MAX_SAFE_INTEGER = 9007199254740991L;
+ private static final long MIN_SAFE_INTEGER = -9007199254740991L;
+
+ /**
+ * 鎻愪緵瀹炰緥
+ */
+ public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class);
+
+ public BigNumberSerializer(Class<? extends Number> rawType) {
+ super(rawType);
+ }
+
+ @Override
+ public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+ // 瓒呭嚭鑼冨洿 搴忓垪鍖栦负瀛楃涓�
+ if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
+ super.serialize(value, gen, provider);
+ } else {
+ gen.writeString(value.toString());
+ }
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java
new file mode 100755
index 0000000..21c6a6a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java
@@ -0,0 +1,37 @@
+package org.dromara.common.json.handler;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import org.dromara.common.core.utils.ObjectUtils;
+
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * 鑷畾涔� Date 绫诲瀷鍙嶅簭鍒楀寲澶勭悊鍣紙鏀寔澶氱鏍煎紡锛�
+ *
+ * @author AprilWind
+ */
+public class CustomDateDeserializer extends JsonDeserializer<Date> {
+
+ /**
+ * 鍙嶅簭鍒楀寲閫昏緫锛氬皢瀛楃涓茶浆鎹负 Date 瀵硅薄
+ *
+ * @param p JSON 瑙f瀽鍣紝鐢ㄤ簬鑾峰彇瀛楃涓插��
+ * @param ctxt 涓婁笅鏂囩幆澧冿紙鍙敤浜庤幏鍙栨洿澶氶厤缃級
+ * @return 杞崲鍚庣殑 Date 瀵硅薄锛岃嫢涓虹┖瀛楃涓茶繑鍥� null
+ * @throws IOException 褰撳瓧绗︿覆鏍煎紡闈炴硶鎴栬浆鎹㈠け璐ユ椂鎶涘嚭
+ */
+ @Override
+ public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ DateTime parse = DateUtil.parse(p.getText());
+ if (ObjectUtils.isNull(parse)) {
+ return null;
+ }
+ return parse.toJdkDate();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java
new file mode 100755
index 0000000..837e15d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java
@@ -0,0 +1,225 @@
+package org.dromara.common.json.utils;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * JSON 宸ュ叿绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class JsonUtils {
+
+ private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class);
+
+ public static ObjectMapper getObjectMapper() {
+ return OBJECT_MAPPER;
+ }
+
+ /**
+ * 灏嗗璞¤浆鎹负JSON鏍煎紡鐨勫瓧绗︿覆
+ *
+ * @param object 瑕佽浆鎹㈢殑瀵硅薄
+ * @return JSON鏍煎紡鐨勫瓧绗︿覆锛屽鏋滃璞′负null锛屽垯杩斿洖null
+ * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烰SON澶勭悊寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+ */
+ public static String toJsonString(Object object) {
+ if (ObjectUtil.isNull(object)) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.writeValueAsString(object);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 灏咼SON鏍煎紡鐨勫瓧绗︿覆杞崲涓烘寚瀹氱被鍨嬬殑瀵硅薄
+ *
+ * @param text JSON鏍煎紡鐨勫瓧绗︿覆
+ * @param clazz 瑕佽浆鎹㈢殑鐩爣瀵硅薄绫诲瀷
+ * @param <T> 鐩爣瀵硅薄鐨勬硾鍨嬬被鍨�
+ * @return 杞崲鍚庣殑瀵硅薄锛屽鏋滃瓧绗︿覆涓虹┖鍒欒繑鍥瀗ull
+ * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烮O寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+ */
+ public static <T> T parseObject(String text, Class<T> clazz) {
+ if (StringUtils.isEmpty(text)) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.readValue(text, clazz);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 灏嗗瓧鑺傛暟缁勮浆鎹负鎸囧畾绫诲瀷鐨勫璞�
+ *
+ * @param bytes 瀛楄妭鏁扮粍
+ * @param clazz 瑕佽浆鎹㈢殑鐩爣瀵硅薄绫诲瀷
+ * @param <T> 鐩爣瀵硅薄鐨勬硾鍨嬬被鍨�
+ * @return 杞崲鍚庣殑瀵硅薄锛屽鏋滃瓧鑺傛暟缁勪负绌哄垯杩斿洖null
+ * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烮O寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+ */
+ public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
+ if (ArrayUtil.isEmpty(bytes)) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.readValue(bytes, clazz);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 灏咼SON鏍煎紡鐨勫瓧绗︿覆杞崲涓烘寚瀹氱被鍨嬬殑瀵硅薄锛屾敮鎸佸鏉傜被鍨�
+ *
+ * @param text JSON鏍煎紡鐨勫瓧绗︿覆
+ * @param typeReference 鎸囧畾绫诲瀷鐨凾ypeReference瀵硅薄
+ * @param <T> 鐩爣瀵硅薄鐨勬硾鍨嬬被鍨�
+ * @return 杞崲鍚庣殑瀵硅薄锛屽鏋滃瓧绗︿覆涓虹┖鍒欒繑鍥瀗ull
+ * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烮O寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+ */
+ public static <T> T parseObject(String text, TypeReference<T> typeReference) {
+ if (StringUtils.isBlank(text)) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.readValue(text, typeReference);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 灏咼SON鏍煎紡鐨勫瓧绗︿覆杞崲涓篋ict瀵硅薄
+ *
+ * @param text JSON鏍煎紡鐨勫瓧绗︿覆
+ * @return 杞崲鍚庣殑Dict瀵硅薄锛屽鏋滃瓧绗︿覆涓虹┖鎴栬�呬笉鏄疛SON鏍煎紡鍒欒繑鍥瀗ull
+ * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烮O寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+ */
+ public static Dict parseMap(String text) {
+ if (StringUtils.isBlank(text)) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class));
+ } catch (MismatchedInputException e) {
+ // 绫诲瀷涓嶅尮閰嶈鏄庝笉鏄痡son
+ return null;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 灏咼SON鏍煎紡鐨勫瓧绗︿覆杞崲涓篋ict瀵硅薄鐨勫垪琛�
+ *
+ * @param text JSON鏍煎紡鐨勫瓧绗︿覆
+ * @return 杞崲鍚庣殑Dict瀵硅薄鐨勫垪琛紝濡傛灉瀛楃涓蹭负绌哄垯杩斿洖null
+ * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烮O寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+ */
+ public static List<Dict> parseArrayMap(String text) {
+ if (StringUtils.isBlank(text)) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 灏咼SON鏍煎紡鐨勫瓧绗︿覆杞崲涓烘寚瀹氱被鍨嬪璞$殑鍒楄〃
+ *
+ * @param text JSON鏍煎紡鐨勫瓧绗︿覆
+ * @param clazz 瑕佽浆鎹㈢殑鐩爣瀵硅薄绫诲瀷
+ * @param <T> 鐩爣瀵硅薄鐨勬硾鍨嬬被鍨�
+ * @return 杞崲鍚庣殑瀵硅薄鐨勫垪琛紝濡傛灉瀛楃涓蹭负绌哄垯杩斿洖绌哄垪琛�
+ * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烮O寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+ */
+ public static <T> List<T> parseArray(String text, Class<T> clazz) {
+ if (StringUtils.isEmpty(text)) {
+ return new ArrayList<>();
+ }
+ try {
+ return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 鍒ゆ柇瀛楃涓叉槸鍚︿负鍚堟硶 JSON锛堝璞℃垨鏁扮粍锛�
+ *
+ * @param str 寰呮牎楠屽瓧绗︿覆
+ * @return true = 鍚堟硶 JSON锛宖alse = 闈炴硶鎴栫┖
+ */
+ public static boolean isJson(String str) {
+ if (StringUtils.isBlank(str)) {
+ return false;
+ }
+ try {
+ OBJECT_MAPPER.readTree(str);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * 鍒ゆ柇瀛楃涓叉槸鍚︿负 JSON 瀵硅薄锛坽}锛�
+ *
+ * @param str 寰呮牎楠屽瓧绗︿覆
+ * @return true = JSON 瀵硅薄
+ */
+ public static boolean isJsonObject(String str) {
+ if (StringUtils.isBlank(str)) {
+ return false;
+ }
+ try {
+ JsonNode node = OBJECT_MAPPER.readTree(str);
+ return node.isObject();
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * 鍒ゆ柇瀛楃涓叉槸鍚︿负 JSON 鏁扮粍锛圼]锛�
+ *
+ * @param str 寰呮牎楠屽瓧绗︿覆
+ * @return true = JSON 鏁扮粍
+ */
+ public static boolean isJsonArray(String str) {
+ if (StringUtils.isBlank(str)) {
+ return false;
+ }
+ try {
+ JsonNode node = OBJECT_MAPPER.readTree(str);
+ return node.isArray();
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/validate/JsonPattern.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/validate/JsonPattern.java
new file mode 100755
index 0000000..9b9538f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/validate/JsonPattern.java
@@ -0,0 +1,33 @@
+package org.dromara.common.json.validate;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.*;
+
+/**
+ * JSON 鏍煎紡鏍¢獙娉ㄨВ
+ *
+ * @author AprilWind
+ */
+@Documented
+@Target({ElementType.METHOD, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = JsonPatternValidator.class)
+public @interface JsonPattern {
+
+ /**
+ * 闄愬埗 JSON 绫诲瀷锛岄粯璁や负 {@link JsonType#ANY}锛屽嵆瀵硅薄鎴栨暟缁勯兘鍏佽
+ */
+ JsonType type() default JsonType.ANY;
+
+ /**
+ * 鏍¢獙澶辫触鏃剁殑鎻愮ず娑堟伅
+ */
+ String message() default "涓嶆槸鏈夋晥鐨� JSON 鏍煎紡";
+
+ Class<?>[] groups() default {};
+
+ Class<? extends Payload>[] payload() default {};
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/validate/JsonPatternValidator.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/validate/JsonPatternValidator.java
new file mode 100755
index 0000000..8747fa3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/validate/JsonPatternValidator.java
@@ -0,0 +1,51 @@
+package org.dromara.common.json.validate;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+
+/**
+ * JSON 鏍煎紡鏍¢獙鍣�
+ *
+ * @author AprilWind
+ */
+public class JsonPatternValidator implements ConstraintValidator<JsonPattern, String> {
+
+ /**
+ * 娉ㄨВ涓寚瀹氱殑 JSON 绫诲瀷鏋氫妇
+ */
+ private JsonType jsonType;
+
+ /**
+ * 鍒濆鍖栨牎楠屽櫒锛屼粠娉ㄨВ涓彁鍙� JSON 绫诲瀷
+ *
+ * @param annotation 娉ㄨВ瀹炰緥
+ */
+ @Override
+ public void initialize(JsonPattern annotation) {
+ this.jsonType = annotation.type();
+ }
+
+ /**
+ * 鏍¢獙瀛楃涓叉槸鍚︿负鍚堟硶 JSON
+ *
+ * @param value 寰呮牎楠屽瓧绗︿覆
+ * @param context 鏍¢獙涓婁笅鏂囷紝鍙敤浜庤嚜瀹氫箟閿欒淇℃伅
+ * @return true = 鍚堟硶 JSON 鎴栦负绌猴紝false = 闈炴硶 JSON
+ */
+ @Override
+ public boolean isValid(String value, ConstraintValidatorContext context) {
+ if (StringUtils.isBlank(value)) {
+ // 浜ょ粰 @NotBlank 鎴� @NotNull 鎺у埗鏄惁鍏佽涓虹┖
+ return true;
+ }
+ // 鏍规嵁 JSON 绫诲瀷杩涜涓嶅悓鐨勬牎楠�
+ return switch (jsonType) {
+ case ANY -> JsonUtils.isJson(value);
+ case OBJECT -> JsonUtils.isJsonObject(value);
+ case ARRAY -> JsonUtils.isJsonArray(value);
+ };
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/validate/JsonType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/validate/JsonType.java
new file mode 100755
index 0000000..6ac53e4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/validate/JsonType.java
@@ -0,0 +1,30 @@
+package org.dromara.common.json.validate;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * JSON 绫诲瀷鏋氫妇
+ *
+ * @author AprilWind
+ */
+@Getter
+@AllArgsConstructor
+public enum JsonType {
+
+ /**
+ * JSON 瀵硅薄锛屼緥濡� {"a":1}
+ */
+ OBJECT,
+
+ /**
+ * JSON 鏁扮粍锛屼緥濡� [1,2,3]
+ */
+ ARRAY,
+
+ /**
+ * 浠绘剰 JSON 绫诲瀷锛屽璞℃垨鏁扮粍閮藉彲浠�
+ */
+ ANY
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..1625397
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.json.config.JacksonConfig
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/.flattened-pom.xml
new file mode 100644
index 0000000..fe16c95
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/.flattened-pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-log</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-log 鏃ュ織璁板綍</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-satoken</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/pom.xml
new file mode 100755
index 0000000..1e2b33b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-log</artifactId>
+
+ <description>
+ ruoyi-common-log 鏃ュ織璁板綍
+ </description>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-satoken</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/annotation/Log.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/annotation/Log.java
new file mode 100755
index 0000000..2dced97
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/annotation/Log.java
@@ -0,0 +1,48 @@
+package org.dromara.common.log.annotation;
+
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.log.enums.OperatorType;
+
+import java.lang.annotation.*;
+
+/**
+ * 鑷畾涔夋搷浣滄棩蹇楄褰曟敞瑙�
+ *
+ * @author ruoyi
+ */
+@Target({ElementType.PARAMETER, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Log {
+ /**
+ * 妯″潡
+ */
+ String title() default "";
+
+ /**
+ * 鍔熻兘
+ */
+ BusinessType businessType() default BusinessType.OTHER;
+
+ /**
+ * 鎿嶄綔浜虹被鍒�
+ */
+ OperatorType operatorType() default OperatorType.MANAGE;
+
+ /**
+ * 鏄惁淇濆瓨璇锋眰鐨勫弬鏁�
+ */
+ boolean isSaveRequestData() default true;
+
+ /**
+ * 鏄惁淇濆瓨鍝嶅簲鐨勫弬鏁�
+ */
+ boolean isSaveResponseData() default true;
+
+
+ /**
+ * 鎺掗櫎鎸囧畾鐨勮姹傚弬鏁�
+ */
+ String[] excludeParamNames() default {};
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java
new file mode 100755
index 0000000..2c22811
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java
@@ -0,0 +1,226 @@
+package org.dromara.common.log.aspect;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.time.StopWatch;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessStatus;
+import org.dromara.common.log.event.OperLogEvent;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.http.HttpMethod;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.*;
+
+/**
+ * 鎿嶄綔鏃ュ織璁板綍澶勭悊
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Aspect
+@AutoConfiguration
+public class LogAspect {
+
+ /**
+ * 璁℃椂 key
+ */
+ private static final ThreadLocal<StopWatch> KEY_CACHE = new ThreadLocal<>();
+
+ /**
+ * 澶勭悊璇锋眰鍓嶆墽琛�
+ */
+ @Before(value = "@annotation(controllerLog)")
+ public void doBefore(JoinPoint joinPoint, Log controllerLog) {
+ StopWatch stopWatch = new StopWatch();
+ KEY_CACHE.set(stopWatch);
+ stopWatch.start();
+ }
+
+ /**
+ * 澶勭悊瀹岃姹傚悗鎵ц
+ *
+ * @param joinPoint 鍒囩偣
+ */
+ @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
+ public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
+ handleLog(joinPoint, controllerLog, null, jsonResult);
+ }
+
+ /**
+ * 鎷︽埅寮傚父鎿嶄綔
+ *
+ * @param joinPoint 鍒囩偣
+ * @param e 寮傚父
+ */
+ @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
+ public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
+ handleLog(joinPoint, controllerLog, e, null);
+ }
+
+ protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
+ try {
+
+ // *========鏁版嵁搴撴棩蹇�=========*//
+ OperLogEvent operLog = new OperLogEvent();
+ operLog.setTenantId(LoginHelper.getTenantId());
+ operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
+ // 璇锋眰鐨勫湴鍧�
+ String ip = ServletUtils.getClientIP();
+ operLog.setOperIp(ip);
+ operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
+ LoginUser loginUser = LoginHelper.getLoginUser();
+ operLog.setOperName(loginUser.getUsername());
+ operLog.setDeptName(loginUser.getDeptName());
+
+ if (e != null) {
+ operLog.setStatus(BusinessStatus.FAIL.ordinal());
+ operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 3800));
+ }
+ // 璁剧疆鏂规硶鍚嶇О
+ String className = joinPoint.getTarget().getClass().getName();
+ String methodName = joinPoint.getSignature().getName();
+ operLog.setMethod(className + "." + methodName + "()");
+ // 璁剧疆璇锋眰鏂瑰紡
+ operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
+ // 澶勭悊璁剧疆娉ㄨВ涓婄殑鍙傛暟
+ getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
+ // 璁剧疆娑堣�楁椂闂�
+ StopWatch stopWatch = KEY_CACHE.get();
+ stopWatch.stop();
+ operLog.setCostTime(stopWatch.getDuration().toMillis());
+ // 鍙戝竷浜嬩欢淇濆瓨鏁版嵁搴�
+ SpringUtils.context().publishEvent(operLog);
+ } catch (Exception exp) {
+ // 璁板綍鏈湴寮傚父鏃ュ織
+ log.error("寮傚父淇℃伅:{}", exp.getMessage());
+ } finally {
+ KEY_CACHE.remove();
+ }
+ }
+
+ /**
+ * 鑾峰彇娉ㄨВ涓鏂规硶鐨勬弿杩颁俊鎭� 鐢ㄤ簬Controller灞傛敞瑙�
+ *
+ * @param log 鏃ュ織
+ * @param operLog 鎿嶄綔鏃ュ織
+ * @throws Exception
+ */
+ public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperLogEvent operLog, Object jsonResult) throws Exception {
+ // 璁剧疆action鍔ㄤ綔
+ operLog.setBusinessType(log.businessType().ordinal());
+ // 璁剧疆鏍囬
+ operLog.setTitle(log.title());
+ // 璁剧疆鎿嶄綔浜虹被鍒�
+ operLog.setOperatorType(log.operatorType().ordinal());
+ // 鏄惁闇�瑕佷繚瀛榬equest锛屽弬鏁板拰鍊�
+ if (log.isSaveRequestData()) {
+ // 鑾峰彇鍙傛暟鐨勪俊鎭紝浼犲叆鍒版暟鎹簱涓��
+ setRequestValue(joinPoint, operLog, log.excludeParamNames());
+ }
+ // 鏄惁闇�瑕佷繚瀛榬esponse锛屽弬鏁板拰鍊�
+ if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {
+ operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 3800));
+ }
+ }
+
+ /**
+ * 鑾峰彇璇锋眰鐨勫弬鏁帮紝鏀惧埌log涓�
+ *
+ * @param operLog 鎿嶄綔鏃ュ織
+ * @throws Exception 寮傚父
+ */
+ private void setRequestValue(JoinPoint joinPoint, OperLogEvent operLog, String[] excludeParamNames) throws Exception {
+ Map<String, String> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
+ String requestMethod = operLog.getRequestMethod();
+ if (MapUtil.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) {
+ String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
+ operLog.setOperParam(StringUtils.substring(params, 0, 3800));
+ } else {
+ MapUtil.removeAny(paramsMap, SystemConstants.EXCLUDE_PROPERTIES);
+ MapUtil.removeAny(paramsMap, excludeParamNames);
+ operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 3800));
+ }
+ }
+
+ /**
+ * 鍙傛暟鎷艰
+ */
+ private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
+ StringJoiner params = new StringJoiner(" ");
+ if (ArrayUtil.isEmpty(paramsArray)) {
+ return params.toString();
+ }
+ String[] exclude = ArrayUtil.addAll(excludeParamNames, SystemConstants.EXCLUDE_PROPERTIES);
+ for (Object o : paramsArray) {
+ if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
+ String str = "";
+ if (o instanceof List<?> list) {
+ List<Dict> list1 = new ArrayList<>();
+ for (Object obj : list) {
+ String str1 = JsonUtils.toJsonString(obj);
+ Dict dict = JsonUtils.parseMap(str1);
+ if (MapUtil.isNotEmpty(dict)) {
+ MapUtil.removeAny(dict, exclude);
+ list1.add(dict);
+ }
+ }
+ str = JsonUtils.toJsonString(list1);
+ } else {
+ str = JsonUtils.toJsonString(o);
+ Dict dict = JsonUtils.parseMap(str);
+ if (MapUtil.isNotEmpty(dict)) {
+ MapUtil.removeAny(dict, exclude);
+ str = JsonUtils.toJsonString(dict);
+ }
+ }
+ params.add(str);
+ }
+ }
+ return params.toString();
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁闇�瑕佽繃婊ょ殑瀵硅薄銆�
+ *
+ * @param o 瀵硅薄淇℃伅銆�
+ * @return 濡傛灉鏄渶瑕佽繃婊ょ殑瀵硅薄锛屽垯杩斿洖true锛涘惁鍒欒繑鍥瀎alse銆�
+ */
+ @SuppressWarnings("rawtypes")
+ public boolean isFilterObject(final Object o) {
+ Class<?> clazz = o.getClass();
+ if (clazz.isArray()) {
+ return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
+ } else if (Collection.class.isAssignableFrom(clazz)) {
+ Collection collection = (Collection) o;
+ for (Object value : collection) {
+ return value instanceof MultipartFile;
+ }
+ } else if (Map.class.isAssignableFrom(clazz)) {
+ Map map = (Map) o;
+ for (Object value : map.values()) {
+ return value instanceof MultipartFile;
+ }
+ }
+ return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
+ || o instanceof BindingResult;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessStatus.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessStatus.java
new file mode 100755
index 0000000..d303dc3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessStatus.java
@@ -0,0 +1,18 @@
+package org.dromara.common.log.enums;
+
+/**
+ * 鎿嶄綔鐘舵��
+ *
+ * @author ruoyi
+ */
+public enum BusinessStatus {
+ /**
+ * 鎴愬姛
+ */
+ SUCCESS,
+
+ /**
+ * 澶辫触
+ */
+ FAIL,
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessType.java
new file mode 100755
index 0000000..2d25ebb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessType.java
@@ -0,0 +1,58 @@
+package org.dromara.common.log.enums;
+
+/**
+ * 涓氬姟鎿嶄綔绫诲瀷
+ *
+ * @author ruoyi
+ */
+public enum BusinessType {
+ /**
+ * 鍏跺畠
+ */
+ OTHER,
+
+ /**
+ * 鏂板
+ */
+ INSERT,
+
+ /**
+ * 淇敼
+ */
+ UPDATE,
+
+ /**
+ * 鍒犻櫎
+ */
+ DELETE,
+
+ /**
+ * 鎺堟潈
+ */
+ GRANT,
+
+ /**
+ * 瀵煎嚭
+ */
+ EXPORT,
+
+ /**
+ * 瀵煎叆
+ */
+ IMPORT,
+
+ /**
+ * 寮洪��
+ */
+ FORCE,
+
+ /**
+ * 鐢熸垚浠g爜
+ */
+ GENCODE,
+
+ /**
+ * 娓呯┖鏁版嵁
+ */
+ CLEAN,
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/OperatorType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/OperatorType.java
new file mode 100755
index 0000000..de9328b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/OperatorType.java
@@ -0,0 +1,23 @@
+package org.dromara.common.log.enums;
+
+/**
+ * 鎿嶄綔浜虹被鍒�
+ *
+ * @author ruoyi
+ */
+public enum OperatorType {
+ /**
+ * 鍏跺畠
+ */
+ OTHER,
+
+ /**
+ * 鍚庡彴鐢ㄦ埛
+ */
+ MANAGE,
+
+ /**
+ * 鎵嬫満绔敤鎴�
+ */
+ MOBILE
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogininforEvent.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogininforEvent.java
new file mode 100755
index 0000000..938eaad
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogininforEvent.java
@@ -0,0 +1,52 @@
+package org.dromara.common.log.event;
+
+import lombok.Data;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 鐧诲綍浜嬩欢
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class LogininforEvent implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 鐢ㄦ埛璐﹀彿
+ */
+ private String username;
+
+ /**
+ * 鐧诲綍鐘舵�� 0鎴愬姛 1澶辫触
+ */
+ private String status;
+
+ /**
+ * 鎻愮ず娑堟伅
+ */
+ private String message;
+
+ /**
+ * 璇锋眰浣�
+ */
+ private HttpServletRequest request;
+
+ /**
+ * 鍏朵粬鍙傛暟
+ */
+ private Object[] args;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/OperLogEvent.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/OperLogEvent.java
new file mode 100755
index 0000000..0386192
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/OperLogEvent.java
@@ -0,0 +1,115 @@
+package org.dromara.common.log.event;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 鎿嶄綔鏃ュ織浜嬩欢
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class OperLogEvent implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃ュ織涓婚敭
+ */
+ private Long operId;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 鎿嶄綔妯″潡
+ */
+ private String title;
+
+ /**
+ * 涓氬姟绫诲瀷锛�0鍏跺畠 1鏂板 2淇敼 3鍒犻櫎锛�
+ */
+ private Integer businessType;
+
+ /**
+ * 涓氬姟绫诲瀷鏁扮粍
+ */
+ private Integer[] businessTypes;
+
+ /**
+ * 璇锋眰鏂规硶
+ */
+ private String method;
+
+ /**
+ * 璇锋眰鏂瑰紡
+ */
+ private String requestMethod;
+
+ /**
+ * 鎿嶄綔绫诲埆锛�0鍏跺畠 1鍚庡彴鐢ㄦ埛 2鎵嬫満绔敤鎴凤級
+ */
+ private Integer operatorType;
+
+ /**
+ * 鎿嶄綔浜哄憳
+ */
+ private String operName;
+
+ /**
+ * 閮ㄩ棬鍚嶇О
+ */
+ private String deptName;
+
+ /**
+ * 璇锋眰url
+ */
+ private String operUrl;
+
+ /**
+ * 鎿嶄綔鍦板潃
+ */
+ private String operIp;
+
+ /**
+ * 鎿嶄綔鍦扮偣
+ */
+ private String operLocation;
+
+ /**
+ * 璇锋眰鍙傛暟
+ */
+ private String operParam;
+
+ /**
+ * 杩斿洖鍙傛暟
+ */
+ private String jsonResult;
+
+ /**
+ * 鎿嶄綔鐘舵�侊紙0姝e父 1寮傚父锛�
+ */
+ private Integer status;
+
+ /**
+ * 閿欒娑堟伅
+ */
+ private String errorMsg;
+
+ /**
+ * 鎿嶄綔鏃堕棿
+ */
+ private Date operTime;
+
+ /**
+ * 娑堣�楁椂闂�
+ */
+ private Long costTime;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..6893020
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.log.aspect.LogAspect
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/.flattened-pom.xml
new file mode 100644
index 0000000..cf9d4ff
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/.flattened-pom.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mail</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-mail 閭欢妯″潡</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.mail</groupId>
+ <artifactId>jakarta.mail-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.angus</groupId>
+ <artifactId>jakarta.mail</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/pom.xml
new file mode 100755
index 0000000..c0e1b2e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-mail</artifactId>
+
+ <description>
+ ruoyi-common-mail 閭欢妯″潡
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>jakarta.mail</groupId>
+ <artifactId>jakarta.mail-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.angus</groupId>
+ <artifactId>jakarta.mail</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfig.java
new file mode 100755
index 0000000..0ea3007
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfig.java
@@ -0,0 +1,37 @@
+package org.dromara.common.mail.config;
+
+import cn.hutool.extra.mail.MailAccount;
+import org.dromara.common.mail.config.properties.MailProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * JavaMail 閰嶇疆
+ *
+ * @author Michelle.Chung
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(MailProperties.class)
+public class MailConfig {
+
+ @Bean
+ @ConditionalOnProperty(value = "mail.enabled", havingValue = "true")
+ public MailAccount mailAccount(MailProperties mailProperties) {
+ MailAccount account = new MailAccount();
+ account.setHost(mailProperties.getHost());
+ account.setPort(mailProperties.getPort());
+ account.setAuth(mailProperties.getAuth());
+ account.setFrom(mailProperties.getFrom());
+ account.setUser(mailProperties.getUser());
+ account.setPass(mailProperties.getPass());
+ account.setSocketFactoryPort(mailProperties.getPort());
+ account.setStarttlsEnable(mailProperties.getStarttlsEnable());
+ account.setSslEnable(mailProperties.getSslEnable());
+ account.setTimeout(mailProperties.getTimeout());
+ account.setConnectionTimeout(mailProperties.getConnectionTimeout());
+ return account;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/properties/MailProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/properties/MailProperties.java
new file mode 100755
index 0000000..e44aa3d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/properties/MailProperties.java
@@ -0,0 +1,75 @@
+package org.dromara.common.mail.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * JavaMail 閰嶇疆灞炴��
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@ConfigurationProperties(prefix = "mail")
+public class MailProperties {
+
+ /**
+ * 杩囨护寮�鍏�
+ */
+ private Boolean enabled;
+
+ /**
+ * SMTP鏈嶅姟鍣ㄥ煙鍚�
+ */
+ private String host;
+
+ /**
+ * SMTP鏈嶅姟绔彛
+ */
+ private Integer port;
+
+ /**
+ * 鏄惁闇�瑕佺敤鎴峰悕瀵嗙爜楠岃瘉
+ */
+ private Boolean auth;
+
+ /**
+ * 鐢ㄦ埛鍚�
+ */
+ private String user;
+
+ /**
+ * 瀵嗙爜
+ */
+ private String pass;
+
+ /**
+ * 鍙戦�佹柟锛岄伒寰猂FC-822鏍囧噯<br>
+ * 鍙戜欢浜哄彲浠ユ槸浠ヤ笅褰㈠紡锛�
+ *
+ * <pre>
+ * 1. user@xxx.xx
+ * 2. name <user@xxx.xx>
+ * </pre>
+ */
+ private String from;
+
+ /**
+ * 浣跨敤 STARTTLS瀹夊叏杩炴帴锛孲TARTTLS鏄绾枃鏈�氫俊鍗忚鐨勬墿灞曘�傚畠灏嗙函鏂囨湰杩炴帴鍗囩骇涓哄姞瀵嗚繛鎺ワ紙TLS鎴朣SL锛夛紝 鑰屼笉鏄娇鐢ㄤ竴涓崟鐙殑鍔犲瘑閫氫俊绔彛銆�
+ */
+ private Boolean starttlsEnable;
+
+ /**
+ * 浣跨敤 SSL瀹夊叏杩炴帴
+ */
+ private Boolean sslEnable;
+
+ /**
+ * SMTP瓒呮椂鏃堕暱锛屽崟浣嶆绉掞紝缂虹渷鍊间笉瓒呮椂
+ */
+ private Long timeout;
+
+ /**
+ * Socket杩炴帴瓒呮椂鍊硷紝鍗曚綅姣锛岀己鐪佸�间笉瓒呮椂
+ */
+ private Long connectionTimeout;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java
new file mode 100755
index 0000000..793c024
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java
@@ -0,0 +1,469 @@
+package org.dromara.common.mail.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.CharUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.mail.JakartaMail;
+import cn.hutool.extra.mail.JakartaUserPassAuthenticator;
+import cn.hutool.extra.mail.MailAccount;
+import jakarta.mail.Authenticator;
+import jakarta.mail.Session;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * 閭欢宸ュ叿绫�
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MailUtils {
+
+ private static final MailAccount ACCOUNT = SpringUtils.getBean(MailAccount.class);
+
+ /**
+ * 鑾峰彇閭欢鍙戦�佸疄渚�
+ */
+ public static MailAccount getMailAccount() {
+ return ACCOUNT;
+ }
+
+ /**
+ * 鑾峰彇閭欢鍙戦�佸疄渚� (鑷畾涔夊彂閫佷汉浠ュ強鎺堟潈鐮�)
+ *
+ * @param user 鍙戦�佷汉
+ * @param pass 鎺堟潈鐮�
+ */
+ public static MailAccount getMailAccount(String from, String user, String pass) {
+ ACCOUNT.setFrom(StringUtils.blankToDefault(from, ACCOUNT.getFrom()));
+ ACCOUNT.setUser(StringUtils.blankToDefault(user, ACCOUNT.getUser()));
+ ACCOUNT.setPass(StringUtils.blankToDefault(pass, ACCOUNT.getPass()));
+ return ACCOUNT;
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�佹枃鏈偖浠讹紝鍙戦�佺粰鍗曚釜鎴栧涓敹浠朵汉<br>
+ * 澶氫釜鏀朵欢浜哄彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+ *
+ * @param to 鏀朵欢浜�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String sendText(String to, String subject, String content, File... files) {
+ return send(to, subject, content, false, files);
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�丠TML閭欢锛屽彂閫佺粰鍗曚釜鎴栧涓敹浠朵汉<br>
+ * 澶氫釜鏀朵欢浜哄彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+ *
+ * @param to 鏀朵欢浜�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String sendHtml(String to, String subject, String content, File... files) {
+ return send(to, subject, content, true, files);
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佸崟涓垨澶氫釜鏀朵欢浜�<br>
+ * 澶氫釜鏀朵欢浜哄彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+ *
+ * @param to 鏀朵欢浜�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param isHtml 鏄惁涓篐TML
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ */
+ public static String send(String to, String subject, String content, boolean isHtml, File... files) {
+ return send(splitAddress(to), subject, content, isHtml, files);
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佸崟涓垨澶氫釜鏀朵欢浜�<br>
+ * 澶氫釜鏀朵欢浜恒�佹妱閫佷汉銆佸瘑閫佷汉鍙互浣跨敤閫楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+ *
+ * @param to 鏀朵欢浜猴紝鍙互浣跨敤閫楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+ * @param cc 鎶勯�佷汉锛屽彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+ * @param bcc 瀵嗛�佷汉锛屽彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param isHtml 鏄惁涓篐TML
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 4.0.3
+ */
+ public static String send(String to, String cc, String bcc, String subject, String content, boolean isHtml, File... files) {
+ return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, isHtml, files);
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�佹枃鏈偖浠讹紝鍙戦�佺粰澶氫汉
+ *
+ * @param tos 鏀朵欢浜哄垪琛�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ */
+ public static String sendText(Collection<String> tos, String subject, String content, File... files) {
+ return send(tos, subject, content, false, files);
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�丠TML閭欢锛屽彂閫佺粰澶氫汉
+ *
+ * @param tos 鏀朵欢浜哄垪琛�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String sendHtml(Collection<String> tos, String subject, String content, File... files) {
+ return send(tos, subject, content, true, files);
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佺粰澶氫汉
+ *
+ * @param tos 鏀朵欢浜哄垪琛�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param isHtml 鏄惁涓篐TML
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ */
+ public static String send(Collection<String> tos, String subject, String content, boolean isHtml, File... files) {
+ return send(tos, null, null, subject, content, isHtml, files);
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佺粰澶氫汉
+ *
+ * @param tos 鏀朵欢浜哄垪琛�
+ * @param ccs 鎶勯�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+ * @param bccs 瀵嗛�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param isHtml 鏄惁涓篐TML
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 4.0.3
+ */
+ public static String send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) {
+ return send(getMailAccount(), true, tos, ccs, bccs, subject, content, null, isHtml, files);
+ }
+
+ // ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
+
+ /**
+ * 鍙戦�侀偖浠剁粰澶氫汉
+ *
+ * @param mailAccount 閭欢璁よ瘉瀵硅薄
+ * @param to 鏀朵欢浜猴紝澶氫釜鏀朵欢浜洪�楀彿鎴栬�呭垎鍙烽殧寮�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param isHtml 鏄惁涓篐TML鏍煎紡
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String send(MailAccount mailAccount, String to, String subject, String content, boolean isHtml, File... files) {
+ return send(mailAccount, splitAddress(to), subject, content, isHtml, files);
+ }
+
+ /**
+ * 鍙戦�侀偖浠剁粰澶氫汉
+ *
+ * @param mailAccount 閭欢璐︽埛淇℃伅
+ * @param tos 鏀朵欢浜哄垪琛�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param isHtml 鏄惁涓篐TML鏍煎紡
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ */
+ public static String send(MailAccount mailAccount, Collection<String> tos, String subject, String content, boolean isHtml, File... files) {
+ return send(mailAccount, tos, null, null, subject, content, isHtml, files);
+ }
+
+ /**
+ * 鍙戦�侀偖浠剁粰澶氫汉
+ *
+ * @param mailAccount 閭欢璐︽埛淇℃伅
+ * @param tos 鏀朵欢浜哄垪琛�
+ * @param ccs 鎶勯�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+ * @param bccs 瀵嗛�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param isHtml 鏄惁涓篐TML鏍煎紡
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 4.0.3
+ */
+ public static String send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) {
+ return send(mailAccount, false, tos, ccs, bccs, subject, content, null, isHtml, files);
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�丠TML閭欢锛屽彂閫佺粰鍗曚釜鎴栧涓敹浠朵汉<br>
+ * 澶氫釜鏀朵欢浜哄彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+ *
+ * @param to 鏀朵欢浜�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String sendHtml(String to, String subject, String content, Map<String, InputStream> imageMap, File... files) {
+ return send(to, subject, content, imageMap, true, files);
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佸崟涓垨澶氫釜鏀朵欢浜�<br>
+ * 澶氫釜鏀朵欢浜哄彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+ *
+ * @param to 鏀朵欢浜�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+ * @param isHtml 鏄惁涓篐TML
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ */
+ public static String send(String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+ return send(splitAddress(to), subject, content, imageMap, isHtml, files);
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佸崟涓垨澶氫釜鏀朵欢浜�<br>
+ * 澶氫釜鏀朵欢浜恒�佹妱閫佷汉銆佸瘑閫佷汉鍙互浣跨敤閫楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+ *
+ * @param to 鏀朵欢浜猴紝鍙互浣跨敤閫楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+ * @param cc 鎶勯�佷汉锛屽彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+ * @param bcc 瀵嗛�佷汉锛屽彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+ * @param isHtml 鏄惁涓篐TML
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 4.0.3
+ */
+ public static String send(String to, String cc, String bcc, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+ return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, imageMap, isHtml, files);
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�丠TML閭欢锛屽彂閫佺粰澶氫汉
+ *
+ * @param tos 鏀朵欢浜哄垪琛�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String sendHtml(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, File... files) {
+ return send(tos, subject, content, imageMap, true, files);
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佺粰澶氫汉
+ *
+ * @param tos 鏀朵欢浜哄垪琛�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+ * @param isHtml 鏄惁涓篐TML
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ */
+ public static String send(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+ return send(tos, null, null, subject, content, imageMap, isHtml, files);
+ }
+
+ /**
+ * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佺粰澶氫汉
+ *
+ * @param tos 鏀朵欢浜哄垪琛�
+ * @param ccs 鎶勯�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+ * @param bccs 瀵嗛�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+ * @param isHtml 鏄惁涓篐TML
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 4.0.3
+ */
+ public static String send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+ return send(getMailAccount(), true, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
+ }
+
+ // ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
+
+ /**
+ * 鍙戦�侀偖浠剁粰澶氫汉
+ *
+ * @param mailAccount 閭欢璁よ瘉瀵硅薄
+ * @param to 鏀朵欢浜猴紝澶氫釜鏀朵欢浜洪�楀彿鎴栬�呭垎鍙烽殧寮�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+ * @param isHtml 鏄惁涓篐TML鏍煎紡
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 3.2.0
+ */
+ public static String send(MailAccount mailAccount, String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+ return send(mailAccount, splitAddress(to), subject, content, imageMap, isHtml, files);
+ }
+
+ /**
+ * 鍙戦�侀偖浠剁粰澶氫汉
+ *
+ * @param mailAccount 閭欢璐︽埛淇℃伅
+ * @param tos 鏀朵欢浜哄垪琛�
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+ * @param isHtml 鏄惁涓篐TML鏍煎紡
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 4.6.3
+ */
+ public static String send(MailAccount mailAccount, Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+ return send(mailAccount, tos, null, null, subject, content, imageMap, isHtml, files);
+ }
+
+ /**
+ * 鍙戦�侀偖浠剁粰澶氫汉
+ *
+ * @param mailAccount 閭欢璐︽埛淇℃伅
+ * @param tos 鏀朵欢浜哄垪琛�
+ * @param ccs 鎶勯�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+ * @param bccs 瀵嗛�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+ * @param isHtml 鏄惁涓篐TML鏍煎紡
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 4.6.3
+ */
+ public static String send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap,
+ boolean isHtml, File... files) {
+ return send(mailAccount, false, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
+ }
+
+ /**
+ * 鏍规嵁閰嶇疆鏂囦欢锛岃幏鍙栭偖浠跺鎴风浼氳瘽
+ *
+ * @param mailAccount 閭欢璐︽埛閰嶇疆
+ * @param isSingleton 鏄惁鍗曚緥锛堝叏灞�鍏变韩浼氳瘽锛�
+ * @return {@link Session}
+ * @since 5.5.7
+ */
+ public static Session getSession(MailAccount mailAccount, boolean isSingleton) {
+ Authenticator authenticator = null;
+ if (mailAccount.isAuth()) {
+ authenticator = new JakartaUserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
+ }
+
+ return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) //
+ : Session.getInstance(mailAccount.getSmtpProps(), authenticator);
+ }
+
+ // ------------------------------------------------------------------------------------------------------------------------ Private method start
+
+ /**
+ * 鍙戦�侀偖浠剁粰澶氫汉
+ *
+ * @param mailAccount 閭欢璐︽埛淇℃伅
+ * @param useGlobalSession 鏄惁鍏ㄥ眬鍏变韩Session
+ * @param tos 鏀朵欢浜哄垪琛�
+ * @param ccs 鎶勯�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+ * @param bccs 瀵嗛�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+ * @param subject 鏍囬
+ * @param content 姝f枃
+ * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:${cid}
+ * @param isHtml 鏄惁涓篐TML鏍煎紡
+ * @param files 闄勪欢鍒楄〃
+ * @return message-id
+ * @since 4.6.3
+ */
+ private static String send(MailAccount mailAccount, boolean useGlobalSession, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content,
+ Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+ final JakartaMail mail = JakartaMail.create(mailAccount).setUseGlobalSession(useGlobalSession);
+
+ // 鍙�夋妱閫佷汉
+ if (CollUtil.isNotEmpty(ccs)) {
+ mail.setCcs(ccs.toArray(new String[0]));
+ }
+ // 鍙�夊瘑閫佷汉
+ if (CollUtil.isNotEmpty(bccs)) {
+ mail.setBccs(bccs.toArray(new String[0]));
+ }
+
+ mail.setTos(tos.toArray(new String[0]));
+ mail.setTitle(subject);
+ mail.setContent(content);
+ mail.setHtml(isHtml);
+ mail.setFiles(files);
+
+ // 鍥剧墖
+ if (MapUtil.isNotEmpty(imageMap)) {
+ for (Entry<String, InputStream> entry : imageMap.entrySet()) {
+ mail.addImage(entry.getKey(), entry.getValue());
+ // 鍏抽棴娴�
+ IoUtil.close(entry.getValue());
+ }
+ }
+
+ return mail.send();
+ }
+
+ /**
+ * 灏嗗涓仈绯讳汉杞负鍒楄〃锛屽垎闅旂涓洪�楀彿鎴栬�呭垎鍙�
+ *
+ * @param addresses 澶氫釜鑱旂郴浜猴紝濡傛灉涓虹┖杩斿洖null
+ * @return 鑱旂郴浜哄垪琛�
+ */
+ private static List<String> splitAddress(String addresses) {
+ if (StrUtil.isBlank(addresses)) {
+ return null;
+ }
+
+ List<String> result;
+ if (StrUtil.contains(addresses, CharUtil.COMMA)) {
+ result = StrUtil.splitTrim(addresses, CharUtil.COMMA);
+ } else if (StrUtil.contains(addresses, ';')) {
+ result = StrUtil.splitTrim(addresses, ';');
+ } else {
+ result = CollUtil.newArrayList(addresses);
+ }
+ return result;
+ }
+ // ------------------------------------------------------------------------------------------------------------------------ Private method end
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..ef0cf11
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.mail.config.MailConfig
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/.flattened-pom.xml
new file mode 100644
index 0000000..c578bbd
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/.flattened-pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-mybatis 鏁版嵁搴撴湇鍔�</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-satoken</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-jsqlparser</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>p6spy</groupId>
+ <artifactId>p6spy</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/pom.xml
new file mode 100755
index 0000000..d79ba28
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-mybatis</artifactId>
+
+ <description>
+ ruoyi-common-mybatis 鏁版嵁搴撴湇鍔�
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-satoken</artifactId>
+ </dependency>
+
+ <!-- dynamic-datasource 澶氭暟鎹簮-->
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-jsqlparser</artifactId>
+ </dependency>
+
+ <!-- sql鎬ц兘鍒嗘瀽鎻掍欢 -->
+ <dependency>
+ <groupId>p6spy</groupId>
+ <artifactId>p6spy</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java
new file mode 100755
index 0000000..2879b9d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java
@@ -0,0 +1,40 @@
+package org.dromara.common.mybatis.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 鏁版嵁鏉冮檺娉ㄨВ锛岀敤浜庢爣璁版暟鎹潈闄愮殑鍗犱綅绗﹀叧閿瓧鍜屾浛鎹㈠��
+ * <p>
+ * 涓�涓敞瑙e彧鑳藉搴斾竴涓ā鏉�
+ * </p>
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataColumn {
+
+ /**
+ * 鏁版嵁鏉冮檺妯℃澘鐨勫崰浣嶇鍏抽敭瀛楋紝榛樿涓� "deptName"
+ *
+ * @return 鍗犱綅绗﹀叧閿瓧鏁扮粍
+ */
+ String[] key() default "deptName";
+
+ /**
+ * 鏁版嵁鏉冮檺妯℃澘鐨勫崰浣嶇鏇挎崲鍊硷紝榛樿涓� "dept_id"
+ *
+ * @return 鍗犱綅绗︽浛鎹㈠�兼暟缁�
+ */
+ String[] value() default "dept_id";
+
+ /**
+ * 鏉冮檺鏍囪瘑绗� 鐢ㄤ簬閫氳繃鑿滃崟鏉冮檺鏍囪瘑绗︽潵鑾峰彇鏁版嵁鏉冮檺
+ * 鎷ユ湁姝ゆ爣璇嗙鐨勮鑹� 灏嗕笉浼氭嫾鎺ユ瑙掕壊鐨勬暟鎹繃婊ql
+ *
+ * @return 鏉冮檺鏍囪瘑绗�
+ */
+ String permission() default "";
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java
new file mode 100755
index 0000000..f5f22d5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java
@@ -0,0 +1,30 @@
+package org.dromara.common.mybatis.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 鏁版嵁鏉冮檺缁勬敞瑙o紝鐢ㄤ簬鏍囪鏁版嵁鏉冮檺閰嶇疆鏁扮粍
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataPermission {
+
+ /**
+ * 鏁版嵁鏉冮檺閰嶇疆鏁扮粍锛岀敤浜庢寚瀹氭暟鎹潈闄愮殑鍗犱綅绗﹀叧閿瓧鍜屾浛鎹㈠��
+ *
+ * @return 鏁版嵁鏉冮檺閰嶇疆鏁扮粍
+ */
+ DataColumn[] value();
+
+ /**
+ * 鏉冮檺鎷兼帴鏍囪瘑绗�(鐢ㄤ簬鎸囧畾杩炴帴璇彞鐨剆ql绗﹀彿)
+ * 濡備笉濉� 榛樿 select 鐢� OR 鍏朵粬璇彞鐢� AND
+ * 鍐呭 OR 鎴栬�� AND
+ */
+ String joinStr() default "";
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java
new file mode 100755
index 0000000..54d5ad4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java
@@ -0,0 +1,54 @@
+package org.dromara.common.mybatis.aspect;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.dromara.common.mybatis.annotation.DataPermission;
+import org.dromara.common.mybatis.helper.DataPermissionHelper;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * 鏁版嵁鏉冮檺娉ㄨВAdvice
+ *
+ * @author 绉嬭緸鏈瘨
+ */
+@Slf4j
+public class DataPermissionAdvice implements MethodInterceptor {
+
+ @Override
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+ Object target = invocation.getThis();
+ Method method = invocation.getMethod();
+ Object[] args = invocation.getArguments();
+ // 璁剧疆鏉冮檺娉ㄨВ
+ DataPermissionHelper.setPermission(getDataPermissionAnnotation(target, method, args));
+ try {
+ // 鎵ц浠g悊鏂规硶
+ return invocation.proceed();
+ } finally {
+ // 娓呴櫎鏉冮檺娉ㄨВ
+ DataPermissionHelper.removePermission();
+ }
+ }
+
+ /**
+ * 鑾峰彇鏁版嵁鏉冮檺娉ㄨВ
+ */
+ private DataPermission getDataPermissionAnnotation(Object target, Method method,Object[] args){
+ DataPermission dataPermission = method.getAnnotation(DataPermission.class);
+ // 浼樺厛鑾峰彇鏂规硶涓婄殑娉ㄨВ
+ if (dataPermission != null) {
+ return dataPermission;
+ }
+ // 鏂规硶涓婃病鏈夋敞瑙o紝鍒欒幏鍙栫被涓婄殑娉ㄨВ
+ Class<?> targetClass = target.getClass();
+ // 濡傛灉鏄� JDK 鍔ㄦ�佷唬鐞嗭紝鍒欒幏鍙栫湡瀹炵殑Class瀹炰緥
+ if (Proxy.isProxyClass(targetClass)) {
+ targetClass = targetClass.getInterfaces()[0];
+ }
+ dataPermission = targetClass.getAnnotation(DataPermission.class);
+ return dataPermission;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java
new file mode 100755
index 0000000..4b7d945
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java
@@ -0,0 +1,39 @@
+package org.dromara.common.mybatis.aspect;
+
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.mybatis.annotation.DataPermission;
+import org.springframework.aop.support.StaticMethodMatcherPointcut;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * 鏁版嵁鏉冮檺鍖归厤鍒囩偣
+ *
+ * @author 绉嬭緸鏈瘨
+ */
+@Slf4j
+@SuppressWarnings("all")
+public class DataPermissionPointcut extends StaticMethodMatcherPointcut {
+
+ @Override
+ public boolean matches(Method method, Class<?> targetClass) {
+ // 浼樺厛鍖归厤鏂规硶
+ // 鏁版嵁鏉冮檺娉ㄨВ涓嶅缁ф壙鐢熸晥锛屾墍浠ユ鏌ュ綋鍓嶆柟娉曟槸鍚︽湁娉ㄨВ鍗冲彲锛屼笉鍐嶅線涓婂尮閰嶇埗绫绘垨鎺ュ彛
+ if (method.isAnnotationPresent(DataPermission.class)) {
+ return true;
+ }
+
+ // MyBatis 鐨� Mapper 灏辨槸閫氳繃 JDK 鍔ㄦ�佷唬鐞嗗疄鐜扮殑锛屾墍浠ヨ繖閲岄渶瑕佹鏌ユ槸鍚﹀尮閰� JDK 鐨勫姩鎬佷唬鐞�
+ Class<?> targetClassRef = targetClass;
+ if (Proxy.isProxyClass(targetClassRef)) {
+ // 鏁版嵁鏉冮檺娉ㄨВ涓嶅缁ф壙鐢熸晥锛屼絾鐢变簬 SpringIOC 瀹瑰櫒鎷垮埌鐨勫疄闄呬笂鏄� MyBatis 浠g悊杩囧悗鐨� Mapper锛岃�� targetClass.isAnnotationPresent 瀹為檯鍖归厤鐨勬槸 Proxy 绫荤殑娉ㄨВ锛屼笉浼氭煡鎵句唬鐞嗙被銆�
+ // 鎵�浠ヨ繖閲屼笉鑳界敤 targetClass.isAnnotationPresent锛屽彧鑳界敤 AnnotatedElementUtils.hasAnnotation 鎴� targetClass.getInterfaces()[0].isAnnotationPresent 鍘诲仛鍖归厤锛屼互妫�鏌ヨ浠g悊鐨� MapperClass 鏄惁鍏锋湁娉ㄨВ
+ // 鍘熺悊锛欽DK 鍔ㄦ�佷唬鐞嗘湰璐ㄤ笂灏辨槸瀵规帴鍙h繘琛屽疄鐜扮劧鍚庡鍏蜂綋鐨勬帴鍙e疄鐜板仛浠g悊锛屾墍浠ョ洿鎺ラ�氳繃鎺ュ彛鍙互鎷垮埌瀹為檯鐨� MapperClass
+ targetClassRef = targetClass.getInterfaces()[0];
+
+ }
+ return targetClassRef.isAnnotationPresent(DataPermission.class);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcutAdvisor.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcutAdvisor.java
new file mode 100755
index 0000000..351288c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcutAdvisor.java
@@ -0,0 +1,33 @@
+package org.dromara.common.mybatis.aspect;
+
+import org.aopalliance.aop.Advice;
+import org.springframework.aop.Pointcut;
+import org.springframework.aop.support.AbstractPointcutAdvisor;
+
+/**
+ * 鏁版嵁鏉冮檺娉ㄨВ鍒囬潰瀹氫箟
+ *
+ * @author 绉嬭緸鏈瘨
+ */
+@SuppressWarnings("all")
+public class DataPermissionPointcutAdvisor extends AbstractPointcutAdvisor {
+
+ private final Advice advice;
+ private final Pointcut pointcut;
+
+ public DataPermissionPointcutAdvisor() {
+ this.advice = new DataPermissionAdvice();
+ this.pointcut = new DataPermissionPointcut();
+ }
+
+ @Override
+ public Pointcut getPointcut() {
+ return this.pointcut;
+ }
+
+ @Override
+ public Advice getAdvice() {
+ return this.advice;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java
new file mode 100755
index 0000000..b088257
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java
@@ -0,0 +1,142 @@
+package org.dromara.common.mybatis.config;
+
+import cn.hutool.core.net.NetUtil;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
+import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
+import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
+import org.dromara.common.core.factory.YmlPropertySourceFactory;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.mybatis.aspect.DataPermissionPointcutAdvisor;
+import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
+import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
+import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler;
+import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.context.annotation.Role;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+/**
+ * mybatis-plus閰嶇疆绫�(涓嬫柟娉ㄩ噴鏈夋彃浠朵粙缁�)
+ *
+ * @author Lion Li
+ */
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+@EnableTransactionManagement(proxyTargetClass = true)
+@MapperScan("${mybatis-plus.mapperPackage}")
+@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
+public class MybatisPlusConfig {
+
+ @Bean
+ public MybatisPlusInterceptor mybatisPlusInterceptor() {
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ // 澶氱鎴锋彃浠� 蹇呴』鏀惧埌绗竴浣�
+ try {
+ TenantLineInnerInterceptor tenant = SpringUtils.getBean(TenantLineInnerInterceptor.class);
+ interceptor.addInnerInterceptor(tenant);
+ } catch (BeansException ignore) {
+ }
+ // 鏁版嵁鏉冮檺澶勭悊
+ interceptor.addInnerInterceptor(dataPermissionInterceptor());
+ // 鍒嗛〉鎻掍欢
+ interceptor.addInnerInterceptor(paginationInnerInterceptor());
+ // 涔愯閿佹彃浠�
+ interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
+ return interceptor;
+ }
+
+ /**
+ * 鏁版嵁鏉冮檺鎷︽埅鍣�
+ */
+ public PlusDataPermissionInterceptor dataPermissionInterceptor() {
+ return new PlusDataPermissionInterceptor();
+ }
+
+ /**
+ * 鏁版嵁鏉冮檺鍒囬潰澶勭悊鍣�
+ */
+ @Bean
+ @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ public DataPermissionPointcutAdvisor dataPermissionPointcutAdvisor() {
+ return new DataPermissionPointcutAdvisor();
+ }
+
+ /**
+ * 鍒嗛〉鎻掍欢锛岃嚜鍔ㄨ瘑鍒暟鎹簱绫诲瀷
+ */
+ public PaginationInnerInterceptor paginationInnerInterceptor() {
+ PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
+ // 鍒嗛〉鍚堢悊鍖�
+ paginationInnerInterceptor.setOverflow(true);
+ return paginationInnerInterceptor;
+ }
+
+ /**
+ * 涔愯閿佹彃浠�
+ */
+ public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
+ return new OptimisticLockerInnerInterceptor();
+ }
+
+ /**
+ * 鍏冨璞″瓧娈靛~鍏呮帶鍒跺櫒
+ */
+ @Bean
+ public MetaObjectHandler metaObjectHandler() {
+ return new InjectionMetaObjectHandler();
+ }
+
+ /**
+ * 浣跨敤缃戝崱淇℃伅缁戝畾闆姳鐢熸垚鍣�
+ * 闃叉闆嗙兢闆姳ID閲嶅
+ */
+ @Bean
+ public IdentifierGenerator idGenerator() {
+ return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
+ }
+
+ /**
+ * 寮傚父澶勭悊鍣�
+ */
+ @Bean
+ public MybatisExceptionHandler mybatisExceptionHandler() {
+ return new MybatisExceptionHandler();
+ }
+
+ /**
+ * 鍒濆鍖栬〃瀵硅薄澶勭悊鍣�
+ */
+ @Bean
+ public PostInitTableInfoHandler postInitTableInfoHandler() {
+ return new PlusPostInitTableInfoHandler();
+ }
+
+ /**
+ * PaginationInnerInterceptor 鍒嗛〉鎻掍欢锛岃嚜鍔ㄨ瘑鍒暟鎹簱绫诲瀷
+ * https://baomidou.com/pages/97710a/
+ * OptimisticLockerInnerInterceptor 涔愯閿佹彃浠�
+ * https://baomidou.com/pages/0d93c0/
+ * MetaObjectHandler 鍏冨璞″瓧娈靛~鍏呮帶鍒跺櫒
+ * https://baomidou.com/pages/4c6bcf/
+ * ISqlInjector sql娉ㄥ叆鍣�
+ * https://baomidou.com/pages/42ea4a/
+ * BlockAttackInnerInterceptor 濡傛灉鏄鍏ㄨ〃鐨勫垹闄ゆ垨鏇存柊鎿嶄綔锛屽氨浼氱粓姝㈣鎿嶄綔
+ * https://baomidou.com/pages/f9a237/
+ * IllegalSQLInnerInterceptor sql鎬ц兘瑙勮寖鎻掍欢(鍨冨溇SQL鎷︽埅)
+ * IdentifierGenerator 鑷畾涔変富閿瓥鐣�
+ * https://baomidou.com/pages/568eb2/
+ * TenantLineInnerInterceptor 澶氱鎴锋彃浠�
+ * https://baomidou.com/pages/aef2f2/
+ * DynamicTableNameInnerInterceptor 鍔ㄦ�佽〃鍚嶆彃浠�
+ * https://baomidou.com/pages/2a45ff/
+ */
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java
new file mode 100755
index 0000000..13a7941
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java
@@ -0,0 +1,70 @@
+package org.dromara.common.mybatis.core.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Entity鍩虹被
+ *
+ * @author Lion Li
+ */
+@Data
+public class BaseEntity implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鎼滅储鍊�
+ */
+ @JsonIgnore
+ @TableField(exist = false)
+ private String searchValue;
+
+ /**
+ * 鍒涘缓閮ㄩ棬
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Long createDept;
+
+ /**
+ * 鍒涘缓鑰�
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Long createBy;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Date createTime;
+
+ /**
+ * 鏇存柊鑰�
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Long updateBy;
+
+ /**
+ * 鏇存柊鏃堕棿
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Date updateTime;
+
+ /**
+ * 璇锋眰鍙傛暟
+ */
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ @TableField(exist = false)
+ private Map<String, Object> params = new HashMap<>();
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java
new file mode 100755
index 0000000..24557ed
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java
@@ -0,0 +1,334 @@
+package org.dromara.common.mybatis.core.mapper;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.toolkit.Db;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StreamUtils;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * 鑷畾涔� Mapper 鎺ュ彛, 瀹炵幇 鑷畾涔夋墿灞�
+ *
+ * @param <T> table 娉涘瀷
+ * @param <V> vo 娉涘瀷
+ * @author Lion Li
+ * @since 2021-05-13
+ */
+@SuppressWarnings("unchecked")
+public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
+
+ Log log = LogFactory.getLog(BaseMapperPlus.class);
+
+ /**
+ * 鑾峰彇褰撳墠瀹炰緥瀵硅薄鍏宠仈鐨勬硾鍨嬬被鍨� V 鐨� Class 瀵硅薄
+ *
+ * @return 杩斿洖褰撳墠瀹炰緥瀵硅薄鍏宠仈鐨勬硾鍨嬬被鍨� V 鐨� Class 瀵硅薄
+ */
+ default Class<V> currentVoClass() {
+ return (Class<V>) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[1];
+ }
+
+ /**
+ * 鑾峰彇褰撳墠瀹炰緥瀵硅薄鍏宠仈鐨勬硾鍨嬬被鍨� T 鐨� Class 瀵硅薄
+ *
+ * @return 杩斿洖褰撳墠瀹炰緥瀵硅薄鍏宠仈鐨勬硾鍨嬬被鍨� T 鐨� Class 瀵硅薄
+ */
+ default Class<T> currentModelClass() {
+ return (Class<T>) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[0];
+ }
+
+ /**
+ * 浣跨敤榛樿鐨勬煡璇㈡潯浠舵煡璇㈠苟杩斿洖缁撴灉鍒楄〃
+ *
+ * @return 杩斿洖鏌ヨ缁撴灉鐨勫垪琛�
+ */
+ default List<T> selectList() {
+ return this.selectList(new QueryWrapper<>());
+ }
+
+ /**
+ * 鎵归噺鎻掑叆瀹炰綋瀵硅薄闆嗗悎
+ *
+ * @param entityList 瀹炰綋瀵硅薄闆嗗悎
+ * @return 鎻掑叆鎿嶄綔鏄惁鎴愬姛鐨勫竷灏斿��
+ */
+ default boolean insertBatch(Collection<T> entityList) {
+ return Db.saveBatch(entityList);
+ }
+
+ /**
+ * 鎵归噺鏍规嵁ID鏇存柊瀹炰綋瀵硅薄闆嗗悎
+ *
+ * @param entityList 瀹炰綋瀵硅薄闆嗗悎
+ * @return 鏇存柊鎿嶄綔鏄惁鎴愬姛鐨勫竷灏斿��
+ */
+ default boolean updateBatchById(Collection<T> entityList) {
+ return Db.updateBatchById(entityList);
+ }
+
+ /**
+ * 鎵归噺鎻掑叆鎴栨洿鏂板疄浣撳璞¢泦鍚�
+ *
+ * @param entityList 瀹炰綋瀵硅薄闆嗗悎
+ * @return 鎻掑叆鎴栨洿鏂版搷浣滄槸鍚︽垚鍔熺殑甯冨皵鍊�
+ */
+ default boolean insertOrUpdateBatch(Collection<T> entityList) {
+ return Db.saveOrUpdateBatch(entityList);
+ }
+
+ /**
+ * 鎵归噺鎻掑叆瀹炰綋瀵硅薄闆嗗悎骞舵寚瀹氭壒澶勭悊澶у皬
+ *
+ * @param entityList 瀹炰綋瀵硅薄闆嗗悎
+ * @param batchSize 鎵瑰鐞嗗ぇ灏�
+ * @return 鎻掑叆鎿嶄綔鏄惁鎴愬姛鐨勫竷灏斿��
+ */
+ default boolean insertBatch(Collection<T> entityList, int batchSize) {
+ return Db.saveBatch(entityList, batchSize);
+ }
+
+ /**
+ * 鎵归噺鏍规嵁ID鏇存柊瀹炰綋瀵硅薄闆嗗悎骞舵寚瀹氭壒澶勭悊澶у皬
+ *
+ * @param entityList 瀹炰綋瀵硅薄闆嗗悎
+ * @param batchSize 鎵瑰鐞嗗ぇ灏�
+ * @return 鏇存柊鎿嶄綔鏄惁鎴愬姛鐨勫竷灏斿��
+ */
+ default boolean updateBatchById(Collection<T> entityList, int batchSize) {
+ return Db.updateBatchById(entityList, batchSize);
+ }
+
+ /**
+ * 鎵归噺鎻掑叆鎴栨洿鏂板疄浣撳璞¢泦鍚堝苟鎸囧畾鎵瑰鐞嗗ぇ灏�
+ *
+ * @param entityList 瀹炰綋瀵硅薄闆嗗悎
+ * @param batchSize 鎵瑰鐞嗗ぇ灏�
+ * @return 鎻掑叆鎴栨洿鏂版搷浣滄槸鍚︽垚鍔熺殑甯冨皵鍊�
+ */
+ default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
+ return Db.saveOrUpdateBatch(entityList, batchSize);
+ }
+
+ /**
+ * 鏍规嵁ID鏌ヨ鍗曚釜VO瀵硅薄
+ *
+ * @param id 涓婚敭ID
+ * @return 鏌ヨ鍒扮殑鍗曚釜VO瀵硅薄
+ */
+ default V selectVoById(Serializable id) {
+ return selectVoById(id, this.currentVoClass());
+ }
+
+ /**
+ * 鏍规嵁ID鏌ヨ鍗曚釜VO瀵硅薄骞跺皢鍏惰浆鎹负鎸囧畾鐨刅O绫�
+ *
+ * @param id 涓婚敭ID
+ * @param voClass 瑕佽浆鎹㈢殑VO绫荤殑Class瀵硅薄
+ * @param <C> VO绫荤殑绫诲瀷
+ * @return 鏌ヨ鍒扮殑鍗曚釜VO瀵硅薄锛岀粡杩囪浆鎹负鎸囧畾鐨刅O绫诲悗杩斿洖
+ */
+ default <C> C selectVoById(Serializable id, Class<C> voClass) {
+ T obj = this.selectById(id);
+ if (ObjectUtil.isNull(obj)) {
+ return null;
+ }
+ return MapstructUtils.convert(obj, voClass);
+ }
+
+ /**
+ * 鏍规嵁ID闆嗗悎鎵归噺鏌ヨVO瀵硅薄鍒楄〃
+ *
+ * @param idList 涓婚敭ID闆嗗悎
+ * @return 鏌ヨ鍒扮殑VO瀵硅薄鍒楄〃
+ */
+ default List<V> selectVoByIds(Collection<? extends Serializable> idList) {
+ return selectVoByIds(idList, this.currentVoClass());
+ }
+
+ /**
+ * 鏍规嵁ID闆嗗悎鎵归噺鏌ヨ瀹炰綋瀵硅薄鍒楄〃锛屽苟灏嗗叾杞崲涓烘寚瀹氱殑VO瀵硅薄鍒楄〃
+ *
+ * @param idList 涓婚敭ID闆嗗悎
+ * @param voClass 瑕佽浆鎹㈢殑VO绫荤殑Class瀵硅薄
+ * @param <C> VO绫荤殑绫诲瀷
+ * @return 鏌ヨ鍒扮殑VO瀵硅薄鍒楄〃锛岀粡杩囪浆鎹负鎸囧畾鐨刅O绫诲悗杩斿洖
+ */
+ default <C> List<C> selectVoByIds(Collection<? extends Serializable> idList, Class<C> voClass) {
+ List<T> list = this.selectByIds(idList);
+ if (CollUtil.isEmpty(list)) {
+ return CollUtil.newArrayList();
+ }
+ return MapstructUtils.convert(list, voClass);
+ }
+
+ /**
+ * 鏍规嵁鏌ヨ鏉′欢Map鏌ヨVO瀵硅薄鍒楄〃
+ *
+ * @param map 鏌ヨ鏉′欢Map
+ * @return 鏌ヨ鍒扮殑VO瀵硅薄鍒楄〃
+ */
+ default List<V> selectVoByMap(Map<String, Object> map) {
+ return selectVoByMap(map, this.currentVoClass());
+ }
+
+ /**
+ * 鏍规嵁鏌ヨ鏉′欢Map鏌ヨ瀹炰綋瀵硅薄鍒楄〃锛屽苟灏嗗叾杞崲涓烘寚瀹氱殑VO瀵硅薄鍒楄〃
+ *
+ * @param map 鏌ヨ鏉′欢Map
+ * @param voClass 瑕佽浆鎹㈢殑VO绫荤殑Class瀵硅薄
+ * @param <C> VO绫荤殑绫诲瀷
+ * @return 鏌ヨ鍒扮殑VO瀵硅薄鍒楄〃锛岀粡杩囪浆鎹负鎸囧畾鐨刅O绫诲悗杩斿洖
+ */
+ default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
+ List<T> list = this.selectByMap(map);
+ if (CollUtil.isEmpty(list)) {
+ return CollUtil.newArrayList();
+ }
+ return MapstructUtils.convert(list, voClass);
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鏌ヨ鍗曚釜VO瀵硅薄
+ *
+ * @param wrapper 鏌ヨ鏉′欢Wrapper
+ * @return 鏌ヨ鍒扮殑鍗曚釜VO瀵硅薄
+ */
+ default V selectVoOne(Wrapper<T> wrapper) {
+ return selectVoOne(wrapper, this.currentVoClass());
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鏌ヨ鍗曚釜VO瀵硅薄锛屽苟鏍规嵁闇�瑕佸喅瀹氭槸鍚︽姏鍑哄紓甯�
+ *
+ * @param wrapper 鏌ヨ鏉′欢Wrapper
+ * @param throwEx 鏄惁鎶涘嚭寮傚父鐨勬爣蹇�
+ * @return 鏌ヨ鍒扮殑鍗曚釜VO瀵硅薄
+ */
+ default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) {
+ return selectVoOne(wrapper, this.currentVoClass(), throwEx);
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鏌ヨ鍗曚釜VO瀵硅薄锛屽苟鎸囧畾杩斿洖鐨刅O瀵硅薄鐨勭被鍨�
+ *
+ * @param wrapper 鏌ヨ鏉′欢Wrapper
+ * @param voClass 杩斿洖鐨刅O瀵硅薄鐨凜lass瀵硅薄
+ * @param <C> 杩斿洖鐨刅O瀵硅薄鐨勭被鍨�
+ * @return 鏌ヨ鍒扮殑鍗曚釜VO瀵硅薄锛岀粡杩囩被鍨嬭浆鎹负鎸囧畾鐨刅O绫诲悗杩斿洖
+ */
+ default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
+ return selectVoOne(wrapper, voClass, true);
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鏌ヨ鍗曚釜瀹炰綋瀵硅薄锛屽苟灏嗗叾杞崲涓烘寚瀹氱殑VO瀵硅薄
+ *
+ * @param wrapper 鏌ヨ鏉′欢Wrapper
+ * @param voClass 瑕佽浆鎹㈢殑VO绫荤殑Class瀵硅薄
+ * @param throwEx 鏄惁鎶涘嚭寮傚父鐨勬爣蹇�
+ * @param <C> VO绫荤殑绫诲瀷
+ * @return 鏌ヨ鍒扮殑鍗曚釜VO瀵硅薄锛岀粡杩囪浆鎹负鎸囧畾鐨刅O绫诲悗杩斿洖
+ */
+ default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass, boolean throwEx) {
+ T obj = this.selectOne(wrapper, throwEx);
+ if (ObjectUtil.isNull(obj)) {
+ return null;
+ }
+ return MapstructUtils.convert(obj, voClass);
+ }
+
+ /**
+ * 鏌ヨ鎵�鏈塚O瀵硅薄鍒楄〃
+ *
+ * @return 鏌ヨ鍒扮殑VO瀵硅薄鍒楄〃
+ */
+ default List<V> selectVoList() {
+ return selectVoList(new QueryWrapper<>(), this.currentVoClass());
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鏌ヨVO瀵硅薄鍒楄〃
+ *
+ * @param wrapper 鏌ヨ鏉′欢Wrapper
+ * @return 鏌ヨ鍒扮殑VO瀵硅薄鍒楄〃
+ */
+ default List<V> selectVoList(Wrapper<T> wrapper) {
+ return selectVoList(wrapper, this.currentVoClass());
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鏌ヨ瀹炰綋瀵硅薄鍒楄〃锛屽苟灏嗗叾杞崲涓烘寚瀹氱殑VO瀵硅薄鍒楄〃
+ *
+ * @param wrapper 鏌ヨ鏉′欢Wrapper
+ * @param voClass 瑕佽浆鎹㈢殑VO绫荤殑Class瀵硅薄
+ * @param <C> VO绫荤殑绫诲瀷
+ * @return 鏌ヨ鍒扮殑VO瀵硅薄鍒楄〃锛岀粡杩囪浆鎹负鎸囧畾鐨刅O绫诲悗杩斿洖
+ */
+ default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
+ List<T> list = this.selectList(wrapper);
+ if (CollUtil.isEmpty(list)) {
+ return CollUtil.newArrayList();
+ }
+ return MapstructUtils.convert(list, voClass);
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨVO瀵硅薄鍒楄〃
+ *
+ * @param page 鍒嗛〉淇℃伅
+ * @param wrapper 鏌ヨ鏉′欢Wrapper
+ * @return 鏌ヨ鍒扮殑VO瀵硅薄鍒嗛〉鍒楄〃
+ */
+ default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
+ return selectVoPage(page, wrapper, this.currentVoClass());
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ瀹炰綋瀵硅薄鍒楄〃锛屽苟灏嗗叾杞崲涓烘寚瀹氱殑VO瀵硅薄鍒嗛〉鍒楄〃
+ *
+ * @param page 鍒嗛〉淇℃伅
+ * @param wrapper 鏌ヨ鏉′欢Wrapper
+ * @param voClass 瑕佽浆鎹㈢殑VO绫荤殑Class瀵硅薄
+ * @param <C> VO绫荤殑绫诲瀷
+ * @param <P> VO瀵硅薄鍒嗛〉鍒楄〃鐨勭被鍨�
+ * @return 鏌ヨ鍒扮殑VO瀵硅薄鍒嗛〉鍒楄〃锛岀粡杩囪浆鎹负鎸囧畾鐨刅O绫诲悗杩斿洖
+ */
+ default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
+ // 鏍规嵁鏉′欢鍒嗛〉鏌ヨ瀹炰綋瀵硅薄鍒楄〃
+ List<T> list = this.selectList(page, wrapper);
+ // 鍒涘缓涓�涓柊鐨刅O瀵硅薄鍒嗛〉鍒楄〃锛屽苟璁剧疆鍒嗛〉淇℃伅
+ IPage<C> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
+ if (CollUtil.isEmpty(list)) {
+ return (P) voPage;
+ }
+ voPage.setRecords(MapstructUtils.convert(list, voClass));
+ return (P) voPage;
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鏌ヨ绗﹀悎鏉′欢鐨勫璞★紝骞跺皢鍏惰浆鎹负鎸囧畾绫诲瀷鐨勫璞″垪琛�
+ *
+ * @param wrapper 鏌ヨ鏉′欢Wrapper
+ * @param mapper 杞崲鍑芥暟锛岀敤浜庡皢鏌ヨ鍒扮殑瀵硅薄杞崲涓烘寚瀹氱被鍨嬬殑瀵硅薄
+ * @param <C> 瑕佽浆鎹㈢殑瀵硅薄鐨勭被鍨�
+ * @return 鏌ヨ鍒扮殑绗﹀悎鏉′欢鐨勫璞″垪琛紝缁忚繃杞崲涓烘寚瀹氱被鍨嬬殑瀵硅薄鍚庤繑鍥�
+ */
+ default <C> List<C> selectObjs(Wrapper<T> wrapper, Function<? super Object, C> mapper) {
+ return StreamUtils.toList(this.selectObjs(wrapper), mapper);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java
new file mode 100755
index 0000000..1d5c3c9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java
@@ -0,0 +1,127 @@
+package org.dromara.common.mybatis.core.page;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Data;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.sql.SqlUtil;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鍒嗛〉鏌ヨ瀹炰綋绫�
+ *
+ * @author Lion Li
+ */
+@Data
+public class PageQuery implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鍒嗛〉澶у皬
+ */
+ private Integer pageSize;
+
+ /**
+ * 褰撳墠椤垫暟
+ */
+ private Integer pageNum;
+
+ /**
+ * 鎺掑簭鍒�
+ */
+ private String orderByColumn;
+
+ /**
+ * 鎺掑簭鐨勬柟鍚慸esc鎴栬�卆sc
+ */
+ private String isAsc;
+
+ /**
+ * 褰撳墠璁板綍璧峰绱㈠紩 榛樿鍊�
+ */
+ public static final int DEFAULT_PAGE_NUM = 1;
+
+ /**
+ * 姣忛〉鏄剧ず璁板綍鏁� 榛樿鍊� 榛樿鏌ュ叏閮�
+ */
+ public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
+
+ /**
+ * 鏋勫缓鍒嗛〉瀵硅薄
+ */
+ public <T> Page<T> build() {
+ Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
+ Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
+ if (pageNum <= 0) {
+ pageNum = DEFAULT_PAGE_NUM;
+ }
+ Page<T> page = new Page<>(pageNum, pageSize);
+ List<OrderItem> orderItems = buildOrderItem();
+ if (CollUtil.isNotEmpty(orderItems)) {
+ page.addOrder(orderItems);
+ }
+ return page;
+ }
+
+ /**
+ * 鏋勫缓鎺掑簭
+ *
+ * 鏀寔鐨勭敤娉曞涓�:
+ * {isAsc:"asc",orderByColumn:"id"} order by id asc
+ * {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc
+ * {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc
+ * {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc
+ */
+ private List<OrderItem> buildOrderItem() {
+ if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) {
+ return null;
+ }
+ String orderBy = SqlUtil.escapeOrderBySql(orderByColumn);
+ orderBy = StringUtils.toUnderScoreCase(orderBy);
+
+ // 鍏煎鍓嶇鎺掑簭绫诲瀷
+ isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
+
+ String[] orderByArr = orderBy.split(StringUtils.SEPARATOR);
+ String[] isAscArr = isAsc.split(StringUtils.SEPARATOR);
+ if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
+ throw new ServiceException("鎺掑簭鍙傛暟鏈夎");
+ }
+
+ List<OrderItem> list = new ArrayList<>();
+ // 姣忎釜瀛楁鍚勮嚜鎺掑簭
+ for (int i = 0; i < orderByArr.length; i++) {
+ String orderByStr = orderByArr[i];
+ String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i];
+ if ("asc".equals(isAscStr)) {
+ list.add(OrderItem.asc(orderByStr));
+ } else if ("desc".equals(isAscStr)) {
+ list.add(OrderItem.desc(orderByStr));
+ } else {
+ throw new ServiceException("鎺掑簭鍙傛暟鏈夎");
+ }
+ }
+ return list;
+ }
+
+ @JsonIgnore
+ public Integer getFirstNum() {
+ return (pageNum - 1) * pageSize;
+ }
+
+ public PageQuery(Integer pageSize, Integer pageNum) {
+ this.pageSize = pageSize;
+ this.pageNum = pageNum;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java
new file mode 100755
index 0000000..1fe2b3e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java
@@ -0,0 +1,107 @@
+package org.dromara.common.mybatis.core.page;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.http.HttpStatus;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 琛ㄦ牸鍒嗛〉鏁版嵁瀵硅薄
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class TableDataInfo<T> implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鎬昏褰曟暟
+ */
+ private long total;
+
+ /**
+ * 鍒楄〃鏁版嵁
+ */
+ private List<T> rows;
+
+ /**
+ * 娑堟伅鐘舵�佺爜
+ */
+ private int code;
+
+ /**
+ * 娑堟伅鍐呭
+ */
+ private String msg;
+
+ /**
+ * 鍒嗛〉
+ *
+ * @param list 鍒楄〃鏁版嵁
+ * @param total 鎬昏褰曟暟
+ */
+ public TableDataInfo(List<T> list, long total) {
+ this.rows = list;
+ this.total = total;
+ this.code = HttpStatus.HTTP_OK;
+ this.msg = "鏌ヨ鎴愬姛";
+ }
+
+ /**
+ * 鏍规嵁鍒嗛〉瀵硅薄鏋勫缓琛ㄦ牸鍒嗛〉鏁版嵁瀵硅薄
+ */
+ public static <T> TableDataInfo<T> build(IPage<T> page) {
+ TableDataInfo<T> rspData = new TableDataInfo<>();
+ rspData.setCode(HttpStatus.HTTP_OK);
+ rspData.setMsg("鏌ヨ鎴愬姛");
+ rspData.setRows(page.getRecords());
+ rspData.setTotal(page.getTotal());
+ return rspData;
+ }
+
+ /**
+ * 鏍规嵁鏁版嵁鍒楄〃鏋勫缓琛ㄦ牸鍒嗛〉鏁版嵁瀵硅薄
+ */
+ public static <T> TableDataInfo<T> build(List<T> list) {
+ TableDataInfo<T> rspData = new TableDataInfo<>();
+ rspData.setCode(HttpStatus.HTTP_OK);
+ rspData.setMsg("鏌ヨ鎴愬姛");
+ rspData.setRows(list);
+ rspData.setTotal(list.size());
+ return rspData;
+ }
+
+ /**
+ * 鏋勫缓琛ㄦ牸鍒嗛〉鏁版嵁瀵硅薄
+ */
+ public static <T> TableDataInfo<T> build() {
+ TableDataInfo<T> rspData = new TableDataInfo<>();
+ rspData.setCode(HttpStatus.HTTP_OK);
+ rspData.setMsg("鏌ヨ鎴愬姛");
+ return rspData;
+ }
+
+ /**
+ * 鏍规嵁鍘熷鏁版嵁鍒楄〃鍜屽垎椤靛弬鏁帮紝鏋勫缓琛ㄦ牸鍒嗛〉鏁版嵁瀵硅薄锛堢敤浜庡亣鍒嗛〉锛�
+ *
+ * @param list 鍘熷鏁版嵁鍒楄〃锛堝叏閮ㄦ暟鎹級
+ * @param page 鍒嗛〉鍙傛暟瀵硅薄锛堝寘鍚綋鍓嶉〉鐮併�佹瘡椤靛ぇ灏忕瓑锛�
+ * @return 鏋勯�犲ソ鐨勫垎椤电粨鏋� TableDataInfo<T>
+ */
+ public static <T> TableDataInfo<T> build(List<T> list, IPage<T> page) {
+ if (CollUtil.isEmpty(list)) {
+ return TableDataInfo.build();
+ }
+ List<T> pageList = CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), list);
+ return new TableDataInfo<>(pageList, list.size());
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java
new file mode 100755
index 0000000..2d5244b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java
@@ -0,0 +1,87 @@
+package org.dromara.common.mybatis.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.dromara.common.core.utils.StringUtils;
+
+/**
+ * 鏁版嵁搴撶被鍨�
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum DataBaseType {
+
+ /**
+ * MySQL
+ */
+ MY_SQL("MySQL"),
+
+ /**
+ * Oracle
+ */
+ ORACLE("Oracle"),
+
+ /**
+ * PostgreSQL
+ */
+ POSTGRE_SQL("PostgreSQL"),
+
+ /**
+ * SQL Server
+ */
+ SQL_SERVER("Microsoft SQL Server");
+
+ /**
+ * 鏁版嵁搴撶被鍨�
+ */
+ private final String type;
+
+ /**
+ * 鏍规嵁鏁版嵁搴撲骇鍝佸悕绉版煡鎵惧搴旂殑鏁版嵁搴撶被鍨�
+ *
+ * @param databaseProductName 鏁版嵁搴撲骇鍝佸悕绉�
+ * @return 瀵瑰簲鐨勬暟鎹簱绫诲瀷鏋氫妇鍊�
+ */
+ public static DataBaseType find(String databaseProductName) {
+ if (StringUtils.isBlank(databaseProductName)) {
+ return MY_SQL;
+ }
+ for (DataBaseType type : values()) {
+ if (type.getType().equals(databaseProductName)) {
+ return type;
+ }
+ }
+ return MY_SQL;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓� MySQL 绫诲瀷
+ */
+ public boolean isMySql() {
+ return this == MY_SQL;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓� Oracle 绫诲瀷
+ */
+ public boolean isOracle() {
+ return this == ORACLE;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓� PostgreSQL 绫诲瀷
+ */
+ public boolean isPostgreSql() {
+ return this == POSTGRE_SQL;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓� SQL Server 绫诲瀷
+ */
+ public boolean isSqlServer() {
+ return this == SQL_SERVER;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java
new file mode 100755
index 0000000..02a5f48
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java
@@ -0,0 +1,87 @@
+package org.dromara.common.mybatis.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.helper.DataPermissionHelper;
+
+/**
+ * 鏁版嵁鏉冮檺绫诲瀷鏋氫妇
+ * <p>
+ * 鏀寔浣跨敤 SpEL 妯℃澘琛ㄨ揪寮忓畾涔� SQL 鏌ヨ鏉′欢
+ * 鍐呯疆鏁版嵁锛�
+ * - {@code user}: 褰撳墠鐧诲綍鐢ㄦ埛淇℃伅锛屽弬鑰� {@link LoginUser}
+ * 鍐呯疆鏈嶅姟锛�
+ * - {@code sdss}: 绯荤粺鏁版嵁鏉冮檺鏈嶅姟锛屽弬鑰� ISysDataScopeService
+ * 濡傞渶鎵╁睍鏁版嵁锛屽彲浠ラ�氳繃 {@link DataPermissionHelper} 杩涜鎿嶄綔
+ * 濡傞渶鎵╁睍鏈嶅姟锛屽彲浠ラ�氳繃 ISysDataScopeService 鑷缂栧啓
+ * </p>
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Getter
+@AllArgsConstructor
+public enum DataScopeType {
+
+ /**
+ * 鍏ㄩ儴鏁版嵁鏉冮檺
+ */
+ ALL("1", "", ""),
+
+ /**
+ * 鑷畾鏁版嵁鏉冮檺
+ */
+ CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "),
+
+ /**
+ * 閮ㄩ棬鏁版嵁鏉冮檺
+ */
+ DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "),
+
+ /**
+ * 閮ㄩ棬鍙婁互涓嬫暟鎹潈闄�
+ */
+ DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "),
+
+ /**
+ * 浠呮湰浜烘暟鎹潈闄�
+ */
+ SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "),
+
+ /**
+ * 閮ㄩ棬鍙婁互涓嬫垨鏈汉鏁版嵁鏉冮檺
+ */
+ DEPT_AND_CHILD_OR_SELF("6", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} ) OR #{#userName} = #{#user.userId} ", " 1 = 0 ");
+
+ private final String code;
+
+ /**
+ * SpEL 妯℃澘琛ㄨ揪寮忥紝鐢ㄤ簬鏋勫缓 SQL 鏌ヨ鏉′欢
+ */
+ private final String sqlTemplate;
+
+ /**
+ * 濡傛灉涓嶆弧瓒� {@code sqlTemplate} 鐨勬潯浠讹紝鍒欎娇鐢ㄦ榛樿 SQL 琛ㄨ揪寮�
+ */
+ private final String elseSql;
+
+ /**
+ * 鏍规嵁鏋氫妇浠g爜鏌ユ壘瀵瑰簲鐨勬灇涓惧��
+ *
+ * @param code 鏋氫妇浠g爜
+ * @return 瀵瑰簲鐨勬灇涓惧�硷紝濡傛灉鏈壘鍒板垯杩斿洖 null
+ */
+ public static DataScopeType findCode(String code) {
+ if (StringUtils.isBlank(code)) {
+ return null;
+ }
+ for (DataScopeType type : values()) {
+ if (type.getCode().equals(code)) {
+ return type;
+ }
+ }
+ return null;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java
new file mode 100755
index 0000000..38f7247
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java
@@ -0,0 +1,113 @@
+package org.dromara.common.mybatis.handler;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpStatus;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.reflection.MetaObject;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.ObjectUtils;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.satoken.utils.LoginHelper;
+
+import java.util.Date;
+
+/**
+ * MP娉ㄥ叆澶勭悊鍣�
+ *
+ * @author Lion Li
+ * @date 2021/4/25
+ */
+@Slf4j
+public class InjectionMetaObjectHandler implements MetaObjectHandler {
+
+ /**
+ * 濡傛灉鐢ㄦ埛涓嶅瓨鍦ㄩ粯璁ゆ敞鍏�-1浠h〃鏃犵敤鎴�
+ */
+ private static final Long DEFAULT_USER_ID = -1L;
+
+ /**
+ * 鎻掑叆濉厖鏂规硶锛岀敤浜庡湪鎻掑叆鏁版嵁鏃惰嚜鍔ㄥ~鍏呭疄浣撳璞′腑鐨勫垱寤烘椂闂淬�佹洿鏂版椂闂淬�佸垱寤轰汉銆佹洿鏂颁汉绛変俊鎭�
+ *
+ * @param metaObject 鍏冨璞★紝鐢ㄤ簬鑾峰彇鍘熷瀵硅薄骞惰繘琛屽~鍏�
+ */
+ @Override
+ public void insertFill(MetaObject metaObject) {
+ try {
+ if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
+ // 鑾峰彇褰撳墠鏃堕棿浣滀负鍒涘缓鏃堕棿鍜屾洿鏂版椂闂达紝濡傛灉鍒涘缓鏃堕棿涓嶄负绌猴紝鍒欎娇鐢ㄥ垱寤烘椂闂达紝鍚﹀垯浣跨敤褰撳墠鏃堕棿
+ Date current = ObjectUtils.notNull(baseEntity.getCreateTime(), new Date());
+ baseEntity.setCreateTime(current);
+ baseEntity.setUpdateTime(current);
+
+ // 濡傛灉鍒涘缓浜轰负绌猴紝鍒欏~鍏呭綋鍓嶇櫥褰曠敤鎴风殑淇℃伅
+ if (ObjectUtil.isNull(baseEntity.getCreateBy())) {
+ LoginUser loginUser = getLoginUser();
+ if (ObjectUtil.isNotNull(loginUser)) {
+ Long userId = loginUser.getUserId();
+ // 濉厖鍒涘缓浜恒�佹洿鏂颁汉鍜屽垱寤洪儴闂ㄤ俊鎭�
+ baseEntity.setCreateBy(userId);
+ baseEntity.setUpdateBy(userId);
+ baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId()));
+ } else {
+ // 濉厖鍒涘缓浜恒�佹洿鏂颁汉鍜屽垱寤洪儴闂ㄤ俊鎭�
+ baseEntity.setCreateBy(DEFAULT_USER_ID);
+ baseEntity.setUpdateBy(DEFAULT_USER_ID);
+ baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), DEFAULT_USER_ID));
+ }
+ }
+ } else {
+ Date date = new Date();
+ this.strictInsertFill(metaObject, "createTime", Date.class, date);
+ this.strictInsertFill(metaObject, "updateTime", Date.class, date);
+ }
+ } catch (Exception e) {
+ throw new ServiceException("鑷姩娉ㄥ叆寮傚父 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
+ }
+ }
+
+ /**
+ * 鏇存柊濉厖鏂规硶锛岀敤浜庡湪鏇存柊鏁版嵁鏃惰嚜鍔ㄥ~鍏呭疄浣撳璞′腑鐨勬洿鏂版椂闂村拰鏇存柊浜轰俊鎭�
+ *
+ * @param metaObject 鍏冨璞★紝鐢ㄤ簬鑾峰彇鍘熷瀵硅薄骞惰繘琛屽~鍏�
+ */
+ @Override
+ public void updateFill(MetaObject metaObject) {
+ try {
+ if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
+ // 鑾峰彇褰撳墠鏃堕棿浣滀负鏇存柊鏃堕棿锛屾棤璁哄師濮嬪璞′腑鐨勬洿鏂版椂闂存槸鍚︿负绌洪兘濉厖
+ Date current = new Date();
+ baseEntity.setUpdateTime(current);
+
+ // 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛鐨処D锛屽苟濉厖鏇存柊浜轰俊鎭�
+ Long userId = LoginHelper.getUserId();
+ if (ObjectUtil.isNotNull(userId)) {
+ baseEntity.setUpdateBy(userId);
+ } else {
+ baseEntity.setUpdateBy(DEFAULT_USER_ID);
+ }
+ } else {
+ this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
+ }
+ } catch (Exception e) {
+ throw new ServiceException("鑷姩娉ㄥ叆寮傚父 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
+ }
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛淇℃伅
+ *
+ * @return 褰撳墠鐧诲綍鐢ㄦ埛鐨勪俊鎭紝濡傛灉鐢ㄦ埛鏈櫥褰曞垯杩斿洖 null
+ */
+ private LoginUser getLoginUser() {
+ LoginUser loginUser;
+ try {
+ loginUser = LoginHelper.getLoginUser();
+ } catch (Exception e) {
+ return null;
+ }
+ return loginUser;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java
new file mode 100755
index 0000000..094785b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java
@@ -0,0 +1,89 @@
+package org.dromara.common.mybatis.handler;
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.hutool.http.HttpStatus;
+import com.baomidou.dynamic.datasource.exception.CannotFindDataSourceException;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.mybatis.spring.MyBatisSystemException;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * Mybatis寮傚父澶勭悊鍣�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RestControllerAdvice
+public class MybatisExceptionHandler {
+
+ /**
+ * 涓婚敭鎴朥NIQUE绱㈠紩锛屾暟鎹噸澶嶅紓甯�
+ */
+ @ExceptionHandler(DuplicateKeyException.class)
+ public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("璇锋眰鍦板潃'{}',鏁版嵁搴撲腑宸插瓨鍦ㄨ褰�'{}'", requestURI, e.getMessage());
+ return R.fail(HttpStatus.HTTP_CONFLICT, "鏁版嵁搴撲腑宸插瓨鍦ㄨ璁板綍锛岃鑱旂郴绠$悊鍛樼‘璁�");
+ }
+
+ /**
+ * Mybatis绯荤粺寮傚父 閫氱敤澶勭悊
+ */
+ @ExceptionHandler(MyBatisSystemException.class)
+ public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ Throwable root = getRootCause(e);
+ if (root instanceof NotLoginException) {
+ log.error("璇锋眰鍦板潃'{}',璁よ瘉澶辫触'{}',鏃犳硶璁块棶绯荤粺璧勬簮", requestURI, root.getMessage());
+ return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "璁よ瘉澶辫触锛屾棤娉曡闂郴缁熻祫婧�");
+ }
+ if (root instanceof CannotFindDataSourceException) {
+ log.error("璇锋眰鍦板潃'{}', 鏈壘鍒版暟鎹簮", requestURI);
+ return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "鏈壘鍒版暟鎹簮锛岃鑱旂郴绠$悊鍛樼‘璁�");
+ }
+ log.error("璇锋眰鍦板潃'{}', Mybatis绯荤粺寮傚父", requestURI, e);
+ return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, e.getMessage());
+ }
+
+ /**
+ * 鑾峰彇寮傚父鐨勬牴鍥狅紙閫掑綊鏌ユ壘锛�
+ *
+ * @param e 褰撳墠寮傚父
+ * @return 鏍瑰洜寮傚父锛堟渶搴曞眰鐨� cause锛�
+ * <p>
+ * 閫昏緫璇存槑锛�
+ * 1. 濡傛灉 e 娌℃湁 cause锛岃鏄� e 鏈韩灏辨槸鏍瑰洜锛岀洿鎺ヨ繑鍥�
+ * 2. 濡傛灉 e 鐨� cause 鍜岃嚜韬浉鍚岋紙闃叉寰幆寮曠敤锛夛紝涔熻繑鍥� e
+ * 3. 鍚﹀垯閫掑綊璋冪敤锛岀户缁悜涓嬪鎵炬渶搴曞眰鐨� cause
+ */
+ public static Throwable getRootCause(Throwable e) {
+ Throwable cause = e.getCause();
+ if (cause == null || cause == e) {
+ return e;
+ }
+ return getRootCause(cause);
+ }
+
+ /**
+ * 鍦ㄥ紓甯搁摼涓煡鎵炬寚瀹氱被鍨嬬殑寮傚父
+ *
+ * @param e 褰撳墠寮傚父
+ * @param clazz 鐩爣寮傚父绫�
+ * @return 鎵惧埌鐨勬寚瀹氱被鍨嬪紓甯革紝濡傛灉娌℃湁鎵惧埌杩斿洖 null
+ */
+ public static Throwable findCause(Throwable e, Class<? extends Throwable> clazz) {
+ Throwable t = e;
+ while (t != null && t != t.getCause()) {
+ if (clazz.isInstance(t)) {
+ return t;
+ }
+ t = t.getCause();
+ }
+ return null;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java
new file mode 100755
index 0000000..5c2ca98
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java
@@ -0,0 +1,261 @@
+package org.dromara.common.mybatis.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
+import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import org.dromara.common.core.domain.dto.RoleDTO;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.annotation.DataColumn;
+import org.dromara.common.mybatis.annotation.DataPermission;
+import org.dromara.common.mybatis.enums.DataScopeType;
+import org.dromara.common.mybatis.helper.DataPermissionHelper;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.expression.*;
+import org.springframework.expression.common.TemplateParserContext;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+import java.util.*;
+import java.util.function.Function;
+
+/**
+ * 鏁版嵁鏉冮檺杩囨护
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Slf4j
+public class PlusDataPermissionHandler {
+
+ /**
+ * spel 瑙f瀽鍣�
+ */
+ private final ExpressionParser parser = new SpelExpressionParser();
+ private final ParserContext parserContext = new TemplateParserContext();
+ /**
+ * bean瑙f瀽鍣� 鐢ㄤ簬澶勭悊 spel 琛ㄨ揪寮忎腑瀵� bean 鐨勮皟鐢�
+ */
+ private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
+
+ /**
+ * 鑾峰彇鏁版嵁杩囨护鏉′欢鐨� SQL 鐗囨
+ *
+ * @param where 鍘熷鐨勬煡璇㈡潯浠惰〃杈惧紡
+ * @param isSelect 鏄惁涓烘煡璇㈣鍙�
+ * @return 鏁版嵁杩囨护鏉′欢鐨� SQL 鐗囨
+ */
+ public Expression getSqlSegment(Expression where, boolean isSelect) {
+ try {
+ // 鑾峰彇鏁版嵁鏉冮檺閰嶇疆
+ DataPermission dataPermission = getDataPermission();
+ // 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛淇℃伅
+ LoginUser currentUser = DataPermissionHelper.getVariable("user");
+ if (ObjectUtil.isNull(currentUser)) {
+ currentUser = LoginHelper.getLoginUser();
+ DataPermissionHelper.setVariable("user", currentUser);
+ }
+ // 濡傛灉鏄秴绾х鐞嗗憳鎴栫鎴风鐞嗗憳锛屽垯涓嶈繃婊ゆ暟鎹�
+ if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
+ return where;
+ }
+ // 鏋勯�犳暟鎹繃婊ゆ潯浠剁殑 SQL 鐗囨
+ String dataFilterSql = buildDataFilter(dataPermission, isSelect);
+ if (StringUtils.isBlank(dataFilterSql)) {
+ return where;
+ }
+ Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql);
+ // 鏁版嵁鏉冮檺浣跨敤鍗曠嫭鐨勬嫭鍙� 闃叉涓庡叾浠栨潯浠跺啿绐�
+ ParenthesedExpressionList<Expression> parenthesis = new ParenthesedExpressionList<>(expression);
+ if (ObjectUtil.isNotNull(where)) {
+ return new AndExpression(where, parenthesis);
+ } else {
+ return parenthesis;
+ }
+ } catch (JSQLParserException e) {
+ throw new ServiceException("鏁版嵁鏉冮檺瑙f瀽寮傚父 => " + e.getMessage());
+ } finally {
+ DataPermissionHelper.removePermission();
+ }
+ }
+
+ /**
+ * 鏋勫缓鏁版嵁杩囨护鏉′欢鐨� SQL 璇彞
+ *
+ * @param dataPermission 鏁版嵁鏉冮檺娉ㄨВ
+ * @param isSelect 鏍囧織褰撳墠鎿嶄綔鏄惁涓烘煡璇㈡搷浣滐紝鏌ヨ鎿嶄綔鍜屾洿鏂版垨鍒犻櫎鎿嶄綔鍦ㄥ鐞嗚繃婊ゆ潯浠舵椂浼氭湁涓嶅悓鐨勫鐞嗘柟寮�
+ * @return 鏋勫缓鐨勬暟鎹繃婊ゆ潯浠剁殑 SQL 璇彞
+ * @throws ServiceException 濡傛灉瑙掕壊鐨勬暟鎹寖鍥村紓甯告垨鑰� key 涓� value 鐨勯暱搴︿笉鍖归厤锛屽垯鎶涘嚭 ServiceException 寮傚父
+ */
+ private String buildDataFilter(DataPermission dataPermission, boolean isSelect) {
+ // 鏇存柊鎴栧垹闄ら渶婊¤冻鎵�鏈夋潯浠�
+ String joinStr = isSelect ? " OR " : " AND ";
+ if (StringUtils.isNotBlank(dataPermission.joinStr())) {
+ joinStr = " " + dataPermission.joinStr() + " ";
+ }
+ LoginUser user = DataPermissionHelper.getVariable("user");
+ Object defaultValue = "-1";
+ NullSafeStandardEvaluationContext context = new NullSafeStandardEvaluationContext(defaultValue);
+ context.addPropertyAccessor(new NullSafePropertyAccessor(context.getPropertyAccessors().get(0), defaultValue));
+ context.setBeanResolver(beanResolver);
+ DataPermissionHelper.getContext().forEach(context::setVariable);
+ Set<String> conditions = new HashSet<>();
+ // 浼樺厛璁剧疆鍙橀噺
+ List<String> keys = new ArrayList<>();
+ Map<DataColumn, Boolean> ignoreMap = new HashMap<>();
+ for (DataColumn dataColumn : dataPermission.value()) {
+ if (dataColumn.key().length != dataColumn.value().length) {
+ throw new ServiceException("瑙掕壊鏁版嵁鑼冨洿寮傚父 => key涓巚alue闀垮害涓嶅尮閰�");
+ }
+ // 鍖呭惈鏉冮檺鏍囪瘑绗� 杩欑洿鎺ヨ烦杩�
+ if (StringUtils.isNotBlank(dataColumn.permission()) &&
+ CollUtil.contains(user.getMenuPermission(), dataColumn.permission())
+ ) {
+ ignoreMap.put(dataColumn, Boolean.TRUE);
+ continue;
+ }
+ // 璁剧疆娉ㄨВ鍙橀噺 key 涓鸿〃杈惧紡鍙橀噺 value 涓哄彉閲忓��
+ for (int i = 0; i < dataColumn.key().length; i++) {
+ context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
+ }
+ keys.addAll(Arrays.stream(dataColumn.key()).map(key -> "#" + key).toList());
+ }
+
+ for (RoleDTO role : user.getRoles()) {
+ user.setRoleId(role.getRoleId());
+ // 鑾峰彇瑙掕壊鏉冮檺娉涘瀷
+ DataScopeType type = DataScopeType.findCode(role.getDataScope());
+ if (ObjectUtil.isNull(type)) {
+ throw new ServiceException("瑙掕壊鏁版嵁鑼冨洿寮傚父 => " + role.getDataScope());
+ }
+ // 鍏ㄩ儴鏁版嵁鏉冮檺鐩存帴杩斿洖
+ if (type == DataScopeType.ALL) {
+ return StringUtils.EMPTY;
+ }
+ boolean isSuccess = false;
+ for (DataColumn dataColumn : dataPermission.value()) {
+ // 鍖呭惈鏉冮檺鏍囪瘑绗� 杩欑洿鎺ヨ烦杩�
+ if (ignoreMap.containsKey(dataColumn)) {
+ // 淇澶氳鑹蹭笌鏉冮檺鏍囪瘑绗﹀叡鐢ㄩ棶棰� https://gitee.com/dromara/RuoYi-Vue-Plus/issues/IB4CS4
+ conditions.add(joinStr + " 1 = 1 ");
+ isSuccess = true;
+ continue;
+ }
+ // 涓嶅寘鍚� key 鍙橀噺 鍒欎笉澶勭悊
+ if (!StringUtils.containsAny(type.getSqlTemplate(), keys.toArray(String[]::new))) {
+ continue;
+ }
+ // 褰撳墠娉ㄨВ涓嶆弧瓒虫ā鏉� 涓嶅鐞�
+ if (!StringUtils.containsAny(type.getSqlTemplate(), dataColumn.key())) {
+ continue;
+ }
+ // 蹇界暐鏁版嵁鏉冮檺 闃叉spel琛ㄨ揪寮忓唴鏈夊叾浠杝ql鏌ヨ瀵艰嚧姝诲惊鐜皟鐢�
+ String sql = DataPermissionHelper.ignore(() ->
+ parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class)
+ );
+ // 瑙f瀽sql妯℃澘骞跺~鍏�
+ conditions.add(joinStr + sql);
+ isSuccess = true;
+ }
+ // 鏈鐞嗘垚鍔熷垯濉厖鍏滃簳鏂规
+ if (!isSuccess && StringUtils.isNotBlank(type.getElseSql())) {
+ conditions.add(joinStr + type.getElseSql());
+ }
+ }
+
+ if (CollUtil.isNotEmpty(conditions)) {
+ String sql = StreamUtils.join(conditions, Function.identity(), "");
+ return sql.substring(joinStr.length());
+ }
+ return StringUtils.EMPTY;
+ }
+
+ /**
+ * 鏍规嵁鏄犲皠璇彞 ID 鎴栫被鍚嶈幏鍙栧搴旂殑 DataPermission 娉ㄨВ瀵硅薄
+ *
+ * @return DataPermission 娉ㄨВ瀵硅薄锛屽鏋滀笉瀛樺湪鍒欒繑鍥� null
+ */
+ public DataPermission getDataPermission() {
+ return DataPermissionHelper.getPermission();
+ }
+
+ /**
+ * 妫�鏌ョ粰瀹氱殑鏄犲皠璇彞 ID 鏄惁鏈夋晥锛屽嵆鏄惁鑳藉鎵惧埌瀵瑰簲鐨� DataPermission 娉ㄨВ瀵硅薄
+ *
+ * @return 濡傛灉鎵惧埌瀵瑰簲鐨� DataPermission 娉ㄨВ瀵硅薄锛屽垯杩斿洖 false锛涘惁鍒欒繑鍥� true
+ */
+ public boolean invalid() {
+ return getDataPermission() == null;
+ }
+
+ /**
+ * 瀵规墍鏈塶ull鍙橀噺鎵句笉鍒扮殑鍙橀噺杩斿洖榛樿鍊�
+ */
+ @AllArgsConstructor
+ private static class NullSafeStandardEvaluationContext extends StandardEvaluationContext {
+
+ private final Object defaultValue;
+
+ @Override
+ public Object lookupVariable(String name) {
+ Object obj = super.lookupVariable(name);
+ // 濡傛灉璇诲彇鍒扮殑鍊兼槸 null锛屽垯杩斿洖榛樿鍊�
+ if (obj == null) {
+ return defaultValue;
+ }
+ return obj;
+ }
+
+ }
+
+ /**
+ * 瀵规墍鏈塶ull鍙橀噺鎵句笉鍒扮殑鍙橀噺杩斿洖榛樿鍊� 濮旀墭妯″紡 灏嗕笉闇�瑕佸鐞嗙殑鏂规硶濮旀墭缁欏師澶勭悊鍣�
+ */
+ @AllArgsConstructor
+ private static class NullSafePropertyAccessor implements PropertyAccessor {
+
+ private final PropertyAccessor delegate;
+ private final Object defaultValue;
+
+ @Override
+ public Class<?>[] getSpecificTargetClasses() {
+ return delegate.getSpecificTargetClasses();
+ }
+
+ @Override
+ public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
+ return delegate.canRead(context, target, name);
+ }
+
+ @Override
+ public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
+ TypedValue value = delegate.read(context, target, name);
+ // 濡傛灉璇诲彇鍒扮殑鍊兼槸 null锛屽垯杩斿洖榛樿鍊�
+ if (value.getValue() == null) {
+ return new TypedValue(defaultValue);
+ }
+ return value;
+ }
+
+ @Override
+ public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
+ return delegate.canWrite(context, target, name);
+ }
+
+ @Override
+ public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
+ delegate.write(context, target, name, newValue);
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusPostInitTableInfoHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusPostInitTableInfoHandler.java
new file mode 100755
index 0000000..60ca20b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusPostInitTableInfoHandler.java
@@ -0,0 +1,27 @@
+package org.dromara.common.mybatis.handler;
+
+import cn.hutool.core.convert.Convert;
+import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+import org.apache.ibatis.session.Configuration;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+
+/**
+ * 淇敼琛ㄤ俊鎭垵濮嬪寲鏂瑰紡
+ * 鐩墠鐢ㄤ簬鍏ㄥ眬淇敼鏄惁浣跨敤閫昏緫鍒犻櫎
+ *
+ * @author Lion Li
+ */
+public class PlusPostInitTableInfoHandler implements PostInitTableInfoHandler {
+
+ @Override
+ public void postTableInfo(TableInfo tableInfo, Configuration configuration) {
+ String flag = SpringUtils.getProperty("mybatis-plus.enableLogicDelete", "true");
+ // 鍙湁鍏抽棴鏃� 缁熶竴璁剧疆false 涓簍rue鏃秏p鑷姩鍒ゆ柇涓嶅鐞�
+ if (!Convert.toBool(flag)) {
+ ReflectUtils.setFieldValue(tableInfo, "withLogicDelete", false);
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java
new file mode 100755
index 0000000..84a8a5c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java
@@ -0,0 +1,82 @@
+package org.dromara.common.mybatis.helper;
+
+import cn.hutool.core.convert.Convert;
+import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.mybatis.enums.DataBaseType;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鏁版嵁搴撳姪鎵�
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class DataBaseHelper {
+
+ private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class);
+
+ /**
+ * 鑾峰彇褰撳墠鏁版嵁婧愬搴旂殑鏁版嵁搴撶被鍨�
+ * <p>
+ * 閫氳繃 DynamicRoutingDataSource 鑾峰彇褰撳墠绾跨▼缁戝畾鐨勬暟鎹簮锛�
+ * 鐒跺悗浠庢暟鎹簮鑾峰彇鏁版嵁搴撹繛鎺ワ紝鍒╃敤杩炴帴鐨勫厓鏁版嵁鑾峰彇鏁版嵁搴撲骇鍝佸悕绉帮紝
+ * 鏈�鍚庤皟鐢� DataBaseType.find 鏂规硶灏嗘暟鎹簱鍚嶇О杞崲涓哄搴旂殑鏋氫妇绫诲瀷
+ *
+ * @return 褰撳墠鏁版嵁搴撳搴旂殑 DataBaseType 鏋氫妇锛屾壘涓嶅埌鏃堕粯璁よ繑鍥� MY_SQL
+ * @throws ServiceException 褰撹幏鍙栨暟鎹簱杩炴帴鎴栧厓鏁版嵁鍑虹幇寮傚父鏃舵姏鍑轰笟鍔″紓甯�
+ */
+ public static DataBaseType getDataBaseType() {
+ DataSource dataSource = DS.determineDataSource();
+ try (Connection conn = dataSource.getConnection()) {
+ DatabaseMetaData metaData = conn.getMetaData();
+ String databaseProductName = metaData.getDatabaseProductName();
+ return DataBaseType.find(databaseProductName);
+ } catch (SQLException e) {
+ throw new RuntimeException("鑾峰彇鏁版嵁搴撶被鍨嬪け璐�", e);
+ }
+ }
+
+ /**
+ * 鏍规嵁褰撳墠鏁版嵁搴撶被鍨嬶紝鐢熸垚鍏煎鐨� FIND_IN_SET 璇彞鐗囨
+ * <p>
+ * 鐢ㄤ簬鍒ゆ柇鎸囧畾鍊兼槸鍚﹀瓨鍦ㄤ簬閫楀彿鍒嗛殧鐨勫瓧绗︿覆鍒椾腑锛孲QL鍐欐硶鏍规嵁涓嶅悓鏁版嵁搴撴柟瑷�鑷姩鍒囨崲锛�
+ * - Oracle 浣跨敤 instr 鍑芥暟
+ * - PostgreSQL 浣跨敤 strpos 鍑芥暟
+ * - SQL Server 浣跨敤 charindex 鍑芥暟
+ * - 鍏朵粬榛樿浣跨敤 MySQL 鐨� find_in_set 鍑芥暟
+ *
+ * @param var1 瑕佹煡鎵剧殑鍊硷紙鏀寔浠绘剰绫诲瀷锛屽唴閮ㄤ細杞崲鎴愬瓧绗︿覆锛�
+ * @param var2 瀛樺偍閫楀彿鍒嗛殧鍊肩殑鏁版嵁搴撳垪鍚�
+ * @return 閫傜敤浜庡綋鍓嶆暟鎹簱鐨� SQL 鏉′欢瀛楃涓诧紝閫氬父鐢ㄤ簬 where 鎴� apply 涓嫾鎺�
+ */
+ public static String findInSet(Object var1, String var2) {
+ String var = Convert.toStr(var1);
+ return switch (getDataBaseType()) {
+ // instr(',0,100,101,' , ',100,') <> 0
+ case ORACLE -> "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);
+ // (select strpos(',0,100,101,' , ',100,')) <> 0
+ case POSTGRE_SQL -> "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var);
+ // charindex(',100,' , ',0,100,101,') <> 0
+ case SQL_SERVER -> "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2);
+ // find_in_set(100 , '0,100,101')
+ default -> "find_in_set('%s' , %s) <> 0".formatted(var, var2);
+ };
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鍔犺浇鐨勬暟鎹簱鍚�
+ */
+ public static List<String> getDataSourceNameList() {
+ return new ArrayList<>(DS.getDataSources().keySet());
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java
new file mode 100755
index 0000000..8963648
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java
@@ -0,0 +1,176 @@
+package org.dromara.common.mybatis.helper;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.context.model.SaStorage;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import org.dromara.common.mybatis.annotation.DataPermission;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Stack;
+import java.util.function.Supplier;
+
+/**
+ * 鏁版嵁鏉冮檺鍔╂墜
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@SuppressWarnings("unchecked cast")
+public class DataPermissionHelper {
+
+ private static final String DATA_PERMISSION_KEY = "data:permission";
+
+ private static final ThreadLocal<Stack<Integer>> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new);
+
+ private static final ThreadLocal<DataPermission> PERMISSION_CACHE = new ThreadLocal<>();
+
+ /**
+ * 鑾峰彇褰撳墠鎵цmapper鏉冮檺娉ㄨВ
+ *
+ * @return 杩斿洖褰撳墠鎵цmapper鏉冮檺娉ㄨВ
+ */
+ public static DataPermission getPermission() {
+ return PERMISSION_CACHE.get();
+ }
+
+ /**
+ * 璁剧疆褰撳墠鎵цmapper鏉冮檺娉ㄨВ
+ *
+ * @param dataPermission 鏁版嵁鏉冮檺娉ㄨВ
+ */
+ public static void setPermission(DataPermission dataPermission) {
+ PERMISSION_CACHE.set(dataPermission);
+ }
+
+ /**
+ * 鍒犻櫎褰撳墠鎵цmapper鏉冮檺娉ㄨВ
+ */
+ public static void removePermission() {
+ PERMISSION_CACHE.remove();
+ }
+
+ /**
+ * 浠庝笂涓嬫枃涓幏鍙栨寚瀹氶敭鐨勫彉閲忓�硷紝骞跺皢鍏惰浆鎹负鎸囧畾鐨勭被鍨�
+ *
+ * @param key 鍙橀噺鐨勯敭
+ * @param <T> 鍙橀噺鍊肩殑绫诲瀷
+ * @return 鎸囧畾閿殑鍙橀噺鍊硷紝濡傛灉涓嶅瓨鍦ㄥ垯杩斿洖 null
+ */
+ public static <T> T getVariable(String key) {
+ Map<String, Object> context = getContext();
+ return (T) context.get(key);
+ }
+
+ /**
+ * 鍚戜笂涓嬫枃涓缃寚瀹氶敭鐨勫彉閲忓��
+ *
+ * @param key 瑕佽缃殑鍙橀噺鐨勯敭
+ * @param value 瑕佽缃殑鍙橀噺鍊�
+ */
+ public static void setVariable(String key, Object value) {
+ Map<String, Object> context = getContext();
+ context.put(key, value);
+ }
+
+ /**
+ * 鑾峰彇鏁版嵁鏉冮檺涓婁笅鏂�
+ *
+ * @return 瀛樺偍鍦⊿aStorage涓殑Map瀵硅薄锛岀敤浜庡瓨鍌ㄦ暟鎹潈闄愮浉鍏崇殑涓婁笅鏂囦俊鎭�
+ * @throws NullPointerException 濡傛灉鏁版嵁鏉冮檺涓婁笅鏂囩被鍨嬪紓甯革紝鍒欐姏鍑篘ullPointerException
+ */
+ public static Map<String, Object> getContext() {
+ SaStorage saStorage = SaHolder.getStorage();
+ Object attribute = saStorage.get(DATA_PERMISSION_KEY);
+ if (ObjectUtil.isNull(attribute)) {
+ saStorage.set(DATA_PERMISSION_KEY, new HashMap<>());
+ attribute = saStorage.get(DATA_PERMISSION_KEY);
+ }
+ if (attribute instanceof Map map) {
+ return map;
+ }
+ throw new NullPointerException("data permission context type exception");
+ }
+
+ private static IgnoreStrategy getIgnoreStrategy() {
+ Object ignoreStrategyLocal = ReflectUtils.getStaticFieldValue(ReflectUtils.getField(InterceptorIgnoreHelper.class, "IGNORE_STRATEGY_LOCAL"));
+ if (ignoreStrategyLocal instanceof ThreadLocal<?> IGNORE_STRATEGY_LOCAL) {
+ if (IGNORE_STRATEGY_LOCAL.get() instanceof IgnoreStrategy ignoreStrategy) {
+ return ignoreStrategy;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 寮�鍚拷鐣ユ暟鎹潈闄�(寮�鍚悗闇�鎵嬪姩璋冪敤 {@link #disableIgnore()} 鍏抽棴)
+ */
+ private static void enableIgnore() {
+ IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
+ if (ObjectUtil.isNull(ignoreStrategy)) {
+ InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build());
+ } else {
+ ignoreStrategy.setDataPermission(true);
+ }
+ Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
+ reentrantStack.push(reentrantStack.size() + 1);
+ }
+
+ /**
+ * 鍏抽棴蹇界暐鏁版嵁鏉冮檺
+ */
+ private static void disableIgnore() {
+ IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
+ if (ObjectUtil.isNotNull(ignoreStrategy)) {
+ boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName())
+ && !Boolean.TRUE.equals(ignoreStrategy.getBlockAttack())
+ && !Boolean.TRUE.equals(ignoreStrategy.getIllegalSql())
+ && !Boolean.TRUE.equals(ignoreStrategy.getTenantLine())
+ && CollectionUtil.isEmpty(ignoreStrategy.getOthers());
+ Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
+ boolean empty = reentrantStack.isEmpty() || reentrantStack.pop() == 1;
+ if (noOtherIgnoreStrategy && empty) {
+ InterceptorIgnoreHelper.clearIgnoreStrategy();
+ } else if (empty) {
+ ignoreStrategy.setDataPermission(false);
+ }
+
+ }
+ }
+
+ /**
+ * 鍦ㄥ拷鐣ユ暟鎹潈闄愪腑鎵ц
+ *
+ * @param handle 澶勭悊鎵ц鏂规硶
+ */
+ public static void ignore(Runnable handle) {
+ enableIgnore();
+ try {
+ handle.run();
+ } finally {
+ disableIgnore();
+ }
+ }
+
+ /**
+ * 鍦ㄥ拷鐣ユ暟鎹潈闄愪腑鎵ц
+ *
+ * @param handle 澶勭悊鎵ц鏂规硶
+ */
+ public static <T> T ignore(Supplier<T> handle) {
+ enableIgnore();
+ try {
+ return handle.get();
+ } finally {
+ disableIgnore();
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java
new file mode 100755
index 0000000..b37d96e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java
@@ -0,0 +1,172 @@
+package org.dromara.common.mybatis.interceptor;
+
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
+import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
+import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
+import com.baomidou.mybatisplus.extension.plugins.inner.BaseMultiTableInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.delete.Delete;
+import net.sf.jsqlparser.statement.select.PlainSelect;
+import net.sf.jsqlparser.statement.select.Select;
+import net.sf.jsqlparser.statement.select.SetOperationList;
+import net.sf.jsqlparser.statement.update.Update;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.executor.statement.StatementHandler;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlCommandType;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+import org.dromara.common.mybatis.handler.PlusDataPermissionHandler;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * 鏁版嵁鏉冮檺鎷︽埅鍣�
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Slf4j
+public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
+
+ private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler();
+
+ /**
+ * 鍦ㄦ墽琛屾煡璇箣鍓嶏紝妫�鏌ュ苟澶勭悊鏁版嵁鏉冮檺鐩稿叧閫昏緫
+ *
+ * @param executor MyBatis 鎵ц鍣ㄥ璞�
+ * @param ms 鏄犲皠璇彞瀵硅薄
+ * @param parameter 鏂规硶鍙傛暟
+ * @param rowBounds 鍒嗛〉瀵硅薄
+ * @param resultHandler 缁撴灉澶勭悊鍣�
+ * @param boundSql 缁戝畾鐨� SQL 瀵硅薄
+ * @throws SQLException 濡傛灉鍙戠敓 SQL 寮傚父
+ */
+ @Override
+ public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
+ // 妫�鏌ユ槸鍚﹂渶瑕佸拷鐣ユ暟鎹潈闄愬鐞�
+ if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
+ return;
+ }
+ // 妫�鏌ユ槸鍚︾己灏戞湁鏁堢殑鏁版嵁鏉冮檺娉ㄨВ
+ if (dataPermissionHandler.invalid()) {
+ return;
+ }
+ // 瑙f瀽 sql 鍒嗛厤瀵瑰簲鏂规硶
+ PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
+ mpBs.sql(parserSingle(mpBs.sql(), ms.getId()));
+ }
+
+ /**
+ * 鍦ㄥ噯澶� SQL 璇彞涔嬪墠锛屾鏌ュ苟澶勭悊鏇存柊鍜屽垹闄ゆ搷浣滅殑鏁版嵁鏉冮檺鐩稿叧閫昏緫
+ *
+ * @param sh MyBatis StatementHandler 瀵硅薄
+ * @param connection 鏁版嵁搴撹繛鎺ュ璞�
+ * @param transactionTimeout 浜嬪姟瓒呮椂鏃堕棿
+ */
+ @Override
+ public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
+ PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
+ MappedStatement ms = mpSh.mappedStatement();
+ // 鑾峰彇 SQL 鍛戒护绫诲瀷锛堝銆佸垹銆佹敼銆佹煡锛�
+ SqlCommandType sct = ms.getSqlCommandType();
+
+ // 鍙鐞嗘洿鏂板拰鍒犻櫎鎿嶄綔鐨� SQL 璇彞
+ if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
+ if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
+ return;
+ }
+ // 妫�鏌ユ槸鍚︾己灏戞湁鏁堢殑鏁版嵁鏉冮檺娉ㄨВ
+ if (dataPermissionHandler.invalid()) {
+ return;
+ }
+ PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
+ mpBs.sql(parserMulti(mpBs.sql(), ms.getId()));
+ }
+ }
+
+ /**
+ * 澶勭悊 SELECT 鏌ヨ璇彞涓殑 WHERE 鏉′欢
+ *
+ * @param select SELECT 鏌ヨ瀵硅薄
+ * @param index 鏌ヨ璇彞鐨勭储寮�
+ * @param sql 鏌ヨ璇彞
+ * @param obj WHERE 鏉′欢鍙傛暟
+ */
+ @Override
+ protected void processSelect(Select select, int index, String sql, Object obj) {
+ if (select instanceof PlainSelect) {
+ this.setWhere((PlainSelect) select, (String) obj);
+ } else if (select instanceof SetOperationList setOperationList) {
+ List<Select> selectBodyList = setOperationList.getSelects();
+ selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
+ }
+ }
+
+ /**
+ * 澶勭悊 UPDATE 璇彞涓殑 WHERE 鏉′欢
+ *
+ * @param update UPDATE 鏌ヨ瀵硅薄
+ * @param index 鏌ヨ璇彞鐨勭储寮�
+ * @param sql 鏌ヨ璇彞
+ * @param obj WHERE 鏉′欢鍙傛暟
+ */
+ @Override
+ protected void processUpdate(Update update, int index, String sql, Object obj) {
+ Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), false);
+ if (null != sqlSegment) {
+ update.setWhere(sqlSegment);
+ }
+ }
+
+ /**
+ * 澶勭悊 DELETE 璇彞涓殑 WHERE 鏉′欢
+ *
+ * @param delete DELETE 鏌ヨ瀵硅薄
+ * @param index 鏌ヨ璇彞鐨勭储寮�
+ * @param sql 鏌ヨ璇彞
+ * @param obj WHERE 鏉′欢鍙傛暟
+ */
+ @Override
+ protected void processDelete(Delete delete, int index, String sql, Object obj) {
+ Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), false);
+ if (null != sqlSegment) {
+ delete.setWhere(sqlSegment);
+ }
+ }
+
+ /**
+ * 璁剧疆 SELECT 璇彞鐨� WHERE 鏉′欢
+ *
+ * @param plainSelect SELECT 鏌ヨ瀵硅薄
+ * @param mappedStatementId 鏄犲皠璇彞鐨� ID
+ */
+ protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
+ Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), true);
+ if (null != sqlSegment) {
+ plainSelect.setWhere(sqlSegment);
+ }
+ }
+
+ /**
+ * 鏋勫缓琛ㄨ揪寮忥紝鐢ㄤ簬澶勭悊琛ㄧ殑鏁版嵁鏉冮檺
+ *
+ * @param table 琛ㄥ璞�
+ * @param where WHERE 鏉′欢琛ㄨ揪寮�
+ * @param whereSegment WHERE 鏉′欢鐗囨
+ * @return 鏋勫缓鐨勮〃杈惧紡
+ */
+ @Override
+ public Expression buildTableExpression(Table table, Expression where, String whereSegment) {
+ // 鍙湁鏂扮増鏁版嵁鏉冮檺澶勭悊鍣ㄦ墠浼氭墽琛屽埌杩欓噷
+ final MultiDataPermissionHandler handler = (MultiDataPermissionHandler) dataPermissionHandler;
+ return handler.getSqlSegment(table, where, whereSegment);
+ }
+}
+
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/utils/IdGeneratorUtil.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/utils/IdGeneratorUtil.java
new file mode 100755
index 0000000..33b3f1c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/utils/IdGeneratorUtil.java
@@ -0,0 +1,129 @@
+package org.dromara.common.mybatis.utils;
+
+import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+
+/**
+ * ID 鐢熸垚宸ュ叿绫�
+ *
+ * @author AprilWind
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class IdGeneratorUtil {
+
+ private static final IdentifierGenerator GENERATOR = SpringUtils.getBean(IdentifierGenerator.class);
+
+ /**
+ * 鐢熸垚瀛楃涓茬被鍨嬩富閿� ID
+ * <p>
+ * 璋冪敤 {@link IdentifierGenerator#nextId(Object)}锛岃繑鍥� String 鏍煎紡 ID銆�
+ * </p>
+ *
+ * @return 瀛楃涓叉牸寮忎富閿� ID
+ */
+ public static String nextId() {
+ return GENERATOR.nextId(null).toString();
+ }
+
+ /**
+ * 鐢熸垚 Long 绫诲瀷涓婚敭 ID
+ * <p>
+ * 鑷姩灏嗙敓鎴愮殑鏁板瓧鍨嬩富閿浆鎹负 Long 绫诲瀷
+ * </p>
+ *
+ * @return Long 绫诲瀷涓婚敭 ID
+ */
+ public static Long nextLongId() {
+ return GENERATOR.nextId(null).longValue();
+ }
+
+ /**
+ * 鐢熸垚 Number 绫诲瀷涓婚敭 ID
+ * <p>
+ * 鎺ㄨ崘鍦ㄩ渶瑕佷繚鐣欏師濮� Number 绫诲瀷鏃朵娇鐢�
+ * </p>
+ *
+ * @return Number 绫诲瀷涓婚敭 ID
+ */
+ public static Number nextNumberId() {
+ return GENERATOR.nextId(null);
+ }
+
+ /**
+ * 鏍规嵁瀹炰綋鐢熸垚鏁板瓧鍨嬩富閿� ID
+ * <p>
+ * 鑻ヨ嚜瀹氫箟鐨� {@link IdentifierGenerator} 鏍规嵁瀹炰綋鍐呭鐢熸垚 ID锛屽垯鍙互浣跨敤鏈柟娉�
+ * </p>
+ *
+ * @param entity 瀹炰綋瀵硅薄
+ * @return Number 绫诲瀷涓婚敭 ID
+ */
+ public static Number nextId(Object entity) {
+ return GENERATOR.nextId(entity);
+ }
+
+ /**
+ * 鏍规嵁瀹炰綋鐢熸垚瀛楃涓蹭富閿� ID
+ * <p>
+ * 涓� {@link #nextId(Object)} 绫讳技锛屼絾杩斿洖 String 绫诲瀷
+ * </p>
+ *
+ * @param entity 瀹炰綋瀵硅薄
+ * @return 瀛楃涓叉牸寮忎富閿� ID
+ */
+ public static String nextStringId(Object entity) {
+ return GENERATOR.nextId(entity).toString();
+ }
+
+ /**
+ * 鐢熸垚 32 浣� UUID
+ * <p>
+ * 搴曞眰浣跨敤 {@link IdWorker#get32UUID()}
+ * </p>
+ *
+ * @return 32 浣� UUID 瀛楃涓�
+ */
+ public static String nextUUID() {
+ return IdWorker.get32UUID();
+ }
+
+ /**
+ * 鏍规嵁瀹炰綋鐢熸垚 32 浣� UUID
+ * <p>
+ * 榛樿 {@link IdentifierGenerator#nextUUID(Object)} 瀹炵幇蹇界暐瀹炰綋锛屼絾淇濈暀璇ユ柟娉曚究浜庢墿灞曘��
+ * </p>
+ *
+ * @param entity 瀹炰綋瀵硅薄
+ * @return 32 浣� UUID 瀛楃涓�
+ */
+ public static String nextUUID(Object entity) {
+ return GENERATOR.nextUUID(entity);
+ }
+
+ /**
+ * 鐢熸垚甯︽寚瀹氬墠缂�鐨勫瓧绗︿覆涓婚敭 ID
+ * <p>
+ * 绀轰緥锛歱refix = "ORD"锛岀敓鎴愮粨鏋滃舰濡傦細{@code ORD20251211000123}
+ * </p>
+ *
+ * @param prefix 鑷畾涔夊墠缂�
+ * @return 甯﹀墠缂�鐨勫瓧绗︿覆涓婚敭 ID
+ */
+ public static String nextIdWithPrefix(String prefix) {
+ return prefix + nextId();
+ }
+
+ /**
+ * 鐢熸垚甯﹀墠缂�鐨� UUID
+ *
+ * @param prefix 鍓嶇紑
+ * @return prefix + UUID
+ */
+ public static String nextUUIDWithPrefix(String prefix) {
+ return prefix + nextUUID();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..cc625da
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.mybatis.config.MybatisPlusConfig
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml
new file mode 100755
index 0000000..14662e1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml
@@ -0,0 +1,33 @@
+# 鍐呯疆閰嶇疆 涓嶅厑璁镐慨鏀� 濡傞渶淇敼璇峰湪 nacos 涓婂啓鐩稿悓閰嶇疆瑕嗙洊
+# MyBatisPlus閰嶇疆
+# https://baomidou.com/config/
+mybatis-plus:
+ # 鍚姩鏃舵槸鍚︽鏌� MyBatis XML 鏂囦欢鐨勫瓨鍦紝榛樿涓嶆鏌�
+ checkConfigLocation: false
+ configuration:
+ # 鑷姩椹煎嘲鍛藉悕瑙勫垯锛坈amel case锛夋槧灏�
+ mapUnderscoreToCamelCase: true
+ # MyBatis 鑷姩鏄犲皠绛栫暐
+ # NONE锛氫笉鍚敤 PARTIAL锛氬彧瀵归潪宓屽 resultMap 鑷姩鏄犲皠 FULL锛氬鎵�鏈� resultMap 鑷姩鏄犲皠
+ autoMappingBehavior: FULL
+ # MyBatis 鑷姩鏄犲皠鏃舵湭鐭ュ垪鎴栨湭鐭ュ睘鎬у鐞嗙瓥
+ # NONE锛氫笉鍋氬鐞� WARNING锛氭墦鍗扮浉鍏宠鍛� FAILING锛氭姏鍑哄紓甯稿拰璇︾粏淇℃伅
+ autoMappingUnknownColumnBehavior: NONE
+ # 鏇磋缁嗙殑鏃ュ織杈撳嚭 浼氭湁鎬ц兘鎹熻�� org.apache.ibatis.logging.stdout.StdOutImpl
+ # 鍏抽棴鏃ュ織璁板綍 (鍙崟绾娇鐢� p6spy 鍒嗘瀽) org.apache.ibatis.logging.nologging.NoLoggingImpl
+ # 榛樿鏃ュ織杈撳嚭 org.apache.ibatis.logging.slf4j.Slf4jImpl
+ logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
+ global-config:
+ # 鏄惁鎵撳嵃 Logo banner
+ banner: true
+ dbConfig:
+ # 涓婚敭绫诲瀷
+ # AUTO 鑷 NONE 绌� INPUT 鐢ㄦ埛杈撳叆 ASSIGN_ID 闆姳 ASSIGN_UUID 鍞竴 UUID
+ idType: ASSIGN_ID
+ # 閫昏緫宸插垹闄ゅ��(鍙寜闇�姹傞殢鎰忎慨鏀�)
+ logicDeleteValue: 1
+ # 閫昏緫鏈垹闄ゅ��
+ logicNotDeleteValue: 0
+ insertStrategy: NOT_NULL
+ updateStrategy: NOT_NULL
+ whereStrategy: NOT_NULL
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/resources/spy.properties b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/resources/spy.properties
new file mode 100755
index 0000000..f3ed7d8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-mybatis/src/main/resources/spy.properties
@@ -0,0 +1,20 @@
+# p6spy 鎬ц兘鍒嗘瀽鎻掍欢閰嶇疆鏂囦欢
+modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
+# 鑷畾涔夋棩蹇楁墦鍗�
+logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
+#鏃ュ織杈撳嚭鍒版帶鍒跺彴
+appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
+# 浣跨敤鏃ュ織绯荤粺璁板綍 sql
+#appender=com.p6spy.engine.spy.appender.Slf4JLogger
+# 鍙栨秷JDBC URL鍓嶇紑
+useprefix=true
+# 閰嶇疆璁板綍 Log 渚嬪,鍙幓鎺夌殑缁撴灉闆嗘湁error,info,batch,debug,statement,commit,rollback,result,resultset.
+excludecategories=info,debug,result,commit,resultset
+# 鏃ユ湡鏍煎紡
+dateformat=yyyy-MM-dd HH:mm:ss
+# SQL璇彞鎵撳嵃鏃堕棿鏍煎紡
+databaseDialectTimestampFormat=yyyy-MM-dd HH:mm:ss
+# 鏄惁杩囨护 Log
+filter=true
+# 杩囨护 Log 鏃舵墍鎺掗櫎鐨� sql 鍏抽敭瀛楋紝浠ラ�楀彿鍒嗛殧
+exclude=
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/.flattened-pom.xml
new file mode 100644
index 0000000..ded2209
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/.flattened-pom.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-oss</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-oss oss鏈嶅姟</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>s3</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>aws-crt-client</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>apache-client</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>url-connection-client</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>netty-nio-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>s3-transfer-manager</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/pom.xml
new file mode 100755
index 0000000..bd19cc8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-oss</artifactId>
+
+ <description>
+ ruoyi-common-oss oss鏈嶅姟
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+
+ <!-- AWS SDK for Java 2.x -->
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>s3</artifactId>
+ <exclusions>
+ <!-- 涓滆タ 30M 鐗瑰埆澶х殑 jar 鍖� 鎬ц兘璺� Netty 宸笉澶� 鏈夐渶瑕佸彲浠ヨ嚜琛屾浛鎹娇鐢� -->
+ <exclusion>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>aws-crt-client</artifactId>
+ </exclusion>
+ <!-- 灏嗗熀浜� Apache 鐨� HTTP 瀹㈡埛绔粠绫昏矾寰勪腑绉婚櫎 -->
+ <exclusion>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>apache-client</artifactId>
+ </exclusion>
+ <!-- 灏嗛厤缃熀浜� URL 杩炴帴鐨� HTTP 瀹㈡埛绔粠绫昏矾寰勪腑绉婚櫎 -->
+ <exclusion>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>url-connection-client</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <!-- 閫傜敤浜� Netty 鐨勫鎴风 -->
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>netty-nio-client</artifactId>
+ </dependency>
+
+ <!-- 瀹㈡埛绔殑鎬ц兘澧炲己浼犺緭绠$悊鍣� -->
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>s3-transfer-manager</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java
new file mode 100755
index 0000000..9d8db93
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java
@@ -0,0 +1,40 @@
+package org.dromara.common.oss.constant;
+
+import org.dromara.common.core.constant.GlobalConstants;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 瀵硅薄瀛樺偍甯搁噺
+ *
+ * @author Lion Li
+ */
+public interface OssConstant {
+
+ /**
+ * 榛樿閰嶇疆KEY
+ */
+ String DEFAULT_CONFIG_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss:default_config";
+
+ /**
+ * 棰勮鍒楄〃璧勬簮寮�鍏矺ey
+ */
+ String PEREVIEW_LIST_RESOURCE_KEY = "sys.oss.previewListResource";
+
+ /**
+ * 绯荤粺鏁版嵁ids
+ */
+ List<Long> SYSTEM_DATA_IDS = Arrays.asList(1L, 2L, 3L, 4L);
+
+ /**
+ * 浜戞湇鍔″晢
+ */
+ String[] CLOUD_SERVICE = new String[] {"aliyun", "qcloud", "qiniu", "obs"};
+
+ /**
+ * https 鐘舵��
+ */
+ String IS_HTTPS = "Y";
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
new file mode 100755
index 0000000..14fc4dc
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
@@ -0,0 +1,564 @@
+package org.dromara.common.oss.core;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.IdUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.file.FileUtils;
+import org.dromara.common.oss.constant.OssConstant;
+import org.dromara.common.oss.entity.UploadResult;
+import org.dromara.common.oss.enums.AccessPolicyType;
+import org.dromara.common.oss.exception.OssException;
+import org.dromara.common.oss.properties.OssProperties;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.async.AsyncResponseTransformer;
+import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
+import software.amazon.awssdk.core.async.ResponsePublisher;
+import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3AsyncClient;
+import software.amazon.awssdk.services.s3.S3Configuration;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.presigner.S3Presigner;
+import software.amazon.awssdk.transfer.s3.S3TransferManager;
+import software.amazon.awssdk.transfer.s3.model.*;
+import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.WritableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * S3 瀛樺偍鍗忚 鎵�鏈夊吋瀹筍3鍗忚鐨勪簯鍘傚晢鍧囨敮鎸�
+ * 闃块噷浜� 鑵捐浜� 涓冪墰浜� minio
+ *
+ * @author AprilWind
+ */
+@Slf4j
+public class OssClient {
+
+ /**
+ * 鏈嶅姟鍟�
+ */
+ private final String configKey;
+
+ /**
+ * 閰嶇疆灞炴��
+ */
+ private final OssProperties properties;
+
+ /**
+ * Amazon S3 寮傛瀹㈡埛绔�
+ */
+ private final S3AsyncClient client;
+
+ /**
+ * 鐢ㄤ簬绠$悊 S3 鏁版嵁浼犺緭鐨勯珮绾у伐鍏�
+ */
+ private final S3TransferManager transferManager;
+
+ /**
+ * AWS S3 棰勭鍚� URL 鐨勭敓鎴愬櫒
+ */
+ private final S3Presigner presigner;
+
+ /**
+ * 鏋勯�犳柟娉�
+ *
+ * @param configKey 閰嶇疆閿�
+ * @param ossProperties Oss閰嶇疆灞炴��
+ */
+ public OssClient(String configKey, OssProperties ossProperties) {
+ this.configKey = configKey;
+ this.properties = ossProperties;
+ try {
+ // 鍒涘缓 AWS 璁よ瘉淇℃伅
+ StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
+ AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
+
+ // MinIO 浣跨敤 HTTPS 闄愬埗浣跨敤鍩熷悕璁块棶锛岀珯鐐瑰~鍩熷悕銆傞渶瑕佸惎鐢ㄨ矾寰勬牱寮忚闂�
+ boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
+
+ // 鍒涘缓AWS鍩轰簬 Netty 鐨� S3 瀹㈡埛绔�
+ this.client = S3AsyncClient.builder()
+ .credentialsProvider(credentialsProvider)
+ .endpointOverride(URI.create(getEndpoint()))
+ .region(of())
+ .forcePathStyle(isStyle)
+ .httpClient(NettyNioAsyncHttpClient.builder()
+ .connectionTimeout(Duration.ofSeconds(60))
+ .connectionAcquisitionTimeout(Duration.ofSeconds(30))
+ .maxConcurrency(100)
+ .maxPendingConnectionAcquires(1000)
+ .build())
+ .build();
+
+ //AWS鍩轰簬 CRT 鐨� S3 AsyncClient 瀹炰緥鐢ㄤ綔 S3 浼犺緭绠$悊鍣ㄧ殑搴曞眰瀹㈡埛绔�
+ this.transferManager = S3TransferManager.builder().s3Client(this.client).build();
+
+ // 鍒涘缓 S3 閰嶇疆瀵硅薄
+ S3Configuration config = S3Configuration.builder().chunkedEncodingEnabled(false)
+ .pathStyleAccessEnabled(isStyle).build();
+
+ // 鍒涘缓 棰勭鍚� URL 鐨勭敓鎴愬櫒 瀹炰緥锛岀敤浜庣敓鎴� S3 棰勭鍚� URL
+ this.presigner = S3Presigner.builder()
+ .region(of())
+ .credentialsProvider(credentialsProvider)
+ .endpointOverride(URI.create(getDomain()))
+ .serviceConfiguration(config)
+ .build();
+
+ } catch (Exception e) {
+ if (e instanceof OssException) {
+ throw e;
+ }
+ throw new OssException("閰嶇疆閿欒! 璇锋鏌ョ郴缁熼厤缃�:[" + e.getMessage() + "]");
+ }
+ }
+
+ /**
+ * 涓婁紶鏂囦欢鍒� Amazon S3锛屽苟杩斿洖涓婁紶缁撴灉
+ *
+ * @param filePath 鏈湴鏂囦欢璺緞
+ * @param key 鍦� Amazon S3 涓殑瀵硅薄閿�
+ * @param md5Digest 鏈湴鏂囦欢鐨� MD5 鍝堝笇鍊硷紙鍙�夛級
+ * @param contentType 鏂囦欢鍐呭绫诲瀷
+ * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅
+ * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
+ */
+ public UploadResult upload(Path filePath, String key, String md5Digest, String contentType) {
+ try {
+ // 鏋勫缓涓婁紶璇锋眰瀵硅薄
+ FileUpload fileUpload = transferManager.uploadFile(
+ x -> {
+ x.source(filePath).putObjectRequest(
+ y -> y.bucket(properties.getBucketName())
+ .key(key)
+ .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
+ .contentType(contentType)
+ // 鐢ㄤ簬璁剧疆瀵硅薄鐨勮闂帶鍒跺垪琛紙ACL锛夈�備笉鍚屼簯鍘傚晢瀵笰CL鐨勬敮鎸佸拰瀹炵幇鏂瑰紡鏈夋墍涓嶅悓锛�
+ // 鍥犳鏍规嵁鍏蜂綋鐨勪簯鏈嶅姟鎻愪緵鍟嗭紝浣犲彲鑳介渶瑕佽繘琛屼笉鍚岀殑閰嶇疆锛堣嚜琛屽紑鍚紝闃块噷浜戞湁acl鏉冮檺閰嶇疆锛岃吘璁簯娌℃湁acl鏉冮檺閰嶇疆锛�
+ //.acl(getAccessPolicy().getObjectCannedACL())
+ .build()
+ );
+ if (log.isDebugEnabled()) {
+ x.addTransferListener(LoggingTransferListener.create());
+ }
+ }
+ );
+ // 绛夊緟涓婁紶瀹屾垚骞惰幏鍙栦笂浼犵粨鏋�
+ CompletedFileUpload uploadResult = fileUpload.completionFuture().join();
+ String eTag = uploadResult.response().eTag();
+
+ // 鎻愬彇涓婁紶缁撴灉涓殑 ETag锛屽苟鏋勫缓涓�涓嚜瀹氫箟鐨� UploadResult 瀵硅薄
+ return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build();
+ } catch (Exception e) {
+ // 鎹曡幏寮傚父骞舵姏鍑鸿嚜瀹氫箟寮傚父
+ throw new OssException("涓婁紶鏂囦欢澶辫触锛岃妫�鏌ラ厤缃俊鎭�:[" + e.getMessage() + "]");
+ } finally {
+ // 鏃犺涓婁紶鏄惁鎴愬姛锛屾渶缁堥兘浼氬垹闄や复鏃舵枃浠�
+ FileUtils.del(filePath);
+ }
+ }
+
+ /**
+ * 涓婁紶 InputStream 鍒� Amazon S3
+ *
+ * @param inputStream 瑕佷笂浼犵殑杈撳叆娴�
+ * @param key 鍦� Amazon S3 涓殑瀵硅薄閿�
+ * @param length 杈撳叆娴佺殑闀垮害
+ * @param contentType 鏂囦欢鍐呭绫诲瀷
+ * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅
+ * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
+ */
+ public UploadResult upload(InputStream inputStream, String key, Long length, String contentType) {
+ // 濡傛灉杈撳叆娴佷笉鏄� ByteArrayInputStream锛屽垯灏嗗叾璇诲彇涓哄瓧鑺傛暟缁勫啀鍒涘缓 ByteArrayInputStream
+ if (!(inputStream instanceof ByteArrayInputStream)) {
+ inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
+ }
+ try {
+ // 鍒涘缓寮傛璇锋眰浣擄紙length濡傛灉涓虹┖浼氭姤閿欙級
+ BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
+ .contentLength(length)
+ .subscribeTimeout(Duration.ofSeconds(120))
+ .build();
+
+ // 浣跨敤 transferManager 杩涜涓婁紶
+ Upload upload = transferManager.upload(
+ x -> {
+ x.requestBody(body).putObjectRequest(
+ y -> y.bucket(properties.getBucketName())
+ .key(key)
+ .contentType(contentType)
+ // 鐢ㄤ簬璁剧疆瀵硅薄鐨勮闂帶鍒跺垪琛紙ACL锛夈�備笉鍚屼簯鍘傚晢瀵笰CL鐨勬敮鎸佸拰瀹炵幇鏂瑰紡鏈夋墍涓嶅悓锛�
+ // 鍥犳鏍规嵁鍏蜂綋鐨勪簯鏈嶅姟鎻愪緵鍟嗭紝浣犲彲鑳介渶瑕佽繘琛屼笉鍚岀殑閰嶇疆锛堣嚜琛屽紑鍚紝闃块噷浜戞湁acl鏉冮檺閰嶇疆锛岃吘璁簯娌℃湁acl鏉冮檺閰嶇疆锛�
+ //.acl(getAccessPolicy().getObjectCannedACL())
+ .build()
+ );
+ if (log.isDebugEnabled()) {
+ x.addTransferListener(LoggingTransferListener.create());
+ }
+ }
+ );
+
+ // 灏嗚緭鍏ユ祦鍐欏叆璇锋眰浣�
+ body.writeInputStream(inputStream);
+
+ // 绛夊緟鏂囦欢涓婁紶鎿嶄綔瀹屾垚
+ CompletedUpload uploadResult = upload.completionFuture().join();
+ String eTag = uploadResult.response().eTag();
+
+ // 鎻愬彇涓婁紶缁撴灉涓殑 ETag锛屽苟鏋勫缓涓�涓嚜瀹氫箟鐨� UploadResult 瀵硅薄
+ return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build();
+ } catch (Exception e) {
+ throw new OssException("涓婁紶鏂囦欢澶辫触锛岃妫�鏌ラ厤缃俊鎭�:[" + e.getMessage() + "]");
+ }
+ }
+
+ /**
+ * 涓嬭浇鏂囦欢浠� Amazon S3 鍒颁复鏃剁洰褰�
+ *
+ * @param path 鏂囦欢鍦� Amazon S3 涓殑瀵硅薄閿�
+ * @return 涓嬭浇鍚庣殑鏂囦欢鍦ㄦ湰鍦扮殑涓存椂璺緞
+ * @throws OssException 濡傛灉涓嬭浇澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
+ */
+ public Path fileDownload(String path) {
+ // 鏋勫缓涓存椂鏂囦欢
+ Path tempFilePath = FileUtils.createTempFile().toPath();
+ // 浣跨敤 S3TransferManager 涓嬭浇鏂囦欢
+ FileDownload downloadFile = transferManager.downloadFile(
+ x -> {
+ x.destination(tempFilePath).getObjectRequest(
+ y -> y.bucket(properties.getBucketName())
+ .key(removeBaseUrl(path))
+ .build()
+ );
+ if (log.isDebugEnabled()) {
+ x.addTransferListener(LoggingTransferListener.create());
+ }
+ }
+ );
+ // 绛夊緟鏂囦欢涓嬭浇鎿嶄綔瀹屾垚
+ downloadFile.completionFuture().join();
+ return tempFilePath;
+ }
+
+ /**
+ * 涓嬭浇鏂囦欢浠� Amazon S3 鍒� 杈撳嚭娴�
+ *
+ * @param key 鏂囦欢鍦� Amazon S3 涓殑瀵硅薄閿�
+ * @param out 杈撳嚭娴�
+ * @param consumer 鑷畾涔夊鐞嗛�昏緫
+ * @throws OssException 濡傛灉涓嬭浇澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
+ */
+ public void download(String key, OutputStream out, Consumer<Long> consumer) {
+ try {
+ this.download(key, consumer).writeTo(out);
+ } catch (Exception e) {
+ throw new OssException("鏂囦欢涓嬭浇澶辫触锛岄敊璇俊鎭�:[" + e.getMessage() + "]");
+ }
+ }
+
+ /**
+ * 涓嬭浇鏂囦欢浠� Amazon S3 鍒� 杈撳嚭娴�
+ *
+ * @param key 鏂囦欢鍦� Amazon S3 涓殑瀵硅薄閿�
+ * @param contentLengthConsumer 鏂囦欢澶у皬娑堣垂鑰呭嚱鏁�
+ * @return 鍐欏嚭璁㈤槄鍣�
+ * @throws OssException 濡傛灉涓嬭浇澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
+ */
+ public WriteOutSubscriber<OutputStream> download(String key, Consumer<Long> contentLengthConsumer) {
+ try {
+ DownloadRequest.TypedBuilder<ResponsePublisher<GetObjectResponse>> typedBuilder = DownloadRequest.builder()
+ // 浣跨敤鍙戝竷璁㈤槄杞崲鍣�
+ .responseTransformer(AsyncResponseTransformer.toPublisher())
+ // 鏂囦欢瀵硅薄
+ .getObjectRequest(y -> y.bucket(properties.getBucketName()).key(key).build());
+ if (log.isDebugEnabled()) {
+ typedBuilder.addTransferListener(LoggingTransferListener.create());
+ }
+
+ // 浣跨敤 S3TransferManager 涓嬭浇鏂囦欢
+ Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(typedBuilder.build());
+ // 鑾峰彇涓嬭浇鍙戝竷璁㈤槄杞崲鍣�
+ ResponsePublisher<GetObjectResponse> publisher = publisherDownload.completionFuture().join().result();
+ // 鎵ц鏂囦欢澶у皬娑堣垂鑰呭嚱鏁�
+ Optional.ofNullable(contentLengthConsumer)
+ .ifPresent(lengthConsumer -> lengthConsumer.accept(publisher.response().contentLength()));
+
+ // 鏋勫缓鍐欏嚭璁㈤槄鍣ㄥ璞�
+ return out -> {
+ // 鍒涘缓鍙啓鍏ョ殑瀛楄妭閫氶亾
+ try (WritableByteChannel channel = Channels.newChannel(out)) {
+ // 璁㈤槄鏁版嵁
+ publisher.subscribe(byteBuffer -> {
+ while (byteBuffer.hasRemaining()) {
+ try {
+ channel.write(byteBuffer);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }).join();
+ }
+ };
+ } catch (Exception e) {
+ throw new OssException("鏂囦欢涓嬭浇澶辫触锛岄敊璇俊鎭�:[" + e.getMessage() + "]");
+ }
+ }
+
+ /**
+ * 鍒犻櫎浜戝瓨鍌ㄦ湇鍔′腑鎸囧畾璺緞涓嬫枃浠�
+ *
+ * @param path 鎸囧畾璺緞
+ */
+ public void delete(String path) {
+ try {
+ client.deleteObject(
+ x -> x.bucket(properties.getBucketName())
+ .key(removeBaseUrl(path))
+ .build());
+ } catch (Exception e) {
+ throw new OssException("鍒犻櫎鏂囦欢澶辫触锛岃妫�鏌ラ厤缃俊鎭�:[" + e.getMessage() + "]");
+ }
+ }
+
+ /**
+ * 鍒涘缓涓嬭浇璇锋眰鐨勯绛惧悕URL
+ *
+ * @param objectKey 瀵硅薄KEY
+ * @param expiredTime 閾炬帴鎺堟潈鍒版湡鏃堕棿
+ */
+ public String createPresignedGetUrl(String objectKey, Duration expiredTime) {
+ // 浣跨敤 AWS S3 棰勭鍚� URL 鐨勭敓鎴愬櫒 鑾峰彇涓嬭浇瀵硅薄鐨勯绛惧悕 URL
+ URL url = presigner.presignGetObject(
+ x -> x.signatureDuration(expiredTime)
+ .getObjectRequest(
+ y -> y.bucket(properties.getBucketName())
+ .key(objectKey)
+ .build())
+ .build())
+ .url();
+ return url.toExternalForm();
+ }
+
+ /**
+ * 鍒涘缓涓婁紶璇锋眰鐨勯绛惧悕URL
+ *
+ * @param objectKey 瀵硅薄KEY
+ * @param expiredTime 閾炬帴鎺堟潈鍒版湡鏃堕棿
+ * @param metadata 鍏冩暟鎹�
+ */
+ public String createPresignedPutUrl(String objectKey, Duration expiredTime, Map<String, String> metadata) {
+ // 浣跨敤 AWS S3 棰勭鍚� URL 鐨勭敓鎴愬櫒 鑾峰彇涓婁紶鏂囦欢瀵硅薄鐨勯绛惧悕 URL
+ URL url = presigner.presignPutObject(
+ x -> x.signatureDuration(expiredTime)
+ .putObjectRequest(
+ y -> y.bucket(properties.getBucketName())
+ .key(objectKey)
+ .metadata(metadata)
+ .build())
+ .build())
+ .url();
+ return url.toExternalForm();
+ }
+
+ /**
+ * 涓婁紶 byte[] 鏁版嵁鍒� Amazon S3锛屼娇鐢ㄦ寚瀹氱殑鍚庣紑鏋勯�犲璞¢敭銆�
+ *
+ * @param data 瑕佷笂浼犵殑 byte[] 鏁版嵁
+ * @param suffix 瀵硅薄閿殑鍚庣紑
+ * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅
+ * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
+ */
+ public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
+ return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length), contentType);
+ }
+
+ /**
+ * 涓婁紶 InputStream 鍒� Amazon S3锛屼娇鐢ㄦ寚瀹氱殑鍚庣紑鏋勯�犲璞¢敭銆�
+ *
+ * @param inputStream 瑕佷笂浼犵殑杈撳叆娴�
+ * @param suffix 瀵硅薄閿殑鍚庣紑
+ * @param length 杈撳叆娴佺殑闀垮害
+ * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅
+ * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
+ */
+ public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length, String contentType) {
+ return upload(inputStream, getPath(properties.getPrefix(), suffix), length, contentType);
+ }
+
+ /**
+ * 涓婁紶鏂囦欢鍒� Amazon S3锛屼娇鐢ㄦ寚瀹氱殑鍚庣紑鏋勯�犲璞¢敭
+ *
+ * @param file 瑕佷笂浼犵殑鏂囦欢
+ * @param suffix 瀵硅薄閿殑鍚庣紑
+ * @return UploadResult 鍖呭惈涓婁紶鍚庣殑鏂囦欢淇℃伅
+ * @throws OssException 濡傛灉涓婁紶澶辫触锛屾姏鍑鸿嚜瀹氫箟寮傚父
+ */
+ public UploadResult uploadSuffix(File file, String suffix) {
+ return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null, FileUtils.getMimeType(suffix));
+ }
+
+ /**
+ * 鑾峰彇鏂囦欢杈撳叆娴�
+ *
+ * @param path 瀹屾暣鏂囦欢璺緞
+ * @return 杈撳叆娴�
+ */
+ public InputStream getObjectContent(String path) throws IOException {
+ // 涓嬭浇鏂囦欢鍒颁复鏃剁洰褰�
+ Path tempFilePath = fileDownload(path);
+ // 鍒涘缓杈撳叆娴�
+ InputStream inputStream = Files.newInputStream(tempFilePath);
+ // 鍒犻櫎涓存椂鏂囦欢
+ FileUtils.del(tempFilePath);
+ // 杩斿洖瀵硅薄鍐呭鐨勮緭鍏ユ祦
+ return inputStream;
+ }
+
+ /**
+ * 鑾峰彇 S3 瀹㈡埛绔殑缁堢鐐� URL
+ *
+ * @return 缁堢鐐� URL
+ */
+ public String getEndpoint() {
+ // 鏍规嵁閰嶇疆鏂囦欢涓殑鏄惁浣跨敤 HTTPS锛岃缃崗璁ご閮�
+ String header = getIsHttps();
+ // 鎷兼帴鍗忚澶撮儴鍜岀粓绔偣锛屽緱鍒板畬鏁寸殑缁堢鐐� URL
+ return header + properties.getEndpoint();
+ }
+
+ /**
+ * 鑾峰彇 S3 瀹㈡埛绔殑缁堢鐐� URL锛堣嚜瀹氫箟鍩熷悕锛�
+ *
+ * @return 缁堢鐐� URL
+ */
+ public String getDomain() {
+ // 浠庨厤缃腑鑾峰彇鍩熷悕銆佺粓绔偣銆佹槸鍚︿娇鐢� HTTPS 绛変俊鎭�
+ String domain = properties.getDomain();
+ String endpoint = properties.getEndpoint();
+ String header = getIsHttps();
+
+ // 濡傛灉鏄簯鏈嶅姟鍟嗭紝鐩存帴杩斿洖鍩熷悕鎴栫粓绔偣
+ if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
+ return StringUtils.isNotEmpty(domain) ? header + domain : header + endpoint;
+ }
+
+ // 濡傛灉鏄� MinIO锛屽鐞嗗煙鍚嶅苟杩斿洖
+ if (StringUtils.isNotEmpty(domain)) {
+ return domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP) ? domain : header + domain;
+ }
+
+ // 杩斿洖缁堢鐐�
+ return header + endpoint;
+ }
+
+ /**
+ * 鏍规嵁浼犲叆鐨� region 鍙傛暟杩斿洖鐩稿簲鐨� AWS 鍖哄煙
+ * 濡傛灉 region 鍙傛暟闈炵┖锛屼娇鐢� Region.of 鏂规硶鍒涘缓骞惰繑鍥炲搴旂殑 AWS 鍖哄煙瀵硅薄
+ * 濡傛灉 region 鍙傛暟涓虹┖锛岃繑鍥炰竴涓粯璁ょ殑 AWS 鍖哄煙锛堜緥濡傦紝us-east-1锛夛紝浣滀负骞挎硾鏀寔鐨勫尯鍩�
+ *
+ * @return 瀵瑰簲鐨� AWS 鍖哄煙瀵硅薄锛屾垨鑰呴粯璁ょ殑骞挎硾鏀寔鐨勫尯鍩燂紙us-east-1锛�
+ */
+ public Region of() {
+ //AWS 鍖哄煙瀛楃涓�
+ String region = properties.getRegion();
+ // 濡傛灉 region 鍙傛暟闈炵┖锛屼娇鐢� Region.of 鏂规硶鍒涘缓瀵瑰簲鐨� AWS 鍖哄煙瀵硅薄锛屽惁鍒欒繑鍥為粯璁ゅ尯鍩�
+ return StringUtils.isNotEmpty(region) ? Region.of(region) : Region.US_EAST_1;
+ }
+
+ /**
+ * 鑾峰彇浜戝瓨鍌ㄦ湇鍔$殑URL
+ *
+ * @return 鏂囦欢璺緞
+ */
+ public String getUrl() {
+ String domain = properties.getDomain();
+ String endpoint = properties.getEndpoint();
+ String header = getIsHttps();
+ // 浜戞湇鍔″晢鐩存帴杩斿洖
+ if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
+ return header + (StringUtils.isNotEmpty(domain) ? domain : properties.getBucketName() + "." + endpoint);
+ }
+ // MinIO 鍗曠嫭澶勭悊
+ if (StringUtils.isNotEmpty(domain)) {
+ // 濡傛灉 domain 浠� "https://" 鎴� "http://" 寮�澶�
+ return (domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP)) ?
+ domain + StringUtils.SLASH + properties.getBucketName() : header + domain + StringUtils.SLASH + properties.getBucketName();
+ }
+ return header + endpoint + StringUtils.SLASH + properties.getBucketName();
+ }
+
+ /**
+ * 鐢熸垚涓�涓鍚堢壒瀹氳鍒欑殑銆佸敮涓�鐨勬枃浠惰矾寰勩�傞�氳繃浣跨敤鏃ユ湡銆乁UID銆佸墠缂�鍜屽悗缂�绛夊厓绱犵殑缁勫悎锛岀‘淇濅簡鏂囦欢璺緞鐨勭嫭涓�鏃犱簩鎬�
+ *
+ * @param prefix 鍓嶇紑
+ * @param suffix 鍚庣紑
+ * @return 鏂囦欢璺緞
+ */
+ public String getPath(String prefix, String suffix) {
+ // 鐢熸垚uuid
+ String uuid = IdUtil.fastSimpleUUID();
+ // 鐢熸垚鏃ユ湡璺緞
+ String datePath = DateUtils.datePath();
+ // 鎷兼帴璺緞
+ String path = StringUtils.isNotEmpty(prefix) ?
+ prefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid : datePath + StringUtils.SLASH + uuid;
+ return path + suffix;
+ }
+
+ /**
+ * 绉婚櫎璺緞涓殑鍩虹URL閮ㄥ垎锛屽緱鍒扮浉瀵硅矾寰�
+ *
+ * @param path 瀹屾暣鐨勮矾寰勶紝鍖呮嫭鍩虹URL鍜岀浉瀵硅矾寰�
+ * @return 鍘婚櫎鍩虹URL鍚庣殑鐩稿璺緞
+ */
+ public String removeBaseUrl(String path) {
+ return path.replace(getUrl() + StringUtils.SLASH, "");
+ }
+
+ /**
+ * 鏈嶅姟鍟�
+ */
+ public String getConfigKey() {
+ return configKey;
+ }
+
+ /**
+ * 鑾峰彇鏄惁浣跨敤 HTTPS 鐨勯厤缃紝骞惰繑鍥炵浉搴旂殑鍗忚澶撮儴銆�
+ *
+ * @return 鍗忚澶撮儴锛屾牴鎹槸鍚︿娇鐢� HTTPS 杩斿洖 "https://" 鎴� "http://"
+ */
+ public String getIsHttps() {
+ return OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? Constants.HTTPS : Constants.HTTP;
+ }
+
+ /**
+ * 妫�鏌ラ厤缃槸鍚︾浉鍚�
+ */
+ public boolean checkPropertiesSame(OssProperties properties) {
+ return this.properties.equals(properties);
+ }
+
+ /**
+ * 鑾峰彇褰撳墠妗舵潈闄愮被鍨�
+ *
+ * @return 褰撳墠妗舵潈闄愮被鍨媍ode
+ */
+ public AccessPolicyType getAccessPolicy() {
+ return AccessPolicyType.getByType(properties.getAccessPolicy());
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/WriteOutSubscriber.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/WriteOutSubscriber.java
new file mode 100755
index 0000000..d3a9841
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/WriteOutSubscriber.java
@@ -0,0 +1,15 @@
+package org.dromara.common.oss.core;
+
+import java.io.IOException;
+
+/**
+ * 鍐欏嚭璁㈤槄鍣�
+ *
+ * @author 绉嬭緸鏈瘨
+ */
+@FunctionalInterface
+public interface WriteOutSubscriber<T> {
+
+ void writeTo(T out) throws IOException;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java
new file mode 100755
index 0000000..81a18e6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java
@@ -0,0 +1,30 @@
+package org.dromara.common.oss.entity;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 涓婁紶杩斿洖浣�
+ *
+ * @author Lion Li
+ */
+@Data
+@Builder
+public class UploadResult {
+
+ /**
+ * 鏂囦欢璺緞
+ */
+ private String url;
+
+ /**
+ * 鏂囦欢鍚�
+ */
+ private String filename;
+
+ /**
+ * 宸蹭笂浼犲璞$殑瀹炰綋鏍囪锛堢敤鏉ユ牎楠屾枃浠讹級
+ */
+ private String eTag;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enums/AccessPolicyType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enums/AccessPolicyType.java
new file mode 100755
index 0000000..45b13be
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enums/AccessPolicyType.java
@@ -0,0 +1,56 @@
+package org.dromara.common.oss.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import software.amazon.awssdk.services.s3.model.BucketCannedACL;
+import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
+
+/**
+ * 妗惰闂瓥鐣ラ厤缃�
+ *
+ * @author 闄堣碀
+ */
+@Getter
+@AllArgsConstructor
+public enum AccessPolicyType {
+
+ /**
+ * private
+ */
+ PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE),
+
+ /**
+ * public
+ */
+ PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE),
+
+ /**
+ * custom
+ */
+ CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ);
+
+ /**
+ * 妗� 鏉冮檺绫诲瀷锛堟暟鎹簱鍊硷級
+ */
+ private final String type;
+
+ /**
+ * 妗� 鏉冮檺绫诲瀷
+ */
+ private final BucketCannedACL bucketCannedACL;
+
+ /**
+ * 鏂囦欢瀵硅薄 鏉冮檺绫诲瀷
+ */
+ private final ObjectCannedACL objectCannedACL;
+
+ public static AccessPolicyType getByType(String type) {
+ for (AccessPolicyType value : values()) {
+ if (value.getType().equals(type)) {
+ return value;
+ }
+ }
+ throw new RuntimeException("'type' not found By " + type);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/exception/OssException.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/exception/OssException.java
new file mode 100755
index 0000000..52e9623
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/exception/OssException.java
@@ -0,0 +1,19 @@
+package org.dromara.common.oss.exception;
+
+import java.io.Serial;
+
+/**
+ * OSS寮傚父绫�
+ *
+ * @author Lion Li
+ */
+public class OssException extends RuntimeException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public OssException(String msg) {
+ super(msg);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java
new file mode 100755
index 0000000..3da1ba5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java
@@ -0,0 +1,73 @@
+package org.dromara.common.oss.factory;
+
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.oss.constant.OssConstant;
+import org.dromara.common.oss.core.OssClient;
+import org.dromara.common.oss.exception.OssException;
+import org.dromara.common.oss.properties.OssProperties;
+import org.dromara.common.redis.utils.CacheUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * 鏂囦欢涓婁紶Factory
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class OssFactory {
+
+ private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
+ private static final ReentrantLock LOCK = new ReentrantLock();
+
+ /**
+ * 鑾峰彇榛樿瀹炰緥
+ */
+ public static OssClient instance() {
+ // 鑾峰彇redis 榛樿绫诲瀷
+ String configKey = RedisUtils.getCacheObject(OssConstant.DEFAULT_CONFIG_KEY);
+ if (StringUtils.isEmpty(configKey)) {
+ throw new OssException("鏂囦欢瀛樺偍鏈嶅姟绫诲瀷鏃犳硶鎵惧埌!");
+ }
+ return instance(configKey);
+ }
+
+ /**
+ * 鏍规嵁绫诲瀷鑾峰彇瀹炰緥
+ */
+ public static OssClient instance(String configKey) {
+ String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
+ if (json == null) {
+ throw new OssException("绯荤粺寮傚父, '" + configKey + "'閰嶇疆淇℃伅涓嶅瓨鍦�!");
+ }
+ OssProperties properties = JsonUtils.parseObject(json, OssProperties.class);
+ // 浣跨敤绉熸埛鏍囪瘑閬垮厤澶氫釜绉熸埛鐩稿悓key瀹炰緥瑕嗙洊
+ String key = configKey;
+ if (StringUtils.isNotBlank(properties.getTenantId())) {
+ key = properties.getTenantId() + ":" + configKey;
+ }
+ OssClient client = CLIENT_CACHE.get(key);
+ // 瀹㈡埛绔笉瀛樺湪鎴栭厤缃笉鐩稿悓鍒欓噸鏂版瀯寤�
+ if (client == null || !client.checkPropertiesSame(properties)) {
+ LOCK.lock();
+ try {
+ client = CLIENT_CACHE.get(key);
+ if (client == null || !client.checkPropertiesSame(properties)) {
+ CLIENT_CACHE.put(key, new OssClient(configKey, properties));
+ log.info("鍒涘缓OSS瀹炰緥 key => {}", configKey);
+ return CLIENT_CACHE.get(key);
+ }
+ } finally {
+ LOCK.unlock();
+ }
+ }
+ return client;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/properties/OssProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/properties/OssProperties.java
new file mode 100755
index 0000000..cb37206
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/properties/OssProperties.java
@@ -0,0 +1,63 @@
+package org.dromara.common.oss.properties;
+
+import lombok.Data;
+
+/**
+ * OSS瀵硅薄瀛樺偍 閰嶇疆灞炴��
+ *
+ * @author Lion Li
+ */
+@Data
+public class OssProperties {
+
+ /**
+ * 绉熸埛id
+ */
+ private String tenantId;
+
+ /**
+ * 璁块棶绔欑偣
+ */
+ private String endpoint;
+
+ /**
+ * 鑷畾涔夊煙鍚�
+ */
+ private String domain;
+
+ /**
+ * 鍓嶇紑
+ */
+ private String prefix;
+
+ /**
+ * ACCESS_KEY
+ */
+ private String accessKey;
+
+ /**
+ * SECRET_KEY
+ */
+ private String secretKey;
+
+ /**
+ * 瀛樺偍绌洪棿鍚�
+ */
+ private String bucketName;
+
+ /**
+ * 瀛樺偍鍖哄煙
+ */
+ private String region;
+
+ /**
+ * 鏄惁https锛圷=鏄�,N=鍚︼級
+ */
+ private String isHttps;
+
+ /**
+ * 妗舵潈闄愮被鍨�(0private 1public 2custom)
+ */
+ private String accessPolicy;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/.flattened-pom.xml
new file mode 100644
index 0000000..0ac2882
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/.flattened-pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-ratelimiter</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-ratelimiter 闄愭祦鍔熻兘</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/pom.xml
new file mode 100755
index 0000000..bbde940
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-ratelimiter</artifactId>
+
+ <description>
+ ruoyi-common-ratelimiter 闄愭祦鍔熻兘
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/annotation/RateLimiter.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/annotation/RateLimiter.java
new file mode 100755
index 0000000..79272d4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/annotation/RateLimiter.java
@@ -0,0 +1,47 @@
+package org.dromara.common.ratelimiter.annotation;
+
+import org.dromara.common.ratelimiter.enums.LimitType;
+
+import java.lang.annotation.*;
+
+/**
+ * 闄愭祦娉ㄨВ
+ *
+ * @author Lion Li
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RateLimiter {
+ /**
+ * 闄愭祦key,鏀寔浣跨敤Spring el琛ㄨ揪寮忔潵鍔ㄦ�佽幏鍙栨柟娉曚笂鐨勫弬鏁板��
+ * 鏍煎紡绫讳技浜� #code.id #{#code}
+ */
+ String key() default "";
+
+ /**
+ * 闄愭祦鏃堕棿,鍗曚綅绉�
+ */
+ int time() default 60;
+
+ /**
+ * 闄愭祦娆℃暟
+ */
+ int count() default 100;
+
+ /**
+ * 闄愭祦绫诲瀷
+ */
+ LimitType limitType() default LimitType.DEFAULT;
+
+ /**
+ * 鎻愮ず娑堟伅 鏀寔鍥介檯鍖� 鏍煎紡涓� {code}
+ */
+ String message() default "{rate.limiter.message}";
+
+ /**
+ * 闄愭祦绛栫暐瓒呮椂鏃堕棿 榛樿涓�澶�(绛栫暐瀛樻椿鏃堕棿 浼氭竻闄ゅ凡瀛樺湪鐨勭瓥鐣ユ暟鎹�)
+ */
+ int timeout() default 86400;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java
new file mode 100755
index 0000000..2d6d82e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java
@@ -0,0 +1,112 @@
+package org.dromara.common.ratelimiter.aspectj;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.ratelimiter.annotation.RateLimiter;
+import org.dromara.common.ratelimiter.enums.LimitType;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.redisson.api.RateType;
+import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.context.expression.MethodBasedEvaluationContext;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.ParserContext;
+import org.springframework.expression.common.TemplateParserContext;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+
+import java.lang.reflect.Method;
+
+/**
+ * 闄愭祦澶勭悊
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Aspect
+public class RateLimiterAspect {
+
+ /**
+ * 瀹氫箟spel琛ㄨ揪寮忚В鏋愬櫒
+ */
+ private final ExpressionParser parser = new SpelExpressionParser();
+ /**
+ * 瀹氫箟spel瑙f瀽妯$増
+ */
+ private final ParserContext parserContext = new TemplateParserContext();
+ /**
+ * 鏂规硶鍙傛暟瑙f瀽鍣�
+ */
+ private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
+
+
+ @Before("@annotation(rateLimiter)")
+ public void doBefore(JoinPoint point, RateLimiter rateLimiter) {
+ int time = rateLimiter.time();
+ int count = rateLimiter.count();
+ int timeout = rateLimiter.timeout();
+ try {
+ String combineKey = getCombineKey(rateLimiter, point);
+ RateType rateType = RateType.OVERALL;
+ if (rateLimiter.limitType() == LimitType.CLUSTER) {
+ rateType = RateType.PER_CLIENT;
+ }
+ long number = RedisUtils.rateLimiter(combineKey, rateType, count, time, timeout);
+ if (number == -1) {
+ String message = rateLimiter.message();
+ if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
+ message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
+ }
+ throw new ServiceException(message);
+ }
+ log.info("闄愬埗浠ょ墝 => {}, 鍓╀綑浠ょ墝 => {}, 缂撳瓨key => '{}'", count, number, combineKey);
+ } catch (Exception e) {
+ if (e instanceof ServiceException) {
+ throw e;
+ } else {
+ throw new RuntimeException("鏈嶅姟鍣ㄩ檺娴佸紓甯革紝璇风◢鍊欏啀璇�", e);
+ }
+ }
+ }
+
+ private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
+ String key = rateLimiter.key();
+ // 鍒ゆ柇 key 涓嶄负绌� 鍜� 涓嶆槸琛ㄨ揪寮�
+ if (StringUtils.isNotBlank(key) && StringUtils.containsAny(key, "#")) {
+ MethodSignature signature = (MethodSignature) point.getSignature();
+ Method targetMethod = signature.getMethod();
+ Object[] args = point.getArgs();
+ MethodBasedEvaluationContext context =
+ new MethodBasedEvaluationContext(null, targetMethod, args, pnd);
+ context.setBeanResolver(new BeanFactoryResolver(SpringUtils.getBeanFactory()));
+ Expression expression;
+ if (StringUtils.startsWith(key, parserContext.getExpressionPrefix())
+ && StringUtils.endsWith(key, parserContext.getExpressionSuffix())) {
+ expression = parser.parseExpression(key, parserContext);
+ } else {
+ expression = parser.parseExpression(key);
+ }
+ key = expression.getValue(context, String.class);
+ }
+ StringBuilder stringBuffer = new StringBuilder(GlobalConstants.RATE_LIMIT_KEY);
+ stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":");
+ if (rateLimiter.limitType() == LimitType.IP) {
+ // 鑾峰彇璇锋眰ip
+ stringBuffer.append(ServletUtils.getClientIP()).append(":");
+ } else if (rateLimiter.limitType() == LimitType.CLUSTER) {
+ // 鑾峰彇瀹㈡埛绔疄渚媔d
+ stringBuffer.append(RedisUtils.getClient().getId()).append(":");
+ }
+ return stringBuffer.append(key).toString();
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/config/RateLimiterConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/config/RateLimiterConfig.java
new file mode 100755
index 0000000..4b7e5b7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/config/RateLimiterConfig.java
@@ -0,0 +1,20 @@
+package org.dromara.common.ratelimiter.config;
+
+import org.dromara.common.ratelimiter.aspectj.RateLimiterAspect;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.redis.connection.RedisConfiguration;
+
+/**
+ * @author guangxin
+ * @date 2023/1/18
+ */
+@AutoConfiguration(after = RedisConfiguration.class)
+public class RateLimiterConfig {
+
+ @Bean
+ public RateLimiterAspect rateLimiterAspect() {
+ return new RateLimiterAspect();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/enums/LimitType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/enums/LimitType.java
new file mode 100755
index 0000000..b7f059f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/enums/LimitType.java
@@ -0,0 +1,24 @@
+package org.dromara.common.ratelimiter.enums;
+
+/**
+ * 闄愭祦绫诲瀷
+ *
+ * @author ruoyi
+ */
+
+public enum LimitType {
+ /**
+ * 榛樿绛栫暐鍏ㄥ眬闄愭祦
+ */
+ DEFAULT,
+
+ /**
+ * 鏍规嵁璇锋眰鑰匢P杩涜闄愭祦
+ */
+ IP,
+
+ /**
+ * 瀹炰緥闄愭祦(闆嗙兢澶氬悗绔疄渚�)
+ */
+ CLUSTER
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..3b95432
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.ratelimiter.config.RateLimiterConfig
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/spel-extension.json b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/spel-extension.json
new file mode 100755
index 0000000..b16432b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/spel-extension.json
@@ -0,0 +1,7 @@
+{
+ "org.dromara.common.ratelimiter.annotation.RateLimiter@key": {
+ "method": {
+ "parameters": true
+ }
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/.flattened-pom.xml
new file mode 100644
index 0000000..b07b09a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/.flattened-pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-redis 缂撳瓨鏈嶅姟</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.redisson</groupId>
+ <artifactId>redisson-spring-boot-starter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.github.ben-manes.caffeine</groupId>
+ <artifactId>caffeine</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jsr310</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.fory</groupId>
+ <artifactId>fory-core</artifactId>
+ <version>0.13.1</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/pom.xml
new file mode 100755
index 0000000..576be5e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/pom.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-redis</artifactId>
+
+ <description>
+ ruoyi-common-redis 缂撳瓨鏈嶅姟
+ </description>
+
+ <dependencies>
+ <!-- RuoYi Common Core-->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+
+ <!--redisson-->
+ <dependency>
+ <groupId>org.redisson</groupId>
+ <artifactId>redisson-spring-boot-starter</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.github.ben-manes.caffeine</groupId>
+ <artifactId>caffeine</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jsr310</artifactId>
+ </dependency>
+
+ <!-- redis搴忓垪鍖栨浛浠f柟妗� 姣攋son蹇棤鏁扮殑璺ㄨ瑷�浜岃繘鍒跺簭鍒楀寲 -->
+ <dependency>
+ <groupId>org.apache.fory</groupId>
+ <artifactId>fory-core</artifactId>
+ <version>0.13.1</version>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/CacheConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/CacheConfig.java
new file mode 100755
index 0000000..d57ba4e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/CacheConfig.java
@@ -0,0 +1,45 @@
+package org.dromara.common.redis.config;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.dromara.common.redis.manager.PlusSpringCacheManager;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 缂撳瓨閰嶇疆
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+@EnableCaching
+public class CacheConfig {
+
+ /**
+ * caffeine 鏈湴缂撳瓨澶勭悊鍣�
+ */
+ @Bean
+ public Cache<Object, Object> caffeine() {
+ return Caffeine.newBuilder()
+ // 璁剧疆鏈�鍚庝竴娆″啓鍏ユ垨璁块棶鍚庣粡杩囧浐瀹氭椂闂磋繃鏈�
+ .expireAfterWrite(30, TimeUnit.SECONDS)
+ // 鍒濆鐨勭紦瀛樼┖闂村ぇ灏�
+ .initialCapacity(100)
+ // 缂撳瓨鐨勬渶澶ф潯鏁�
+ .maximumSize(1000)
+ .build();
+ }
+
+ /**
+ * 鑷畾涔夌紦瀛樼鐞嗗櫒 鏁村悎spring-cache
+ */
+ @Bean
+ public CacheManager cacheManager() {
+ return new PlusSpringCacheManager();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java
new file mode 100755
index 0000000..4c39196
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java
@@ -0,0 +1,160 @@
+package org.dromara.common.redis.config;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.redis.config.properties.RedissonProperties;
+import org.dromara.common.redis.handler.KeyPrefixHandler;
+import org.dromara.common.redis.handler.RedisExceptionHandler;
+import org.redisson.client.codec.StringCodec;
+import org.redisson.codec.CompositeCodec;
+import org.redisson.codec.TypedJsonJacksonCodec;
+import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.task.VirtualThreadTaskExecutor;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.TimeZone;
+
+/**
+ * redis閰嶇疆
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@AutoConfiguration
+@EnableConfigurationProperties(RedissonProperties.class)
+public class RedisConfig {
+
+ @Autowired
+ private RedissonProperties redissonProperties;
+
+ @Bean
+ public RedissonAutoConfigurationCustomizer redissonCustomizer() {
+ return config -> {
+ JavaTimeModule javaTimeModule = new JavaTimeModule();
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
+ javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
+ ObjectMapper om = new ObjectMapper();
+ om.registerModule(javaTimeModule);
+ om.setTimeZone(TimeZone.getDefault());
+ om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+ // 鎸囧畾搴忓垪鍖栬緭鍏ョ殑绫诲瀷锛岀被蹇呴』鏄潪final淇グ鐨勩�傚簭鍒楀寲鏃跺皢瀵硅薄鍏ㄧ被鍚嶄竴璧蜂繚瀛樹笅鏉�
+ om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
+ // org.apache.fory.logging.LoggerFactory 鍖呭埆寮曞叆閿欎簡
+ // LoggerFactory.useSlf4jLogging(true);
+ // ForyCodec foryCodec = new ForyCodec();
+ // CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, foryCodec, foryCodec);
+ TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om);
+ // 缁勫悎搴忓垪鍖� key 浣跨敤 String 鍐呭浣跨敤閫氱敤 json 鏍煎紡
+ CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);
+ config.setThreads(redissonProperties.getThreads())
+ .setNettyThreads(redissonProperties.getNettyThreads())
+ // 缂撳瓨 Lua 鑴氭湰 鍑忓皯缃戠粶浼犺緭(redisson 澶ч儴鍒嗙殑鍔熻兘閮芥槸鍩轰簬 Lua 鑴氭湰瀹炵幇)
+ .setUseScriptCache(true)
+ .setCodec(codec);
+ if (SpringUtils.isVirtual()) {
+ config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-"));
+ }
+ RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
+ if (ObjectUtil.isNotNull(singleServerConfig)) {
+ // 浣跨敤鍗曟満妯″紡
+ config.useSingleServer()
+ //璁剧疆redis key鍓嶇紑
+ .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
+ .setTimeout(singleServerConfig.getTimeout())
+ .setClientName(singleServerConfig.getClientName())
+ .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
+ .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
+ .setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
+ .setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
+ }
+ // 闆嗙兢閰嶇疆鏂瑰紡 鍙傝�冧笅鏂规敞閲�
+ RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
+ if (ObjectUtil.isNotNull(clusterServersConfig)) {
+ config.useClusterServers()
+ //璁剧疆redis key鍓嶇紑
+ .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
+ .setTimeout(clusterServersConfig.getTimeout())
+ .setClientName(clusterServersConfig.getClientName())
+ .setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
+ .setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
+ .setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
+ .setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
+ .setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
+ .setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
+ .setReadMode(clusterServersConfig.getReadMode())
+ .setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
+ }
+ log.info("鍒濆鍖� redis 閰嶇疆");
+ };
+ }
+
+ /**
+ * 寮傚父澶勭悊鍣�
+ */
+ @Bean
+ public RedisExceptionHandler redisExceptionHandler() {
+ return new RedisExceptionHandler();
+ }
+
+ /**
+ * redis闆嗙兢閰嶇疆 yml
+ *
+ * --- # redis 闆嗙兢閰嶇疆(鍗曟満涓庨泦缇ゅ彧鑳藉紑鍚竴涓彟涓�涓渶瑕佹敞閲婃帀)
+ * spring.data:
+ * redis:
+ * cluster:
+ * nodes:
+ * - 192.168.0.100:6379
+ * - 192.168.0.101:6379
+ * - 192.168.0.102:6379
+ * # 瀵嗙爜
+ * password:
+ * # 杩炴帴瓒呮椂鏃堕棿
+ * timeout: 10s
+ * # 鏄惁寮�鍚痵sl
+ * ssl.enabled: false
+ *
+ * redisson:
+ * # 绾跨▼姹犳暟閲�
+ * threads: 16
+ * # Netty绾跨▼姹犳暟閲�
+ * nettyThreads: 32
+ * # 闆嗙兢閰嶇疆
+ * clusterServersConfig:
+ * # 瀹㈡埛绔悕绉�
+ * clientName: ${ruoyi.name}
+ * # master鏈�灏忕┖闂茶繛鎺ユ暟
+ * masterConnectionMinimumIdleSize: 32
+ * # master杩炴帴姹犲ぇ灏�
+ * masterConnectionPoolSize: 64
+ * # slave鏈�灏忕┖闂茶繛鎺ユ暟
+ * slaveConnectionMinimumIdleSize: 32
+ * # slave杩炴帴姹犲ぇ灏�
+ * slaveConnectionPoolSize: 64
+ * # 杩炴帴绌洪棽瓒呮椂锛屽崟浣嶏細姣
+ * idleConnectionTimeout: 10000
+ * # 鍛戒护绛夊緟瓒呮椂锛屽崟浣嶏細姣
+ * timeout: 3000
+ * # 鍙戝竷鍜岃闃呰繛鎺ユ睜澶у皬
+ * subscriptionConnectionPoolSize: 50
+ * # 璇诲彇妯″紡
+ * readMode: "SLAVE"
+ * # 璁㈤槄妯″紡
+ * subscriptionMode: "MASTER"
+ */
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/properties/RedissonProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/properties/RedissonProperties.java
new file mode 100755
index 0000000..ebec786
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/properties/RedissonProperties.java
@@ -0,0 +1,135 @@
+package org.dromara.common.redis.config.properties;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.redisson.config.ReadMode;
+import org.redisson.config.SubscriptionMode;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Redisson 閰嶇疆灞炴��
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "redisson")
+public class RedissonProperties {
+
+ /**
+ * redis缂撳瓨key鍓嶇紑
+ */
+ private String keyPrefix;
+
+ /**
+ * 绾跨▼姹犳暟閲�,榛樿鍊� = 褰撳墠澶勭悊鏍告暟閲� * 2
+ */
+ private int threads;
+
+ /**
+ * Netty绾跨▼姹犳暟閲�,榛樿鍊� = 褰撳墠澶勭悊鏍告暟閲� * 2
+ */
+ private int nettyThreads;
+
+ /**
+ * 鍗曟満鏈嶅姟閰嶇疆
+ */
+ private SingleServerConfig singleServerConfig;
+
+ /**
+ * 闆嗙兢鏈嶅姟閰嶇疆
+ */
+ private ClusterServersConfig clusterServersConfig;
+
+ @Data
+ @NoArgsConstructor
+ public static class SingleServerConfig {
+
+ /**
+ * 瀹㈡埛绔悕绉�
+ */
+ private String clientName;
+
+ /**
+ * 鏈�灏忕┖闂茶繛鎺ユ暟
+ */
+ private int connectionMinimumIdleSize;
+
+ /**
+ * 杩炴帴姹犲ぇ灏�
+ */
+ private int connectionPoolSize;
+
+ /**
+ * 杩炴帴绌洪棽瓒呮椂锛屽崟浣嶏細姣
+ */
+ private int idleConnectionTimeout;
+
+ /**
+ * 鍛戒护绛夊緟瓒呮椂锛屽崟浣嶏細姣
+ */
+ private int timeout;
+
+ /**
+ * 鍙戝竷鍜岃闃呰繛鎺ユ睜澶у皬
+ */
+ private int subscriptionConnectionPoolSize;
+
+ }
+
+ @Data
+ @NoArgsConstructor
+ public static class ClusterServersConfig {
+
+ /**
+ * 瀹㈡埛绔悕绉�
+ */
+ private String clientName;
+
+ /**
+ * master鏈�灏忕┖闂茶繛鎺ユ暟
+ */
+ private int masterConnectionMinimumIdleSize;
+
+ /**
+ * master杩炴帴姹犲ぇ灏�
+ */
+ private int masterConnectionPoolSize;
+
+ /**
+ * slave鏈�灏忕┖闂茶繛鎺ユ暟
+ */
+ private int slaveConnectionMinimumIdleSize;
+
+ /**
+ * slave杩炴帴姹犲ぇ灏�
+ */
+ private int slaveConnectionPoolSize;
+
+ /**
+ * 杩炴帴绌洪棽瓒呮椂锛屽崟浣嶏細姣
+ */
+ private int idleConnectionTimeout;
+
+ /**
+ * 鍛戒护绛夊緟瓒呮椂锛屽崟浣嶏細姣
+ */
+ private int timeout;
+
+ /**
+ * 鍙戝竷鍜岃闃呰繛鎺ユ睜澶у皬
+ */
+ private int subscriptionConnectionPoolSize;
+
+ /**
+ * 璇诲彇妯″紡
+ */
+ private ReadMode readMode;
+
+ /**
+ * 璁㈤槄妯″紡
+ */
+ private SubscriptionMode subscriptionMode;
+
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/KeyPrefixHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/KeyPrefixHandler.java
new file mode 100755
index 0000000..3bf3e34
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/KeyPrefixHandler.java
@@ -0,0 +1,50 @@
+package org.dromara.common.redis.handler;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.redisson.api.NameMapper;
+
+/**
+ * redis缂撳瓨key鍓嶇紑澶勭悊
+ *
+ * @author ye
+ * @date 2022/7/14 17:44
+ * @since 4.3.0
+ */
+public class KeyPrefixHandler implements NameMapper {
+
+ private final String keyPrefix;
+
+ public KeyPrefixHandler(String keyPrefix) {
+ //鍓嶇紑涓虹┖ 鍒欒繑鍥炵┖鍓嶇紑
+ this.keyPrefix = StringUtils.isBlank(keyPrefix) ? "" : keyPrefix + ":";
+ }
+
+ /**
+ * 澧炲姞鍓嶇紑
+ */
+ @Override
+ public String map(String name) {
+ if (StringUtils.isBlank(name)) {
+ return null;
+ }
+ if (StringUtils.isNotBlank(keyPrefix) && !name.startsWith(keyPrefix)) {
+ return keyPrefix + name;
+ }
+ return name;
+ }
+
+ /**
+ * 鍘婚櫎鍓嶇紑
+ */
+ @Override
+ public String unmap(String name) {
+ if (StringUtils.isBlank(name)) {
+ return null;
+ }
+ if (StringUtils.isNotBlank(keyPrefix) && name.startsWith(keyPrefix)) {
+ return name.substring(keyPrefix.length());
+ }
+ return name;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/RedisExceptionHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/RedisExceptionHandler.java
new file mode 100755
index 0000000..5e904f3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/RedisExceptionHandler.java
@@ -0,0 +1,30 @@
+package org.dromara.common.redis.handler;
+
+import cn.hutool.http.HttpStatus;
+import com.baomidou.lock.exception.LockFailureException;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * Redis寮傚父澶勭悊鍣�
+ *
+ * @author AprilWind
+ */
+@Slf4j
+@RestControllerAdvice
+public class RedisExceptionHandler {
+
+ /**
+ * 鍒嗗竷寮忛攣Lock4j寮傚父
+ */
+ @ExceptionHandler(LockFailureException.class)
+ public R<Void> handleLockFailureException(LockFailureException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("鑾峰彇閿佸け璐ヤ簡'{}',鍙戠敓Lock4j寮傚父.", requestURI, e);
+ return R.fail(HttpStatus.HTTP_UNAVAILABLE, "涓氬姟澶勭悊涓紝璇风◢鍚庡啀璇�...");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/CaffeineCacheDecorator.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/CaffeineCacheDecorator.java
new file mode 100755
index 0000000..8662c53
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/CaffeineCacheDecorator.java
@@ -0,0 +1,97 @@
+package org.dromara.common.redis.manager;
+
+import org.dromara.common.core.utils.SpringUtils;
+import org.springframework.cache.Cache;
+
+import java.util.concurrent.Callable;
+
+/**
+ * Cache 瑁呴グ鍣ㄦā寮�(鐢ㄤ簬鎵╁睍 Caffeine 涓�绾х紦瀛�)
+ *
+ * @author LionLi
+ */
+public class CaffeineCacheDecorator implements Cache {
+
+ private static final com.github.benmanes.caffeine.cache.Cache<Object, Object>
+ CAFFEINE = SpringUtils.getBean("caffeine");
+
+ private final String name;
+ private final Cache cache;
+
+ public CaffeineCacheDecorator(String name, Cache cache) {
+ this.name = name;
+ this.cache = cache;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Object getNativeCache() {
+ return cache.getNativeCache();
+ }
+
+ public String getUniqueKey(Object key) {
+ return name + ":" + key;
+ }
+
+ @Override
+ public ValueWrapper get(Object key) {
+ Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key));
+ return (ValueWrapper) o;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T get(Object key, Class<T> type) {
+ Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, type));
+ return (T) o;
+ }
+
+ @Override
+ public void put(Object key, Object value) {
+ CAFFEINE.invalidate(getUniqueKey(key));
+ cache.put(key, value);
+ }
+
+ @Override
+ public ValueWrapper putIfAbsent(Object key, Object value) {
+ CAFFEINE.invalidate(getUniqueKey(key));
+ return cache.putIfAbsent(key, value);
+ }
+
+ @Override
+ public void evict(Object key) {
+ evictIfPresent(key);
+ }
+
+ @Override
+ public boolean evictIfPresent(Object key) {
+ boolean b = cache.evictIfPresent(key);
+ if (b) {
+ CAFFEINE.invalidate(getUniqueKey(key));
+ }
+ return b;
+ }
+
+ @Override
+ public void clear() {
+ CAFFEINE.invalidateAll();
+ cache.clear();
+ }
+
+ @Override
+ public boolean invalidate() {
+ return cache.invalidate();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T get(Object key, Callable<T> valueLoader) {
+ Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, valueLoader));
+ return (T) o;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java
new file mode 100755
index 0000000..8428ef7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java
@@ -0,0 +1,202 @@
+/**
+ * Copyright (c) 2013-2021 Nikita Koksharov
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.dromara.common.redis.manager;
+
+import org.dromara.common.redis.utils.RedisUtils;
+import org.redisson.api.RMap;
+import org.redisson.api.RMapCache;
+import org.redisson.spring.cache.CacheConfig;
+import org.redisson.spring.cache.RedissonCache;
+import org.springframework.boot.convert.DurationStyle;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * A {@link org.springframework.cache.CacheManager} implementation
+ * backed by Redisson instance.
+ * <p>
+ * 淇敼 RedissonSpringCacheManager 婧愮爜
+ * 閲嶅啓 cacheName 澶勭悊鏂规硶 鏀寔澶氬弬鏁�
+ *
+ * @author Nikita Koksharov
+ *
+ */
+@SuppressWarnings("unchecked")
+public class PlusSpringCacheManager implements CacheManager {
+
+ private boolean dynamic = true;
+
+ private boolean allowNullValues = true;
+
+ private boolean transactionAware = true;
+
+ Map<String, CacheConfig> configMap = new ConcurrentHashMap<>();
+ ConcurrentMap<String, Cache> instanceMap = new ConcurrentHashMap<>();
+
+ /**
+ * Creates CacheManager supplied by Redisson instance
+ */
+ public PlusSpringCacheManager() {
+ }
+
+
+ /**
+ * Defines possibility of storing {@code null} values.
+ * <p>
+ * Default is <code>true</code>
+ *
+ * @param allowNullValues stores if <code>true</code>
+ */
+ public void setAllowNullValues(boolean allowNullValues) {
+ this.allowNullValues = allowNullValues;
+ }
+
+ /**
+ * Defines if cache aware of Spring-managed transactions.
+ * If {@code true} put/evict operations are executed only for successful transaction in after-commit phase.
+ * <p>
+ * Default is <code>false</code>
+ *
+ * @param transactionAware cache is transaction aware if <code>true</code>
+ */
+ public void setTransactionAware(boolean transactionAware) {
+ this.transactionAware = transactionAware;
+ }
+
+ /**
+ * Defines 'fixed' cache names.
+ * A new cache instance will not be created in dynamic for non-defined names.
+ * <p>
+ * `null` parameter setups dynamic mode
+ *
+ * @param names of caches
+ */
+ public void setCacheNames(Collection<String> names) {
+ if (names != null) {
+ for (String name : names) {
+ getCache(name);
+ }
+ dynamic = false;
+ } else {
+ dynamic = true;
+ }
+ }
+
+ /**
+ * Set cache config mapped by cache name
+ *
+ * @param config object
+ */
+ public void setConfig(Map<String, ? extends CacheConfig> config) {
+ this.configMap = (Map<String, CacheConfig>) config;
+ }
+
+ protected CacheConfig createDefaultConfig() {
+ return new CacheConfig();
+ }
+
+ @Override
+ public Cache getCache(String name) {
+ // 閲嶅啓 cacheName 鏀寔澶氬弬鏁�
+ String[] array = StringUtils.delimitedListToStringArray(name, "#");
+ name = array[0];
+
+ Cache cache = instanceMap.get(name);
+ if (cache != null) {
+ return cache;
+ }
+ if (!dynamic) {
+ return cache;
+ }
+
+ CacheConfig config = configMap.get(name);
+ if (config == null) {
+ config = createDefaultConfig();
+ configMap.put(name, config);
+ }
+
+ if (array.length > 1) {
+ config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
+ }
+ if (array.length > 2) {
+ config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis());
+ }
+ if (array.length > 3) {
+ config.setMaxSize(Integer.parseInt(array[3]));
+ }
+ int local = 1;
+ if (array.length > 4) {
+ local = Integer.parseInt(array[4]);
+ }
+
+ if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
+ return createMap(name, config, local);
+ }
+
+ return createMapCache(name, config, local);
+ }
+
+ private Cache createMap(String name, CacheConfig config, int local) {
+ RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
+
+ Cache cache = new RedissonCache(map, allowNullValues);
+ if (local == 1) {
+ cache = new CaffeineCacheDecorator(name, cache);
+ }
+ if (transactionAware) {
+ cache = new TransactionAwareCacheDecorator(cache);
+ }
+ Cache oldCache = instanceMap.putIfAbsent(name, cache);
+ if (oldCache != null) {
+ cache = oldCache;
+ }
+ return cache;
+ }
+
+ private Cache createMapCache(String name, CacheConfig config, int local) {
+ RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
+
+ Cache cache = new RedissonCache(map, config, allowNullValues);
+ if (local == 1) {
+ cache = new CaffeineCacheDecorator(name, cache);
+ }
+ if (transactionAware) {
+ cache = new TransactionAwareCacheDecorator(cache);
+ }
+ Cache oldCache = instanceMap.putIfAbsent(name, cache);
+ if (oldCache != null) {
+ cache = oldCache;
+ } else {
+ map.setMaxSize(config.getMaxSize());
+ }
+ return cache;
+ }
+
+ @Override
+ public Collection<String> getCacheNames() {
+ return Collections.unmodifiableSet(configMap.keySet());
+ }
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java
new file mode 100755
index 0000000..865ffa5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java
@@ -0,0 +1,61 @@
+package org.dromara.common.redis.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+
+/**
+ * 缂撳瓨鎿嶄綔宸ュ叿绫�
+ *
+ * @author Michelle.Chung
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@SuppressWarnings(value = {"unchecked"})
+public class CacheUtils {
+
+ private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class);
+
+ /**
+ * 鑾峰彇缂撳瓨鍊�
+ *
+ * @param cacheNames 缂撳瓨缁勫悕绉�
+ * @param key 缂撳瓨key
+ */
+ public static <T> T get(String cacheNames, Object key) {
+ Cache.ValueWrapper wrapper = CACHE_MANAGER.getCache(cacheNames).get(key);
+ return wrapper != null ? (T) wrapper.get() : null;
+ }
+
+ /**
+ * 淇濆瓨缂撳瓨鍊�
+ *
+ * @param cacheNames 缂撳瓨缁勫悕绉�
+ * @param key 缂撳瓨key
+ * @param value 缂撳瓨鍊�
+ */
+ public static void put(String cacheNames, Object key, Object value) {
+ CACHE_MANAGER.getCache(cacheNames).put(key, value);
+ }
+
+ /**
+ * 鍒犻櫎缂撳瓨鍊�
+ *
+ * @param cacheNames 缂撳瓨缁勫悕绉�
+ * @param key 缂撳瓨key
+ */
+ public static void evict(String cacheNames, Object key) {
+ CACHE_MANAGER.getCache(cacheNames).evict(key);
+ }
+
+ /**
+ * 娓呯┖缂撳瓨鍊�
+ *
+ * @param cacheNames 缂撳瓨缁勫悕绉�
+ */
+ public static void clear(String cacheNames) {
+ CACHE_MANAGER.getCache(cacheNames).clear();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/QueueUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/QueueUtils.java
new file mode 100755
index 0000000..5b1c7f7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/QueueUtils.java
@@ -0,0 +1,239 @@
+package org.dromara.common.redis.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+import org.redisson.api.*;
+
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+
+/**
+ * 鍒嗗竷寮忛槦鍒楀伐鍏�
+ * 杞婚噺绾ч槦鍒� 閲嶉噺绾ф暟鎹噺 璇蜂娇鐢� MQ
+ * 瑕佹眰 redis 5.X 浠ヤ笂
+ *
+ * @author Lion Li
+ * @version 3.6.0 鏂板
+ * @deprecated redisson 鏂扮増鏈凡缁忓皢闃熷垪鍔熻兘鏍囪鍒犻櫎 涓�浜涙妧鏈棶棰樻棤娉曡В鍐� 寤鸿鎼缓MQ浣跨敤
+ */
+@Deprecated
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class QueueUtils {
+
+ private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
+
+
+ /**
+ * 鑾峰彇瀹㈡埛绔疄渚�
+ */
+ public static RedissonClient getClient() {
+ return CLIENT;
+ }
+
+ /**
+ * 娣诲姞鏅�氶槦鍒楁暟鎹�
+ *
+ * @param queueName 闃熷垪鍚�
+ * @param data 鏁版嵁
+ */
+ public static <T> boolean addQueueObject(String queueName, T data) {
+ RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
+ return queue.offer(data);
+ }
+
+ /**
+ * 閫氱敤鑾峰彇涓�涓槦鍒楁暟鎹� 娌℃湁鏁版嵁杩斿洖 null(涓嶆敮鎸佸欢杩熼槦鍒�)
+ *
+ * @param queueName 闃熷垪鍚�
+ */
+ public static <T> T getQueueObject(String queueName) {
+ RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
+ return queue.poll();
+ }
+
+ /**
+ * 閫氱敤鍒犻櫎闃熷垪鏁版嵁(涓嶆敮鎸佸欢杩熼槦鍒�)
+ */
+ public static <T> boolean removeQueueObject(String queueName, T data) {
+ RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
+ return queue.remove(data);
+ }
+
+ /**
+ * 閫氱敤閿�姣侀槦鍒� 鎵�鏈夐樆濉炵洃鍚� 鎶ラ敊(涓嶆敮鎸佸欢杩熼槦鍒�)
+ */
+ public static <T> boolean destroyQueue(String queueName) {
+ RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
+ return queue.delete();
+ }
+
+ /**
+ * 娣诲姞寤惰繜闃熷垪鏁版嵁 榛樿姣
+ *
+ * @param queueName 闃熷垪鍚�
+ * @param data 鏁版嵁
+ * @param time 寤惰繜鏃堕棿
+ */
+ public static <T> void addDelayedQueueObject(String queueName, T data, long time) {
+ addDelayedQueueObject(queueName, data, time, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * 娣诲姞寤惰繜闃熷垪鏁版嵁
+ *
+ * @param queueName 闃熷垪鍚�
+ * @param data 鏁版嵁
+ * @param time 寤惰繜鏃堕棿
+ * @param timeUnit 鍗曚綅
+ */
+ public static <T> void addDelayedQueueObject(String queueName, T data, long time, TimeUnit timeUnit) {
+ RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
+ RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
+ delayedQueue.offer(data, time, timeUnit);
+ }
+
+ /**
+ * 鑾峰彇涓�涓欢杩熼槦鍒楁暟鎹� 娌℃湁鏁版嵁杩斿洖 null
+ *
+ * @param queueName 闃熷垪鍚�
+ */
+ public static <T> T getDelayedQueueObject(String queueName) {
+ RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
+ RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
+ return delayedQueue.poll();
+ }
+
+ /**
+ * 鍒犻櫎寤惰繜闃熷垪鏁版嵁
+ */
+ public static <T> boolean removeDelayedQueueObject(String queueName, T data) {
+ RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
+ RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
+ return delayedQueue.remove(data);
+ }
+
+ /**
+ * 閿�姣佸欢杩熼槦鍒� 鎵�鏈夐樆濉炵洃鍚� 鎶ラ敊
+ */
+ public static <T> void destroyDelayedQueue(String queueName) {
+ RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
+ RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
+ delayedQueue.destroy();
+ }
+
+ /**
+ * 娣诲姞浼樺厛闃熷垪鏁版嵁
+ *
+ * @param queueName 闃熷垪鍚�
+ * @param data 鏁版嵁
+ */
+ public static <T> boolean addPriorityQueueObject(String queueName, T data) {
+ RPriorityBlockingQueue<T> priorityBlockingQueue = CLIENT.getPriorityBlockingQueue(queueName);
+ return priorityBlockingQueue.offer(data);
+ }
+
+ /**
+ * 浼樺厛闃熷垪鑾峰彇涓�涓槦鍒楁暟鎹� 娌℃湁鏁版嵁杩斿洖 null(涓嶆敮鎸佸欢杩熼槦鍒�)
+ *
+ * @param queueName 闃熷垪鍚�
+ */
+ public static <T> T getPriorityQueueObject(String queueName) {
+ RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
+ return queue.poll();
+ }
+
+ /**
+ * 浼樺厛闃熷垪鍒犻櫎闃熷垪鏁版嵁(涓嶆敮鎸佸欢杩熼槦鍒�)
+ */
+ public static <T> boolean removePriorityQueueObject(String queueName, T data) {
+ RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
+ return queue.remove(data);
+ }
+
+ /**
+ * 浼樺厛闃熷垪閿�姣侀槦鍒� 鎵�鏈夐樆濉炵洃鍚� 鎶ラ敊(涓嶆敮鎸佸欢杩熼槦鍒�)
+ */
+ public static <T> boolean destroyPriorityQueue(String queueName) {
+ RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
+ return queue.delete();
+ }
+
+ /**
+ * 灏濊瘯璁剧疆 鏈夌晫闃熷垪 瀹归噺 鐢ㄤ簬闄愬埗鏁伴噺
+ *
+ * @param queueName 闃熷垪鍚�
+ * @param capacity 瀹归噺
+ */
+ public static <T> boolean trySetBoundedQueueCapacity(String queueName, int capacity) {
+ RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
+ return boundedBlockingQueue.trySetCapacity(capacity);
+ }
+
+ /**
+ * 灏濊瘯璁剧疆 鏈夌晫闃熷垪 瀹归噺 鐢ㄤ簬闄愬埗鏁伴噺
+ *
+ * @param queueName 闃熷垪鍚�
+ * @param capacity 瀹归噺
+ * @param destroy 鏄惁閿�姣�
+ */
+ public static <T> boolean trySetBoundedQueueCapacity(String queueName, int capacity, boolean destroy) {
+ RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
+ if (destroy) {
+ boundedBlockingQueue.delete();
+ }
+ return boundedBlockingQueue.trySetCapacity(capacity);
+ }
+
+ /**
+ * 娣诲姞鏈夌晫闃熷垪鏁版嵁
+ *
+ * @param queueName 闃熷垪鍚�
+ * @param data 鏁版嵁
+ * @return 娣诲姞鎴愬姛 true 宸茶揪鍒扮晫闄� false
+ */
+ public static <T> boolean addBoundedQueueObject(String queueName, T data) {
+ RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
+ return boundedBlockingQueue.offer(data);
+ }
+
+ /**
+ * 鏈夌晫闃熷垪鑾峰彇涓�涓槦鍒楁暟鎹� 娌℃湁鏁版嵁杩斿洖 null(涓嶆敮鎸佸欢杩熼槦鍒�)
+ *
+ * @param queueName 闃熷垪鍚�
+ */
+ public static <T> T getBoundedQueueObject(String queueName) {
+ RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
+ return queue.poll();
+ }
+
+ /**
+ * 鏈夌晫闃熷垪鍒犻櫎闃熷垪鏁版嵁(涓嶆敮鎸佸欢杩熼槦鍒�)
+ */
+ public static <T> boolean removeBoundedQueueObject(String queueName, T data) {
+ RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
+ return queue.remove(data);
+ }
+
+ /**
+ * 鏈夌晫闃熷垪閿�姣侀槦鍒� 鎵�鏈夐樆濉炵洃鍚� 鎶ラ敊(涓嶆敮鎸佸欢杩熼槦鍒�)
+ */
+ public static <T> boolean destroyBoundedQueue(String queueName) {
+ RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
+ return queue.delete();
+ }
+
+ /**
+ * 璁㈤槄闃诲闃熷垪(鍙闃呮墍鏈夊疄鐜扮被 渚嬪: 寤惰繜 浼樺厛 鏈夌晫 绛�)
+ */
+ public static <T> void subscribeBlockingQueue(String queueName, Function<T, CompletionStage<Void>> consumer, boolean isDelayed) {
+ RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
+ if (isDelayed) {
+ // 璁㈤槄寤惰繜闃熷垪
+ CLIENT.getDelayedQueue(queue);
+ }
+ queue.subscribeOnElements(consumer);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java
new file mode 100755
index 0000000..c433bff
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java
@@ -0,0 +1,581 @@
+package org.dromara.common.redis.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+import org.redisson.api.*;
+import org.redisson.api.options.KeysScanOptions;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * redis 宸ュ叿绫�
+ *
+ * @author Lion Li
+ * @version 3.1.0 鏂板
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@SuppressWarnings(value = {"unchecked", "rawtypes"})
+public class RedisUtils {
+
+ private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
+
+ /**
+ * 闄愭祦
+ *
+ * @param key 闄愭祦key
+ * @param rateType 闄愭祦绫诲瀷
+ * @param rate 閫熺巼
+ * @param rateInterval 閫熺巼闂撮殧
+ * @return -1 琛ㄧず澶辫触
+ */
+ public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
+ return rateLimiter(key, rateType, rate, rateInterval, 0);
+ }
+
+ /**
+ * 闄愭祦
+ *
+ * @param key 闄愭祦key
+ * @param rateType 闄愭祦绫诲瀷
+ * @param rate 閫熺巼
+ * @param rateInterval 閫熺巼闂撮殧
+ * @param timeout 瓒呮椂鏃堕棿
+ * @return -1 琛ㄧず澶辫触
+ */
+ public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval, int timeout) {
+ RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
+ rateLimiter.trySetRate(rateType, rate, Duration.ofSeconds(rateInterval), Duration.ofSeconds(timeout));
+ if (rateLimiter.tryAcquire()) {
+ return rateLimiter.availablePermits();
+ } else {
+ return -1L;
+ }
+ }
+
+ /**
+ * 鑾峰彇瀹㈡埛绔疄渚�
+ */
+ public static RedissonClient getClient() {
+ return CLIENT;
+ }
+
+ /**
+ * 鍙戝竷閫氶亾娑堟伅
+ *
+ * @param channelKey 閫氶亾key
+ * @param msg 鍙戦�佹暟鎹�
+ * @param consumer 鑷畾涔夊鐞�
+ */
+ public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
+ RTopic topic = CLIENT.getTopic(channelKey);
+ topic.publish(msg);
+ consumer.accept(msg);
+ }
+
+ /**
+ * 鍙戝竷娑堟伅鍒版寚瀹氱殑棰戦亾
+ *
+ * @param channelKey 閫氶亾key
+ * @param msg 鍙戦�佹暟鎹�
+ */
+ public static <T> void publish(String channelKey, T msg) {
+ RTopic topic = CLIENT.getTopic(channelKey);
+ topic.publish(msg);
+ }
+
+ /**
+ * 璁㈤槄閫氶亾鎺ユ敹娑堟伅
+ *
+ * @param channelKey 閫氶亾key
+ * @param clazz 娑堟伅绫诲瀷
+ * @param consumer 鑷畾涔夊鐞�
+ */
+ public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
+ RTopic topic = CLIENT.getTopic(channelKey);
+ topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
+ }
+
+ /**
+ * 缂撳瓨鍩烘湰鐨勫璞★紝Integer銆丼tring銆佸疄浣撶被绛�
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param value 缂撳瓨鐨勫��
+ */
+ public static <T> void setCacheObject(final String key, final T value) {
+ setCacheObject(key, value, false);
+ }
+
+ /**
+ * 缂撳瓨鍩烘湰鐨勫璞★紝淇濈暀褰撳墠瀵硅薄 TTL 鏈夋晥鏈�
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param value 缂撳瓨鐨勫��
+ * @param isSaveTtl 鏄惁淇濈暀TTL鏈夋晥鏈�(渚嬪: set涔嬪墠ttl鍓╀綑90 set涔嬪悗杩樻槸涓�90)
+ * @since Redis 6.X 浠ヤ笂浣跨敤 setAndKeepTTL 鍏煎 5.X 鏂规
+ */
+ public static <T> void setCacheObject(final String key, final T value, final boolean isSaveTtl) {
+ RBucket<T> bucket = CLIENT.getBucket(key);
+ if (isSaveTtl) {
+ try {
+ bucket.setAndKeepTTL(value);
+ } catch (Exception e) {
+ long timeToLive = bucket.remainTimeToLive();
+ if (timeToLive == -1) {
+ bucket.set(value);
+ } else {
+ bucket.set(value, Duration.ofMillis(timeToLive));
+ }
+ }
+ } else {
+ bucket.set(value);
+ }
+ }
+
+ /**
+ * 缂撳瓨鍩烘湰鐨勫璞★紝Integer銆丼tring銆佸疄浣撶被绛�
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param value 缂撳瓨鐨勫��
+ * @param duration 鏃堕棿
+ */
+ public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
+ RBucket<T> bucket = CLIENT.getBucket(key);
+ bucket.set(value, duration);
+ }
+
+ /**
+ * 濡傛灉涓嶅瓨鍦ㄥ垯璁剧疆 骞惰繑鍥� true 濡傛灉瀛樺湪鍒欒繑鍥� false
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param value 缂撳瓨鐨勫��
+ * @return set鎴愬姛鎴栧け璐�
+ */
+ public static <T> boolean setObjectIfAbsent(final String key, final T value, final Duration duration) {
+ RBucket<T> bucket = CLIENT.getBucket(key);
+ return bucket.setIfAbsent(value, duration);
+ }
+
+ /**
+ * 濡傛灉瀛樺湪鍒欒缃� 骞惰繑鍥� true 濡傛灉瀛樺湪鍒欒繑鍥� false
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param value 缂撳瓨鐨勫��
+ * @return set鎴愬姛鎴栧け璐�
+ */
+ public static <T> boolean setObjectIfExists(final String key, final T value, final Duration duration) {
+ RBucket<T> bucket = CLIENT.getBucket(key);
+ return bucket.setIfExists(value, duration);
+ }
+
+ /**
+ * 娉ㄥ唽瀵硅薄鐩戝惉鍣�
+ * <p>
+ * key 鐩戝惉鍣ㄩ渶寮�鍚� `notify-keyspace-events` 绛� redis 鐩稿叧閰嶇疆
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param listener 鐩戝惉鍣ㄩ厤缃�
+ */
+ public static <T> void addObjectListener(final String key, final ObjectListener listener) {
+ RBucket<T> result = CLIENT.getBucket(key);
+ result.addListener(listener);
+ }
+
+ /**
+ * 璁剧疆鏈夋晥鏃堕棿
+ *
+ * @param key Redis閿�
+ * @param timeout 瓒呮椂鏃堕棿
+ * @return true=璁剧疆鎴愬姛锛沠alse=璁剧疆澶辫触
+ */
+ public static boolean expire(final String key, final long timeout) {
+ return expire(key, Duration.ofSeconds(timeout));
+ }
+
+ /**
+ * 璁剧疆鏈夋晥鏃堕棿
+ *
+ * @param key Redis閿�
+ * @param duration 瓒呮椂鏃堕棿
+ * @return true=璁剧疆鎴愬姛锛沠alse=璁剧疆澶辫触
+ */
+ public static boolean expire(final String key, final Duration duration) {
+ RBucket rBucket = CLIENT.getBucket(key);
+ return rBucket.expire(duration);
+ }
+
+ /**
+ * 鑾峰緱缂撳瓨鐨勫熀鏈璞°��
+ *
+ * @param key 缂撳瓨閿��
+ * @return 缂撳瓨閿�煎搴旂殑鏁版嵁
+ */
+ public static <T> T getCacheObject(final String key) {
+ RBucket<T> rBucket = CLIENT.getBucket(key);
+ return rBucket.get();
+ }
+
+ /**
+ * 鑾峰緱key鍓╀綑瀛樻椿鏃堕棿
+ *
+ * @param key 缂撳瓨閿��
+ * @return 鍓╀綑瀛樻椿鏃堕棿
+ */
+ public static <T> long getTimeToLive(final String key) {
+ RBucket<T> rBucket = CLIENT.getBucket(key);
+ return rBucket.remainTimeToLive();
+ }
+
+ /**
+ * 鍒犻櫎鍗曚釜瀵硅薄
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ */
+ public static boolean deleteObject(final String key) {
+ return CLIENT.getBucket(key).delete();
+ }
+
+ /**
+ * 鍒犻櫎闆嗗悎瀵硅薄
+ *
+ * @param collection 澶氫釜瀵硅薄
+ */
+ public static void deleteObject(final Collection collection) {
+ RBatch batch = CLIENT.createBatch();
+ collection.forEach(t -> {
+ batch.getBucket(t.toString()).deleteAsync();
+ });
+ batch.execute();
+ }
+
+ /**
+ * 妫�鏌ョ紦瀛樺璞℃槸鍚﹀瓨鍦�
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ */
+ public static boolean isExistsObject(final String key) {
+ return CLIENT.getBucket(key).isExists();
+ }
+
+ /**
+ * 缂撳瓨List鏁版嵁
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param dataList 寰呯紦瀛樼殑List鏁版嵁
+ * @return 缂撳瓨鐨勫璞�
+ */
+ public static <T> boolean setCacheList(final String key, final List<T> dataList) {
+ RList<T> rList = CLIENT.getList(key);
+ return rList.addAll(dataList);
+ }
+
+ /**
+ * 杩藉姞缂撳瓨List鏁版嵁
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param data 寰呯紦瀛樼殑鏁版嵁
+ * @return 缂撳瓨鐨勫璞�
+ */
+ public static <T> boolean addCacheList(final String key, final T data) {
+ RList<T> rList = CLIENT.getList(key);
+ return rList.add(data);
+ }
+
+ /**
+ * 娉ㄥ唽List鐩戝惉鍣�
+ * <p>
+ * key 鐩戝惉鍣ㄩ渶寮�鍚� `notify-keyspace-events` 绛� redis 鐩稿叧閰嶇疆
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param listener 鐩戝惉鍣ㄩ厤缃�
+ */
+ public static <T> void addListListener(final String key, final ObjectListener listener) {
+ RList<T> rList = CLIENT.getList(key);
+ rList.addListener(listener);
+ }
+
+ /**
+ * 鑾峰緱缂撳瓨鐨刲ist瀵硅薄
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @return 缂撳瓨閿�煎搴旂殑鏁版嵁
+ */
+ public static <T> List<T> getCacheList(final String key) {
+ RList<T> rList = CLIENT.getList(key);
+ return rList.readAll();
+ }
+
+ /**
+ * 鑾峰緱缂撳瓨鐨刲ist瀵硅薄(鑼冨洿)
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param form 璧峰涓嬫爣
+ * @param to 鎴涓嬫爣
+ * @return 缂撳瓨閿�煎搴旂殑鏁版嵁
+ */
+ public static <T> List<T> getCacheListRange(final String key, int form, int to) {
+ RList<T> rList = CLIENT.getList(key);
+ return rList.range(form, to);
+ }
+
+ /**
+ * 缂撳瓨Set
+ *
+ * @param key 缂撳瓨閿��
+ * @param dataSet 缂撳瓨鐨勬暟鎹�
+ * @return 缂撳瓨鏁版嵁鐨勫璞�
+ */
+ public static <T> boolean setCacheSet(final String key, final Set<T> dataSet) {
+ RSet<T> rSet = CLIENT.getSet(key);
+ return rSet.addAll(dataSet);
+ }
+
+ /**
+ * 杩藉姞缂撳瓨Set鏁版嵁
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param data 寰呯紦瀛樼殑鏁版嵁
+ * @return 缂撳瓨鐨勫璞�
+ */
+ public static <T> boolean addCacheSet(final String key, final T data) {
+ RSet<T> rSet = CLIENT.getSet(key);
+ return rSet.add(data);
+ }
+
+ /**
+ * 娉ㄥ唽Set鐩戝惉鍣�
+ * <p>
+ * key 鐩戝惉鍣ㄩ渶寮�鍚� `notify-keyspace-events` 绛� redis 鐩稿叧閰嶇疆
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param listener 鐩戝惉鍣ㄩ厤缃�
+ */
+ public static <T> void addSetListener(final String key, final ObjectListener listener) {
+ RSet<T> rSet = CLIENT.getSet(key);
+ rSet.addListener(listener);
+ }
+
+ /**
+ * 鑾峰緱缂撳瓨鐨剆et
+ *
+ * @param key 缂撳瓨鐨刱ey
+ * @return set瀵硅薄
+ */
+ public static <T> Set<T> getCacheSet(final String key) {
+ RSet<T> rSet = CLIENT.getSet(key);
+ return rSet.readAll();
+ }
+
+ /**
+ * 缂撳瓨Map
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param dataMap 缂撳瓨鐨勬暟鎹�
+ */
+ public static <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
+ if (dataMap != null) {
+ RMap<String, T> rMap = CLIENT.getMap(key);
+ rMap.putAll(dataMap);
+ }
+ }
+
+ /**
+ * 娉ㄥ唽Map鐩戝惉鍣�
+ * <p>
+ * key 鐩戝惉鍣ㄩ渶寮�鍚� `notify-keyspace-events` 绛� redis 鐩稿叧閰嶇疆
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @param listener 鐩戝惉鍣ㄩ厤缃�
+ */
+ public static <T> void addMapListener(final String key, final ObjectListener listener) {
+ RMap<String, T> rMap = CLIENT.getMap(key);
+ rMap.addListener(listener);
+ }
+
+ /**
+ * 鑾峰緱缂撳瓨鐨凪ap
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @return map瀵硅薄
+ */
+ public static <T> Map<String, T> getCacheMap(final String key) {
+ RMap<String, T> rMap = CLIENT.getMap(key);
+ return rMap.getAll(rMap.keySet());
+ }
+
+ /**
+ * 鑾峰緱缂撳瓨Map鐨刱ey鍒楄〃
+ *
+ * @param key 缂撳瓨鐨勯敭鍊�
+ * @return key鍒楄〃
+ */
+ public static <T> Set<String> getCacheMapKeySet(final String key) {
+ RMap<String, T> rMap = CLIENT.getMap(key);
+ return rMap.keySet();
+ }
+
+ /**
+ * 寰�Hash涓瓨鍏ユ暟鎹�
+ *
+ * @param key Redis閿�
+ * @param hKey Hash閿�
+ * @param value 鍊�
+ */
+ public static <T> void setCacheMapValue(final String key, final String hKey, final T value) {
+ RMap<String, T> rMap = CLIENT.getMap(key);
+ rMap.put(hKey, value);
+ }
+
+ /**
+ * 鑾峰彇Hash涓殑鏁版嵁
+ *
+ * @param key Redis閿�
+ * @param hKey Hash閿�
+ * @return Hash涓殑瀵硅薄
+ */
+ public static <T> T getCacheMapValue(final String key, final String hKey) {
+ RMap<String, T> rMap = CLIENT.getMap(key);
+ return rMap.get(hKey);
+ }
+
+ /**
+ * 鍒犻櫎Hash涓殑鏁版嵁
+ *
+ * @param key Redis閿�
+ * @param hKey Hash閿�
+ * @return Hash涓殑瀵硅薄
+ */
+ public static <T> T delCacheMapValue(final String key, final String hKey) {
+ RMap<String, T> rMap = CLIENT.getMap(key);
+ return rMap.remove(hKey);
+ }
+
+ /**
+ * 鍒犻櫎Hash涓殑鏁版嵁
+ *
+ * @param key Redis閿�
+ * @param hKeys Hash閿�
+ */
+ public static <T> void delMultiCacheMapValue(final String key, final Set<String> hKeys) {
+ RBatch batch = CLIENT.createBatch();
+ RMapAsync<String, T> rMap = batch.getMap(key);
+ for (String hKey : hKeys) {
+ rMap.removeAsync(hKey);
+ }
+ batch.execute();
+ }
+
+ /**
+ * 鑾峰彇澶氫釜Hash涓殑鏁版嵁
+ *
+ * @param key Redis閿�
+ * @param hKeys Hash閿泦鍚�
+ * @return Hash瀵硅薄闆嗗悎
+ */
+ public static <K, V> Map<K, V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
+ RMap<K, V> rMap = CLIENT.getMap(key);
+ return rMap.getAll(hKeys);
+ }
+
+ /**
+ * 璁剧疆鍘熷瓙鍊�
+ *
+ * @param key Redis閿�
+ * @param value 鍊�
+ */
+ public static void setAtomicValue(String key, long value) {
+ RAtomicLong atomic = CLIENT.getAtomicLong(key);
+ atomic.set(value);
+ }
+
+ /**
+ * 鑾峰彇鍘熷瓙鍊�
+ *
+ * @param key Redis閿�
+ * @return 褰撳墠鍊�
+ */
+ public static long getAtomicValue(String key) {
+ RAtomicLong atomic = CLIENT.getAtomicLong(key);
+ return atomic.get();
+ }
+
+ /**
+ * 閫掑鍘熷瓙鍊�
+ *
+ * @param key Redis閿�
+ * @return 褰撳墠鍊�
+ */
+ public static long incrAtomicValue(String key) {
+ RAtomicLong atomic = CLIENT.getAtomicLong(key);
+ return atomic.incrementAndGet();
+ }
+
+ /**
+ * 閫掑噺鍘熷瓙鍊�
+ *
+ * @param key Redis閿�
+ * @return 褰撳墠鍊�
+ */
+ public static long decrAtomicValue(String key) {
+ RAtomicLong atomic = CLIENT.getAtomicLong(key);
+ return atomic.decrementAndGet();
+ }
+
+ /**
+ * 鑾峰緱缂撳瓨鐨勫熀鏈璞″垪琛�(鍏ㄥ眬鍖归厤蹇界暐绉熸埛 鑷鎷兼帴绉熸埛id)
+ * <P>
+ * limit-璁剧疆鎵弿鐨勯檺鍒舵暟閲�(榛樿涓�0,鏌ヨ鍏ㄩ儴)
+ * pattern-璁剧疆閿殑鍖归厤妯″紡(榛樿涓簄ull)
+ * chunkSize-璁剧疆姣忔鎵弿鐨勫潡澶у皬(榛樿涓�0,鏈柟娉曡缃负1000)
+ * type-璁剧疆閿殑绫诲瀷(榛樿涓簄ull,鏌ヨ鍏ㄩ儴绫诲瀷)
+ * </P>
+ * @see KeysScanOptions
+ * @param pattern 瀛楃涓插墠缂�
+ * @return 瀵硅薄鍒楄〃
+ */
+ public static Collection<String> keys(final String pattern) {
+ return keys(KeysScanOptions.defaults().pattern(pattern).chunkSize(1000));
+ }
+
+ /**
+ * 閫氳繃鎵弿鍙傛暟鑾峰彇缂撳瓨鐨勫熀鏈璞″垪琛�
+ * @param keysScanOptions 鎵弿鍙傛暟
+ * <P>
+ * limit-璁剧疆鎵弿鐨勯檺鍒舵暟閲�(榛樿涓�0,鏌ヨ鍏ㄩ儴)
+ * pattern-璁剧疆閿殑鍖归厤妯″紡(榛樿涓簄ull)
+ * chunkSize-璁剧疆姣忔鎵弿鐨勫潡澶у皬(榛樿涓�0)
+ * type-璁剧疆閿殑绫诲瀷(榛樿涓簄ull,鏌ヨ鍏ㄩ儴绫诲瀷)
+ * </P>
+ * @see KeysScanOptions
+ */
+ public static Collection<String> keys(final KeysScanOptions keysScanOptions) {
+ Stream<String> keysStream = CLIENT.getKeys().getKeysStream(keysScanOptions);
+ return keysStream.collect(Collectors.toList());
+ }
+
+ /**
+ * 鍒犻櫎缂撳瓨鐨勫熀鏈璞″垪琛�(鍏ㄥ眬鍖归厤蹇界暐绉熸埛 鑷鎷兼帴绉熸埛id)
+ *
+ * @param pattern 瀛楃涓插墠缂�
+ */
+ public static void deleteKeys(final String pattern) {
+ CLIENT.getKeys().deleteByPattern(pattern);
+ }
+
+ /**
+ * 妫�鏌edis涓槸鍚﹀瓨鍦╧ey
+ *
+ * @param key 閿�
+ */
+ public static Boolean hasKey(String key) {
+ RKeys rKeys = CLIENT.getKeys();
+ return rKeys.countExists(key) > 0;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java
new file mode 100755
index 0000000..a3d0bad
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java
@@ -0,0 +1,351 @@
+package org.dromara.common.redis.utils;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.date.DatePattern;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.redisson.api.RIdGenerator;
+import org.redisson.api.RedissonClient;
+
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAccessor;
+
+/**
+ * 鍙戝彿鍣ㄥ伐鍏风被
+ *
+ * @author 绉嬭緸鏈瘨
+ * @date 2024-12-10
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class SequenceUtils {
+
+ /**
+ * 榛樿鍒濆鍊�
+ */
+ public static final long DEFAULT_INIT_VALUE = 1L;
+
+ /**
+ * 榛樿姝ラ暱
+ */
+ public static final long DEFAULT_STEP_VALUE = 1L;
+
+ /**
+ * 榛樿杩囨湡鏃堕棿-澶�
+ */
+ public static final Duration DEFAULT_EXPIRE_TIME_DAY = Duration.ofDays(1);
+
+ /**
+ * 榛樿杩囨湡鏃堕棿-鍒嗛挓
+ */
+ public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
+
+ /**
+ * 榛樿鏈�灏廔D瀹归噺浣嶆暟 - 6浣嶆暟锛堝嵆鑷冲皯鍙互鐢熸垚鐨処D涓�999999涓級
+ */
+ public static final int DEFAULT_MIN_ID_CAPACITY_BITS = 6;
+
+ /**
+ * 鑾峰彇Redisson瀹㈡埛绔疄渚�
+ */
+ private static final RedissonClient REDISSON_CLIENT = SpringUtils.getBean(RedissonClient.class);
+
+ /**
+ * 鑾峰彇ID鐢熸垚鍣�
+ *
+ * @param key 涓氬姟key
+ * @param expireTime 杩囨湡鏃堕棿
+ * @param initValue ID鍒濆鍊�
+ * @param stepValue ID姝ラ暱
+ * @return ID鐢熸垚鍣�
+ */
+ public static RIdGenerator getIdGenerator(String key, Duration expireTime, long initValue, long stepValue) {
+ RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
+ // 鍒濆鍊煎拰姝ラ暱涓嶈兘灏忎簬绛変簬0
+ initValue = initValue <= 0 ? DEFAULT_INIT_VALUE : initValue;
+ stepValue = stepValue <= 0 ? DEFAULT_STEP_VALUE : stepValue;
+ // 璁剧疆鍒濆鍊煎拰姝ラ暱
+ idGenerator.tryInit(initValue, stepValue);
+ // 璁剧疆杩囨湡鏃堕棿
+ idGenerator.expire(expireTime);
+ return idGenerator;
+ }
+
+ /**
+ * 鑾峰彇ID鐢熸垚鍣�
+ *
+ * @param key 涓氬姟key
+ * @param expireTime 杩囨湡鏃堕棿
+ * @return ID鐢熸垚鍣�
+ */
+ public static RIdGenerator getIdGenerator(String key, Duration expireTime) {
+ return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE);
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾涓氬姟key鐨勫敮涓�id
+ *
+ * @param key 涓氬姟key
+ * @param expireTime 杩囨湡鏃堕棿
+ * @param initValue ID鍒濆鍊�
+ * @param stepValue ID姝ラ暱
+ * @return 鍞竴id
+ */
+ public static long getNextId(String key, Duration expireTime, long initValue, long stepValue) {
+ return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾涓氬姟key鐨勫敮涓�id (ID鍒濆鍊�=1,ID姝ラ暱=1)
+ *
+ * @param key 涓氬姟key
+ * @param expireTime 杩囨湡鏃堕棿
+ * @return 鍞竴id
+ */
+ public static long getNextId(String key, Duration expireTime) {
+ return getIdGenerator(key, expireTime).nextId();
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾涓氬姟key鐨勫敮涓�id瀛楃涓�
+ *
+ * @param key 涓氬姟key
+ * @param expireTime 杩囨湡鏃堕棿
+ * @param initValue ID鍒濆鍊�
+ * @param stepValue ID姝ラ暱
+ * @return 鍞竴id
+ */
+ public static String getNextIdString(String key, Duration expireTime, long initValue, long stepValue) {
+ return Convert.toStr(getNextId(key, expireTime, initValue, stepValue));
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾涓氬姟key鐨勫敮涓�id瀛楃涓� (ID鍒濆鍊�=1,ID姝ラ暱=1)
+ *
+ * @param key 涓氬姟key
+ * @param expireTime 杩囨湡鏃堕棿
+ * @return 鍞竴id
+ */
+ public static String getNextIdString(String key, Duration expireTime) {
+ return Convert.toStr(getNextId(key, expireTime));
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾涓氬姟key鐨勫敮涓�id瀛楃涓� (ID鍒濆鍊�=1,ID姝ラ暱=1)锛屼笉瓒充綅鏁拌嚜鍔ㄨˉ闆�
+ *
+ * @param key 涓氬姟key
+ * @param expireTime 杩囨湡鏃堕棿
+ * @param width 浣嶆暟锛屼笉瓒冲乏琛�0
+ * @return 琛ラ浂鍚庣殑鍞竴id瀛楃涓�
+ */
+ public static String getPaddedNextIdString(String key, Duration expireTime, Integer width) {
+ return StringUtils.leftPad(getNextIdString(key, expireTime), width, '0');
+ }
+
+ /**
+ * 鑾峰彇 yyyyMMdd 鏍煎紡鐨勫敮涓�id
+ *
+ * @return 鍞竴id
+ * @deprecated 璇蜂娇鐢� {@link #getDateId(String)} 鎴� {@link #getDateId(String, boolean)}銆亄@link #getDateId(String, boolean, int)}锛岀‘淇濅笉鍚屼笟鍔$殑ID杩炵画鎬�
+ */
+ @Deprecated
+ public static String getDateId() {
+ return getDateId("");
+ }
+
+ /**
+ * 鑾峰彇 prefix + yyyyMMdd 鏍煎紡鐨勫敮涓�id
+ *
+ * @param prefix 涓氬姟鍓嶇紑
+ * @return 鍞竴id
+ */
+ public static String getDateId(String prefix) {
+ return getDateId(prefix, true);
+ }
+
+ /**
+ * 鑾峰彇 prefix + yyyyMMdd 鏍煎紡鐨勫敮涓�id
+ *
+ * @param prefix 涓氬姟鍓嶇紑
+ * @param isWithPrefix id鏄惁鎼哄甫涓氬姟鍓嶇紑
+ * @return 鍞竴id
+ */
+ public static String getDateId(String prefix, boolean isWithPrefix) {
+ return getDateId(prefix, isWithPrefix, -1);
+ }
+
+ /**
+ * 鑾峰彇 prefix + yyyyMMdd 鏍煎紡鐨勫敮涓�id (鍚敤ID琛ヤ綅锛岃ˉ浣嶉暱搴� = {@link #DEFAULT_MIN_ID_CAPACITY_BITS})}锛�
+ *
+ * @param prefix 涓氬姟鍓嶇紑
+ * @param isWithPrefix id鏄惁鎼哄甫涓氬姟鍓嶇紑
+ * @return 鍞竴id
+ */
+ public static String getPaddedDateId(String prefix, boolean isWithPrefix) {
+ return getDateId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS);
+ }
+
+ /**
+ * 鑾峰彇 prefix + yyyyMMdd 鏍煎紡鐨勫敮涓�id
+ *
+ * @param prefix 涓氬姟鍓嶇紑
+ * @param isWithPrefix id鏄惁鎼哄甫涓氬姟鍓嶇紑
+ * @param minIdCapacityBits 鏈�灏廔D瀹归噺浣嶆暟锛屽皬浜庤浣嶆暟鐨処D锛屽乏琛�0锛堝皬浜庣瓑浜�0琛ㄧず涓嶅惎鐢ㄨˉ浣嶏級
+ * @return 鍞竴id
+ */
+ public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits) {
+ return getDateId(prefix, isWithPrefix, minIdCapacityBits, LocalDate.now());
+ }
+
+ /**
+ * 鑾峰彇 prefix + yyyyMMdd 鏍煎紡鐨勫敮涓�id
+ *
+ * @param prefix 涓氬姟鍓嶇紑
+ * @param isWithPrefix id鏄惁鎼哄甫涓氬姟鍓嶇紑
+ * @param minIdCapacityBits 鏈�灏廔D瀹归噺浣嶆暟锛屽皬浜庤浣嶆暟鐨処D锛屽乏琛�0锛堝皬浜庣瓑浜�0琛ㄧず涓嶅惎鐢ㄨˉ浣嶏級
+ * @param time 鏃堕棿
+ * @return 鍞竴id
+ */
+ public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDate time) {
+ return getDateId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE);
+ }
+
+ /**
+ * 鑾峰彇 prefix + yyyyMMdd 鏍煎紡鐨勫敮涓�id
+ *
+ * @param prefix 涓氬姟鍓嶇紑
+ * @param isWithPrefix id鏄惁鎼哄甫涓氬姟鍓嶇紑
+ * @param minIdCapacityBits 鏈�灏廔D瀹归噺浣嶆暟锛屽皬浜庤浣嶆暟鐨処D锛屽乏琛�0锛堝皬浜庣瓑浜�0琛ㄧず涓嶅惎鐢ㄨˉ浣嶏級
+ * @param time 鏃堕棿
+ * @param initValue ID鍒濆鍊�
+ * @param stepValue ID姝ラ暱
+ * @return 鍞竴id
+ */
+ public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDate time, long initValue, long stepValue) {
+ return getDatePatternId(prefix, isWithPrefix, minIdCapacityBits, time, DatePattern.PURE_DATE_FORMATTER, DEFAULT_EXPIRE_TIME_DAY, initValue, stepValue);
+ }
+
+ /**
+ * 鑾峰彇 yyyyMMddHHmmss 鏍煎紡鐨勫敮涓�id
+ *
+ * @return 鍞竴id
+ * @deprecated 璇蜂娇鐢� {@link #getDateTimeId(String)} 鎴� {@link #getDateTimeId(String, boolean)}銆亄@link #getDateTimeId(String, boolean, int)}锛岀‘淇濅笉鍚屼笟鍔$殑ID杩炵画鎬�
+ */
+ @Deprecated
+ public static String getDateTimeId() {
+ return getDateTimeId("", false);
+ }
+
+ /**
+ * 鑾峰彇 prefix + yyyyMMddHHmmss 鏍煎紡鐨勫敮涓�id
+ *
+ * @param prefix 涓氬姟鍓嶇紑
+ * @return 鍞竴id
+ */
+ public static String getDateTimeId(String prefix) {
+ return getDateTimeId(prefix, true);
+ }
+
+ /**
+ * 鑾峰彇 prefix + yyyyMMddHHmmss 鏍煎紡鐨勫敮涓�id
+ *
+ * @param prefix 涓氬姟鍓嶇紑
+ * @param isWithPrefix id鏄惁鎼哄甫涓氬姟鍓嶇紑
+ * @return 鍞竴id
+ */
+ public static String getDateTimeId(String prefix, boolean isWithPrefix) {
+ return getDateTimeId(prefix, isWithPrefix, -1);
+ }
+
+ /**
+ * 鑾峰彇 prefix + yyyyMMddHHmmss 鏍煎紡鐨勫敮涓�id (鍚敤ID琛ヤ綅锛岃ˉ浣嶉暱搴� = {@link #DEFAULT_MIN_ID_CAPACITY_BITS})}锛�
+ *
+ * @param prefix 涓氬姟鍓嶇紑
+ * @param isWithPrefix id鏄惁鎼哄甫涓氬姟鍓嶇紑
+ * @return 鍞竴id
+ */
+ public static String getPaddedDateTimeId(String prefix, boolean isWithPrefix) {
+ return getDateTimeId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS);
+ }
+
+ /**
+ * 鑾峰彇 prefix + yyyyMMddHHmmss 鏍煎紡鐨勫敮涓�id
+ *
+ * @param prefix 涓氬姟鍓嶇紑
+ * @param isWithPrefix id鏄惁鎼哄甫涓氬姟鍓嶇紑
+ * @param minIdCapacityBits 鏈�灏廔D瀹归噺浣嶆暟锛屽皬浜庤浣嶆暟鐨処D锛屽乏琛�0锛堝皬浜庣瓑浜�0琛ㄧず涓嶅惎鐢ㄨˉ浣嶏級
+ * @return 鍞竴id
+ */
+ public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits) {
+ return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, LocalDateTime.now());
+ }
+
+ /**
+ * 鑾峰彇 prefix + yyyyMMddHHmmss 鏍煎紡鐨勫敮涓�id
+ *
+ * @param prefix 涓氬姟鍓嶇紑
+ * @param isWithPrefix id鏄惁鎼哄甫涓氬姟鍓嶇紑
+ * @param minIdCapacityBits 鏈�灏廔D瀹归噺浣嶆暟锛屽皬浜庤浣嶆暟鐨処D锛屽乏琛�0锛堝皬浜庣瓑浜�0琛ㄧず涓嶅惎鐢ㄨˉ浣嶏級
+ * @param time 鏃堕棿
+ * @return 鍞竴id
+ */
+ public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDateTime time) {
+ return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE);
+ }
+
+ /**
+ * 鑾峰彇 prefix + yyyyMMddHHmmss 鏍煎紡鐨勫敮涓�id
+ *
+ * @param prefix 涓氬姟鍓嶇紑
+ * @param isWithPrefix id鏄惁鎼哄甫涓氬姟鍓嶇紑
+ * @param minIdCapacityBits 鏈�灏廔D瀹归噺浣嶆暟锛屽皬浜庤浣嶆暟鐨処D锛屽乏琛�0锛堝皬浜庣瓑浜�0琛ㄧず涓嶅惎鐢ㄨˉ浣嶏級
+ * @param initValue ID鍒濆鍊�
+ * @param stepValue ID姝ラ暱
+ * @return 鍞竴id
+ */
+ public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDateTime time, long initValue, long stepValue) {
+ return getDatePatternId(prefix, isWithPrefix, minIdCapacityBits, time, DatePattern.PURE_DATETIME_FORMATTER, DEFAULT_EXPIRE_TIME_MINUTE, initValue, stepValue);
+ }
+
+ /**
+ * 鑾峰彇鎸囧畾涓氬姟key鐨勬寚瀹氭椂闂存牸寮忕殑ID
+ *
+ * @param prefix 涓氬姟鍓嶇紑
+ * @param isWithPrefix id鏄惁鎼哄甫涓氬姟鍓嶇紑
+ * @param minIdCapacityBits 鏈�灏廔D瀹归噺浣嶆暟锛屽皬浜庤浣嶆暟鐨処D锛屽乏琛�0锛堝皬浜庣瓑浜�0琛ㄧず涓嶅惎鐢ㄨˉ浣嶏級
+ * @param temporalAccessor 鏃堕棿璁块棶鍣�
+ * @param timeFormatter 鏃堕棿鏍煎紡
+ * @param expireTime 杩囨湡鏃堕棿
+ * @param initValue ID鍒濆鍊�
+ * @param stepValue ID姝ラ暱
+ * @return 鍞竴id
+ */
+ private static String getDatePatternId(String prefix, boolean isWithPrefix, int minIdCapacityBits, TemporalAccessor temporalAccessor, DateTimeFormatter timeFormatter, Duration expireTime, long initValue, long stepValue) {
+ // 鏃堕棿鍓嶇紑
+ String timePrefix = timeFormatter.format(temporalAccessor);
+ // 涓氬姟鍓嶇紑 + 鏃堕棿鍓嶇紑 鏋勫缓 prefixKey
+ String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), timePrefix);
+
+ // 鑾峰彇id锛屼緥 -> 1
+ String nextId = getNextIdString(prefixKey, expireTime, initValue, stepValue);
+
+ // minIdCapacityBits 澶т簬0锛屼笖 nextId 鐨勯暱搴﹀皬浜� minIdCapacityBits锛屽垯宸﹁ˉ0
+ if (minIdCapacityBits > 0 && nextId.length() < minIdCapacityBits) {
+ nextId = StringUtils.leftPad(nextId, minIdCapacityBits, '0');
+ }
+
+ // 鏄惁鎼哄甫涓氬姟鍓嶇紑
+ if (isWithPrefix) {
+ // 渚� -> P202507031
+ // 鍏朵腑 P 涓轰笟鍔″墠缂�锛�202507031 涓� yyyyMMdd 鏍煎紡鏃堕棿, 1 涓簄extId
+ return StringUtils.format("{}{}", prefixKey, nextId);
+ }
+ // 渚� -> 202507031
+ // 鍏朵腑 202507031 涓� yyyyMMdd 鏍煎紡鏃堕棿, 1 涓簄extId
+ return StringUtils.format("{}{}", timePrefix, nextId);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..0475b19
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,2 @@
+org.dromara.common.redis.config.RedisConfig
+org.dromara.common.redis.config.CacheConfig
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/.flattened-pom.xml
new file mode 100644
index 0000000..b1bec46
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/.flattened-pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-satoken</artifactId>
+ <version>5.5.3</version>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-spring-boot3-starter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-jwt</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.github.ben-manes.caffeine</groupId>
+ <artifactId>caffeine</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/pom.xml
new file mode 100755
index 0000000..4df7d4d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-satoken</artifactId>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+
+ <!-- RuoYi Common Redis-->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+
+ <!-- Sa-Token 鏉冮檺璁よ瘉, 鍦ㄧ嚎鏂囨。锛歨ttp://sa-token.dev33.cn/ -->
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-spring-boot3-starter</artifactId>
+ </dependency>
+
+ <!-- Sa-Token 鏁村悎 jwt -->
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-jwt</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.github.ben-manes.caffeine</groupId>
+ <artifactId>caffeine</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/config/SaTokenConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/config/SaTokenConfig.java
new file mode 100755
index 0000000..61c6b9a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/config/SaTokenConfig.java
@@ -0,0 +1,54 @@
+package org.dromara.common.satoken.config;
+
+import cn.dev33.satoken.dao.SaTokenDao;
+import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
+import cn.dev33.satoken.stp.StpInterface;
+import cn.dev33.satoken.stp.StpLogic;
+import org.dromara.common.core.factory.YmlPropertySourceFactory;
+import org.dromara.common.satoken.core.dao.PlusSaTokenDao;
+import org.dromara.common.satoken.core.service.SaPermissionImpl;
+import org.dromara.common.satoken.handler.SaTokenExceptionHandler;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * sa-token 閰嶇疆
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+@PropertySource(value = "classpath:common-satoken.yml", factory = YmlPropertySourceFactory.class)
+public class SaTokenConfig {
+
+ @Bean
+ public StpLogic getStpLogicJwt() {
+ // Sa-Token 鏁村悎 jwt (绠�鍗曟ā寮�)
+ return new StpLogicJwtForSimple();
+ }
+
+ /**
+ * 鏉冮檺鎺ュ彛瀹炵幇(浣跨敤bean娉ㄥ叆鏂逛究鐢ㄦ埛鏇挎崲)
+ */
+ @Bean
+ public StpInterface stpInterface() {
+ return new SaPermissionImpl();
+ }
+
+ /**
+ * 鑷畾涔塪ao灞傚瓨鍌�
+ */
+ @Bean
+ public SaTokenDao saTokenDao() {
+ return new PlusSaTokenDao();
+ }
+
+ /**
+ * 寮傚父澶勭悊鍣�
+ */
+ @Bean
+ public SaTokenExceptionHandler saTokenExceptionHandler() {
+ return new SaTokenExceptionHandler();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java
new file mode 100755
index 0000000..14abf89
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java
@@ -0,0 +1,192 @@
+package org.dromara.common.satoken.core.dao;
+
+import cn.dev33.satoken.dao.auto.SaTokenDaoBySessionFollowObject;
+import cn.dev33.satoken.util.SaFoxUtil;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.dromara.common.redis.utils.RedisUtils;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Sa-Token鎸佷箙灞傛帴鍙�(浣跨敤妗嗘灦鑷甫RedisUtils瀹炵幇 鍗忚缁熶竴)
+ * <p>
+ * 閲囩敤 caffeine + redis 澶氱骇缂撳瓨 浼樺寲骞跺彂鏌ヨ鏁堢巼
+ * <p>
+ * SaTokenDaoBySessionFollowObject 鏄� SaTokenDao 瀛愰泦绠�鍖栦簡session鏂规硶澶勭悊
+ *
+ * @author Lion Li
+ */
+public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
+
+ private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
+ // 璁剧疆鏈�鍚庝竴娆″啓鍏ユ垨璁块棶鍚庣粡杩囧浐瀹氭椂闂磋繃鏈�
+ .expireAfterWrite(5, TimeUnit.SECONDS)
+ // 鍒濆鐨勭紦瀛樼┖闂村ぇ灏�
+ .initialCapacity(100)
+ // 缂撳瓨鐨勬渶澶ф潯鏁�
+ .maximumSize(1000)
+ .build();
+
+ /**
+ * 鑾峰彇Value锛屽鏃犺繑绌�
+ */
+ @Override
+ public String get(String key) {
+ Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
+ return (String) o;
+ }
+
+ /**
+ * 鍐欏叆Value锛屽苟璁惧畾瀛樻椿鏃堕棿 (鍗曚綅: 绉�)
+ */
+ @Override
+ public void set(String key, String value, long timeout) {
+ if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) {
+ return;
+ }
+ // 鍒ゆ柇鏄惁涓烘案涓嶈繃鏈�
+ if (timeout == NEVER_EXPIRE) {
+ RedisUtils.setCacheObject(key, value);
+ } else {
+ RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
+ }
+ CAFFEINE.invalidate(key);
+ }
+
+ /**
+ * 淇慨鏀规寚瀹歬ey-value閿�煎 (杩囨湡鏃堕棿涓嶅彉)
+ */
+ @Override
+ public void update(String key, String value) {
+ if (RedisUtils.hasKey(key)) {
+ RedisUtils.setCacheObject(key, value, true);
+ CAFFEINE.invalidate(key);
+ }
+ }
+
+ /**
+ * 鍒犻櫎Value
+ */
+ @Override
+ public void delete(String key) {
+ if (RedisUtils.deleteObject(key)) {
+ CAFFEINE.invalidate(key);
+ }
+ }
+
+ /**
+ * 鑾峰彇Value鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+ */
+ @Override
+ public long getTimeout(String key) {
+ long timeout = RedisUtils.getTimeToLive(key);
+ // 鍔�1鐨勭洰鐨� 瑙e喅sa-token浣跨敤绉� redis鏄绉掑鑷�1绉掔殑绮惧害闂 鎵嬪姩琛ュ伩
+ return timeout < 0 ? timeout : timeout / 1000 + 1;
+ }
+
+ /**
+ * 淇敼Value鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+ */
+ @Override
+ public void updateTimeout(String key, long timeout) {
+ RedisUtils.expire(key, Duration.ofSeconds(timeout));
+ }
+
+
+ /**
+ * 鑾峰彇Object锛屽鏃犺繑绌�
+ */
+ @Override
+ public Object getObject(String key) {
+ Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
+ return o;
+ }
+
+ /**
+ * 鑾峰彇 Object (鎸囧畾鍙嶅簭鍒楀寲绫诲瀷)锛屽鏃犺繑绌�
+ *
+ * @param key 閿悕绉�
+ * @return object
+ */
+ @SuppressWarnings("unchecked cast")
+ @Override
+ public <T> T getObject(String key, Class<T> classType) {
+ Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
+ return (T) o;
+ }
+
+ /**
+ * 鍐欏叆Object锛屽苟璁惧畾瀛樻椿鏃堕棿 (鍗曚綅: 绉�)
+ */
+ @Override
+ public void setObject(String key, Object object, long timeout) {
+ if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) {
+ return;
+ }
+ // 鍒ゆ柇鏄惁涓烘案涓嶈繃鏈�
+ if (timeout == NEVER_EXPIRE) {
+ RedisUtils.setCacheObject(key, object);
+ } else {
+ RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
+ }
+ CAFFEINE.invalidate(key);
+ }
+
+ /**
+ * 鏇存柊Object (杩囨湡鏃堕棿涓嶅彉)
+ */
+ @Override
+ public void updateObject(String key, Object object) {
+ if (RedisUtils.hasKey(key)) {
+ RedisUtils.setCacheObject(key, object, true);
+ CAFFEINE.invalidate(key);
+ }
+ }
+
+ /**
+ * 鍒犻櫎Object
+ */
+ @Override
+ public void deleteObject(String key) {
+ if (RedisUtils.deleteObject(key)) {
+ CAFFEINE.invalidate(key);
+ }
+ }
+
+ /**
+ * 鑾峰彇Object鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+ */
+ @Override
+ public long getObjectTimeout(String key) {
+ long timeout = RedisUtils.getTimeToLive(key);
+ // 鍔�1鐨勭洰鐨� 瑙e喅sa-token浣跨敤绉� redis鏄绉掑鑷�1绉掔殑绮惧害闂 鎵嬪姩琛ュ伩
+ return timeout < 0 ? timeout : timeout / 1000 + 1;
+ }
+
+ /**
+ * 淇敼Object鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+ */
+ @Override
+ public void updateObjectTimeout(String key, long timeout) {
+ RedisUtils.expire(key, Duration.ofSeconds(timeout));
+ }
+
+ /**
+ * 鎼滅储鏁版嵁
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
+ String keyStr = prefix + "*" + keyword + "*";
+ return (List<String>) CAFFEINE.get(keyStr, k -> {
+ Collection<String> keys = RedisUtils.keys(keyStr);
+ List<String> list = new ArrayList<>(keys);
+ return SaFoxUtil.searchList(list, start, size, sortType);
+ });
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java
new file mode 100755
index 0000000..c9f8c8c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java
@@ -0,0 +1,86 @@
+package org.dromara.common.satoken.core.service;
+
+import cn.dev33.satoken.stp.StpInterface;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.enums.UserType;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.PermissionService;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * sa-token 鏉冮檺绠$悊瀹炵幇绫�
+ *
+ * @author Lion Li
+ */
+public class SaPermissionImpl implements StpInterface {
+
+ /**
+ * 鑾峰彇鑿滃崟鏉冮檺鍒楄〃
+ */
+ @Override
+ public List<String> getPermissionList(Object loginId, String loginType) {
+ LoginUser loginUser = LoginHelper.getLoginUser();
+ if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
+ PermissionService permissionService = getPermissionService();
+ if (ObjectUtil.isNotNull(permissionService)) {
+ List<String> list = StringUtils.splitList(loginId.toString(), ":");
+ return new ArrayList<>(permissionService.getMenuPermission(Long.parseLong(list.get(1))));
+ } else {
+ throw new ServiceException("PermissionService 瀹炵幇绫讳笉瀛樺湪");
+ }
+ }
+ UserType userType = UserType.getUserType(loginUser.getUserType());
+ if (userType == UserType.APP_USER) {
+ // 鍏朵粬绔� 鑷鏍规嵁涓氬姟缂栧啓
+ }
+ if (CollUtil.isNotEmpty(loginUser.getMenuPermission())) {
+ // SYS_USER 榛樿杩斿洖鏉冮檺
+ return new ArrayList<>(loginUser.getMenuPermission());
+ } else {
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * 鑾峰彇瑙掕壊鏉冮檺鍒楄〃
+ */
+ @Override
+ public List<String> getRoleList(Object loginId, String loginType) {
+ LoginUser loginUser = LoginHelper.getLoginUser();
+ if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
+ PermissionService permissionService = getPermissionService();
+ if (ObjectUtil.isNotNull(permissionService)) {
+ List<String> list = StringUtils.splitList(loginId.toString(), ":");
+ return new ArrayList<>(permissionService.getRolePermission(Long.parseLong(list.get(1))));
+ } else {
+ throw new ServiceException("PermissionService 瀹炵幇绫讳笉瀛樺湪");
+ }
+ }
+ UserType userType = UserType.getUserType(loginUser.getUserType());
+ if (userType == UserType.APP_USER) {
+ // 鍏朵粬绔� 鑷鏍规嵁涓氬姟缂栧啓
+ }
+ if (CollUtil.isNotEmpty(loginUser.getRolePermission())) {
+ // SYS_USER 榛樿杩斿洖鏉冮檺
+ return new ArrayList<>(loginUser.getRolePermission());
+ } else {
+ return new ArrayList<>();
+ }
+ }
+
+ private PermissionService getPermissionService() {
+ try {
+ return SpringUtils.getBean(PermissionService.class);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/handler/SaTokenExceptionHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/handler/SaTokenExceptionHandler.java
new file mode 100755
index 0000000..a45af89
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/handler/SaTokenExceptionHandler.java
@@ -0,0 +1,52 @@
+package org.dromara.common.satoken.handler;
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.exception.NotPermissionException;
+import cn.dev33.satoken.exception.NotRoleException;
+import cn.hutool.http.HttpStatus;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * SaToken寮傚父澶勭悊鍣�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RestControllerAdvice
+public class SaTokenExceptionHandler {
+
+ /**
+ * 鏉冮檺鐮佸紓甯�
+ */
+ @ExceptionHandler(NotPermissionException.class)
+ public R<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("璇锋眰鍦板潃'{}',鏉冮檺鐮佹牎楠屽け璐�'{}'", requestURI, e.getMessage());
+ return R.fail(HttpStatus.HTTP_FORBIDDEN, "娌℃湁璁块棶鏉冮檺锛岃鑱旂郴绠$悊鍛樻巿鏉�");
+ }
+
+ /**
+ * 瑙掕壊鏉冮檺寮傚父
+ */
+ @ExceptionHandler(NotRoleException.class)
+ public R<Void> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("璇锋眰鍦板潃'{}',瑙掕壊鏉冮檺鏍¢獙澶辫触'{}'", requestURI, e.getMessage());
+ return R.fail(HttpStatus.HTTP_FORBIDDEN, "娌℃湁璁块棶鏉冮檺锛岃鑱旂郴绠$悊鍛樻巿鏉�");
+ }
+
+ /**
+ * 璁よ瘉澶辫触
+ */
+ @ExceptionHandler(NotLoginException.class)
+ public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("璇锋眰鍦板潃'{}',璁よ瘉澶辫触'{}',鏃犳硶璁块棶绯荤粺璧勬簮", requestURI, e.getMessage());
+ return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "璁よ瘉澶辫触锛屾棤娉曡闂郴缁熻祫婧�");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
new file mode 100755
index 0000000..730bae2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
@@ -0,0 +1,217 @@
+package org.dromara.common.satoken.utils;
+
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.constant.TenantConstants;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.enums.UserType;
+
+import java.util.Set;
+
+
+/**
+ * 鐧诲綍閴存潈鍔╂墜
+ * <p>
+ * user_type 涓� 鐢ㄦ埛绫诲瀷 鍚屼竴涓敤鎴疯〃 鍙互鏈夊绉嶇敤鎴风被鍨� 渚嬪 pc,app
+ * deivce 涓� 璁惧绫诲瀷 鍚屼竴涓敤鎴风被鍨� 鍙互鏈� 澶氱璁惧绫诲瀷 渚嬪 web,ios
+ * 鍙互缁勬垚 鐢ㄦ埛绫诲瀷涓庤澶囩被鍨嬪瀵瑰鐨� 鏉冮檺鐏垫椿鎺у埗
+ * <p>
+ * 澶氱敤鎴蜂綋绯� 閽堝 澶氱鐢ㄦ埛绫诲瀷 浣嗘潈闄愭帶鍒朵笉涓�鑷�
+ * 鍙互缁勬垚 澶氱敤鎴风被鍨嬭〃涓庡璁惧绫诲瀷 鍒嗗埆鎺у埗鏉冮檺
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class LoginHelper {
+
+ public static final String LOGIN_USER_KEY = "loginUser";
+ public static final String TENANT_KEY = "tenantId";
+ public static final String USER_KEY = "userId";
+ public static final String USER_NAME_KEY = "userName";
+ public static final String DEPT_KEY = "deptId";
+ public static final String DEPT_NAME_KEY = "deptName";
+ public static final String DEPT_CATEGORY_KEY = "deptCategory";
+ public static final String CLIENT_KEY = "clientid";
+
+ /**
+ * 鐧诲綍绯荤粺 鍩轰簬 璁惧绫诲瀷
+ * 閽堝鐩稿悓鐢ㄦ埛浣撶郴涓嶅悓璁惧
+ *
+ * @param loginUser 鐧诲綍鐢ㄦ埛淇℃伅
+ * @param model 閰嶇疆鍙傛暟
+ */
+ public static void login(LoginUser loginUser, SaLoginParameter model) {
+ model = ObjectUtil.defaultIfNull(model, new SaLoginParameter());
+ StpUtil.login(loginUser.getLoginId(),
+ model.setExtra(TENANT_KEY, loginUser.getTenantId())
+ .setExtra(USER_KEY, loginUser.getUserId())
+ .setExtra(USER_NAME_KEY, loginUser.getUsername())
+ .setExtra(DEPT_KEY, loginUser.getDeptId())
+ .setExtra(DEPT_NAME_KEY, loginUser.getDeptName())
+ .setExtra(DEPT_CATEGORY_KEY, loginUser.getDeptCategory())
+ );
+ StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛(澶氱骇缂撳瓨)
+ */
+ @SuppressWarnings("unchecked cast")
+ public static <T extends LoginUser> T getLoginUser() {
+ SaSession session = StpUtil.getTokenSession();
+ if (ObjectUtil.isNull(session)) {
+ return null;
+ }
+ return (T) session.get(LOGIN_USER_KEY);
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛鍩轰簬token
+ */
+ @SuppressWarnings("unchecked cast")
+ public static <T extends LoginUser> T getLoginUser(String token) {
+ SaSession session = StpUtil.getTokenSessionByToken(token);
+ if (ObjectUtil.isNull(session)) {
+ return null;
+ }
+ return (T) session.get(LOGIN_USER_KEY);
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛id
+ */
+ public static Long getUserId() {
+ return Convert.toLong(getExtra(USER_KEY));
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛id
+ */
+ public static String getUserIdStr() {
+ return Convert.toStr(getExtra(USER_KEY));
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛璐︽埛
+ */
+ public static String getUsername() {
+ return Convert.toStr(getExtra(USER_NAME_KEY));
+ }
+
+ /**
+ * 鑾峰彇绉熸埛ID
+ */
+ public static String getTenantId() {
+ return Convert.toStr(getExtra(TENANT_KEY));
+ }
+
+ /**
+ * 鑾峰彇閮ㄩ棬ID
+ */
+ public static Long getDeptId() {
+ return Convert.toLong(getExtra(DEPT_KEY));
+ }
+
+ /**
+ * 鑾峰彇閮ㄩ棬鍚�
+ */
+ public static String getDeptName() {
+ return Convert.toStr(getExtra(DEPT_NAME_KEY));
+ }
+
+ /**
+ * 鑾峰彇閮ㄩ棬绫诲埆缂栫爜
+ */
+ public static String getDeptCategory() {
+ return Convert.toStr(getExtra(DEPT_CATEGORY_KEY));
+ }
+
+ /**
+ * 鑾峰彇褰撳墠 Token 鐨勬墿灞曚俊鎭�
+ *
+ * @param key 閿��
+ * @return 瀵瑰簲鐨勬墿灞曟暟鎹�
+ */
+ private static Object getExtra(String key) {
+ try {
+ return StpUtil.getExtra(key);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛绫诲瀷
+ */
+ public static UserType getUserType() {
+ String loginType = StpUtil.getLoginIdAsString();
+ return UserType.getUserType(loginType);
+ }
+
+ /**
+ * 鏄惁涓鸿秴绾х鐞嗗憳
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 缁撴灉
+ */
+ public static boolean isSuperAdmin(Long userId) {
+ return SystemConstants.SUPER_ADMIN_ID.equals(userId);
+ }
+
+ /**
+ * 鏄惁涓鸿秴绾х鐞嗗憳
+ *
+ * @return 缁撴灉
+ */
+ public static boolean isSuperAdmin() {
+ return isSuperAdmin(getUserId());
+ }
+
+ /**
+ * 鏄惁涓虹鎴风鐞嗗憳
+ *
+ * @param rolePermission 瑙掕壊鏉冮檺鏍囪瘑缁�
+ * @return 缁撴灉
+ */
+ public static boolean isTenantAdmin(Set<String> rolePermission) {
+ if (CollUtil.isEmpty(rolePermission)) {
+ return false;
+ }
+ return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY);
+ }
+
+ /**
+ * 鏄惁涓虹鎴风鐞嗗憳
+ *
+ * @return 缁撴灉
+ */
+ public static boolean isTenantAdmin() {
+ LoginUser loginUser = getLoginUser();
+ if (loginUser == null) {
+ return false;
+ }
+ return Convert.toBool(isTenantAdmin(loginUser.getRolePermission()));
+ }
+
+ /**
+ * 妫�鏌ュ綋鍓嶇敤鎴锋槸鍚﹀凡鐧诲綍
+ *
+ * @return 缁撴灉
+ */
+ public static boolean isLogin() {
+ try {
+ StpUtil.checkLogin();
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..6dd284f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.satoken.config.SaTokenConfig
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/resources/common-satoken.yml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/resources/common-satoken.yml
new file mode 100755
index 0000000..95e1b41
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-satoken/src/main/resources/common-satoken.yml
@@ -0,0 +1,13 @@
+# 鍐呯疆閰嶇疆 涓嶅厑璁镐慨鏀� 濡傞渶淇敼璇峰湪 nacos 涓婂啓鐩稿悓閰嶇疆瑕嗙洊
+# Sa-Token閰嶇疆
+sa-token:
+ # 鍏佽鍔ㄦ�佽缃� token 鏈夋晥鏈�
+ dynamic-active-timeout: true
+ # 鍏佽浠� 璇锋眰鍙傛暟 璇诲彇 token
+ is-read-body: true
+ # 鍏佽浠� header 璇诲彇 token
+ is-read-header: true
+ # 鍏抽棴 cookie 閴存潈 浠庢牴婧愭潨缁� csrf 婕忔礊椋庨櫓
+ is-read-cookie: false
+ # token鍓嶇紑
+ token-prefix: "Bearer"
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/.flattened-pom.xml
new file mode 100644
index 0000000..9cfc494
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/.flattened-pom.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-security</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-security 瀹夊叏妯″潡</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-satoken</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/pom.xml
new file mode 100755
index 0000000..5b39df2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/pom.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-security</artifactId>
+
+ <description>
+ ruoyi-common-security 瀹夊叏妯″潡
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-satoken</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java
new file mode 100755
index 0000000..df53537
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java
@@ -0,0 +1,108 @@
+package org.dromara.common.security.config;
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.filter.SaServletFilter;
+import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
+import cn.dev33.satoken.interceptor.SaInterceptor;
+import cn.dev33.satoken.router.SaRouter;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.util.SaResult;
+import cn.dev33.satoken.util.SaTokenConsts;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.HttpStatus;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.security.config.properties.SecurityProperties;
+import org.dromara.common.security.handler.AllUrlHandler;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * 鏉冮檺瀹夊叏閰嶇疆
+ *
+ * @author Lion Li
+ */
+
+@Slf4j
+@AutoConfiguration
+@EnableConfigurationProperties(SecurityProperties.class)
+@RequiredArgsConstructor
+public class SecurityConfig implements WebMvcConfigurer {
+
+ private final SecurityProperties securityProperties;
+ @Value("${sse.path}")
+ private String ssePath;
+
+ /**
+ * 娉ㄥ唽sa-token鐨勬嫤鎴櫒
+ */
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ // 娉ㄥ唽璺敱鎷︽埅鍣紝鑷畾涔夐獙璇佽鍒�
+ registry.addInterceptor(new SaInterceptor(handler -> {
+ AllUrlHandler allUrlHandler = SpringUtils.getBean(AllUrlHandler.class);
+ // 鐧诲綍楠岃瘉 -- 鎺掗櫎澶氫釜璺緞
+ SaRouter
+ // 鑾峰彇鎵�鏈夌殑
+ .match(allUrlHandler.getUrls())
+ // 瀵规湭鎺掗櫎鐨勮矾寰勮繘琛屾鏌�
+ .check(() -> {
+ HttpServletRequest request = ServletUtils.getRequest();
+ HttpServletResponse response = ServletUtils.getResponse();
+ response.setContentType(SaTokenConsts.CONTENT_TYPE_APPLICATION_JSON);
+ // 妫�鏌ユ槸鍚︾櫥褰� 鏄惁鏈塼oken
+ StpUtil.checkLogin();
+
+ // 妫�鏌� header 涓� param 閲岀殑 clientid 涓� token 閲岀殑鏄惁涓�鑷�
+ String headerCid = request.getHeader(LoginHelper.CLIENT_KEY);
+ String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY);
+ String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString();
+ if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) {
+ // token 鏃犳晥
+ throw NotLoginException.newInstance(StpUtil.getLoginType(),
+ "-100", "瀹㈡埛绔疘D涓嶵oken涓嶅尮閰�",
+ StpUtil.getTokenValue());
+ }
+
+ // 鏈夋晥鐜囧奖鍝� 鐢ㄤ簬涓存椂娴嬭瘯
+ // if (log.isDebugEnabled()) {
+ // log.info("鍓╀綑鏈夋晥鏃堕棿: {}", StpUtil.getTokenTimeout());
+ // log.info("涓存椂鏈夋晥鏃堕棿: {}", StpUtil.getTokenActivityTimeout());
+ // }
+
+ });
+ })).addPathPatterns("/**")
+ // 鎺掗櫎涓嶉渶瑕佹嫤鎴殑璺緞
+ .excludePathPatterns(securityProperties.getExcludes())
+ .excludePathPatterns(ssePath);
+ }
+
+ /**
+ * 瀵� actuator 鍋ュ悍妫�鏌ユ帴鍙� 鍋氳处鍙峰瘑鐮侀壌鏉�
+ */
+ @Bean
+ public SaServletFilter getSaServletFilter() {
+ String username = SpringUtils.getProperty("spring.boot.admin.client.username");
+ String password = SpringUtils.getProperty("spring.boot.admin.client.password");
+ return new SaServletFilter()
+ .addInclude("/actuator", "/actuator/**")
+ .setAuth(obj -> {
+ SaHttpBasicUtil.check(username + ":" + password);
+ })
+ .setError(e -> {
+ HttpServletResponse response = ServletUtils.getResponse();
+ response.setContentType(SaTokenConsts.CONTENT_TYPE_APPLICATION_JSON);
+ return SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED);
+ });
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/properties/SecurityProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/properties/SecurityProperties.java
new file mode 100755
index 0000000..be1cc6e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/properties/SecurityProperties.java
@@ -0,0 +1,21 @@
+package org.dromara.common.security.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Security 閰嶇疆灞炴��
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "security")
+public class SecurityProperties {
+
+ /**
+ * 鎺掗櫎璺緞
+ */
+ private String[] excludes;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/handler/AllUrlHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/handler/AllUrlHandler.java
new file mode 100755
index 0000000..a0c6ada
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/handler/AllUrlHandler.java
@@ -0,0 +1,39 @@
+package org.dromara.common.security.handler;
+
+import cn.hutool.core.util.ReUtil;
+import org.dromara.common.core.utils.SpringUtils;
+import lombok.Data;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.util.*;
+import java.util.regex.Pattern;
+
+/**
+ * 鑾峰彇鎵�鏈塙rl閰嶇疆
+ *
+ * @author Lion Li
+ */
+@Data
+public class AllUrlHandler implements InitializingBean {
+
+ private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
+
+ private List<String> urls = new ArrayList<>();
+
+ @Override
+ public void afterPropertiesSet() {
+ Set<String> set = new HashSet<>();
+ RequestMappingHandlerMapping mapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
+ Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
+ map.keySet().forEach(info -> {
+ // 鑾峰彇娉ㄨВ涓婅竟鐨� path 鏇夸唬 path variable 涓� *
+ Objects.requireNonNull(info.getPathPatternsCondition().getPatterns())
+ .forEach(url -> set.add(ReUtil.replaceAll(url.getPatternString(), PATTERN, "*")));
+ });
+ urls.addAll(set);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..6def724
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,2 @@
+org.dromara.common.security.handler.AllUrlHandler
+org.dromara.common.security.config.SecurityConfig
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/.flattened-pom.xml
new file mode 100644
index 0000000..85c14fb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/.flattened-pom.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sensitive</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-sensitive 鑴辨晱妯″潡</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/pom.xml
new file mode 100755
index 0000000..fecdf09
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/pom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-sensitive</artifactId>
+
+ <description>
+ ruoyi-common-sensitive 鑴辨晱妯″潡
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java
new file mode 100755
index 0000000..e75dc5b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java
@@ -0,0 +1,34 @@
+package org.dromara.common.sensitive.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.dromara.common.sensitive.core.SensitiveStrategy;
+import org.dromara.common.sensitive.handler.SensitiveHandler;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 鏁版嵁鑴辨晱娉ㄨВ
+ *
+ * @author zhujie
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@JacksonAnnotationsInside
+@JsonSerialize(using = SensitiveHandler.class)
+public @interface Sensitive {
+ SensitiveStrategy strategy();
+
+ /**
+ * 瑙掕壊鏍囪瘑绗� 澶氫釜瑙掕壊婊¤冻涓�涓嵆鍙�
+ */
+ String[] roleKey() default {};
+
+ /**
+ * 鏉冮檺鏍囪瘑绗� 澶氫釜鏉冮檺婊¤冻涓�涓嵆鍙�
+ */
+ String[] perms() default {};
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveService.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveService.java
new file mode 100755
index 0000000..03a7f9c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveService.java
@@ -0,0 +1,18 @@
+package org.dromara.common.sensitive.core;
+
+/**
+ * 鑴辨晱鏈嶅姟
+ * 榛樿绠$悊鍛樹笉杩囨护
+ * 闇�鑷鏍规嵁涓氬姟閲嶅啓瀹炵幇
+ *
+ * @author Lion Li
+ * @version 3.6.0
+ */
+public interface SensitiveService {
+
+ /**
+ * 鏄惁鑴辨晱
+ */
+ boolean isSensitive(String[] roleKey, String[] perms);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java
new file mode 100755
index 0000000..30de412
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java
@@ -0,0 +1,113 @@
+package org.dromara.common.sensitive.core;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.DesensitizedUtil;
+import lombok.AllArgsConstructor;
+import org.dromara.common.core.utils.DesensitizedUtils;
+
+import java.util.function.Function;
+
+/**
+ * 鑴辨晱绛栫暐
+ *
+ * @author Yjoioooo
+ * @version 3.6.0
+ */
+@AllArgsConstructor
+public enum SensitiveStrategy {
+
+ /**
+ * 韬唤璇佽劚鏁�
+ */
+ ID_CARD(s -> DesensitizedUtil.idCardNum(s, 3, 4)),
+
+ /**
+ * 鎵嬫満鍙疯劚鏁�
+ */
+ PHONE(DesensitizedUtil::mobilePhone),
+
+ /**
+ * 鍦板潃鑴辨晱
+ */
+ ADDRESS(s -> DesensitizedUtil.address(s, 8)),
+
+ /**
+ * 閭鑴辨晱
+ */
+ EMAIL(DesensitizedUtil::email),
+
+ /**
+ * 閾惰鍗�
+ */
+ BANK_CARD(DesensitizedUtil::bankCard),
+
+ /**
+ * 涓枃鍚�
+ */
+ CHINESE_NAME(DesensitizedUtil::chineseName),
+
+ /**
+ * 鍥哄畾鐢佃瘽
+ */
+ FIXED_PHONE(DesensitizedUtil::fixedPhone),
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ USER_ID(s -> Convert.toStr(DesensitizedUtil.userId())),
+
+ /**
+ * 瀵嗙爜
+ */
+ PASSWORD(DesensitizedUtil::password),
+
+ /**
+ * ipv4
+ */
+ IPV4(DesensitizedUtil::ipv4),
+
+ /**
+ * ipv6
+ */
+ IPV6(DesensitizedUtil::ipv6),
+
+ /**
+ * 涓浗澶ч檰杞︾墝锛屽寘鍚櫘閫氳溅杈嗐�佹柊鑳芥簮杞﹁締
+ */
+ CAR_LICENSE(DesensitizedUtil::carLicense),
+
+ /**
+ * 鍙樉绀虹涓�涓瓧绗�
+ */
+ FIRST_MASK(DesensitizedUtil::firstMask),
+
+ /**
+ * 閫氱敤瀛楃涓茶劚鏁�
+ * 鍙厤缃墠鍚庡彲瑙侀暱搴﹀拰涓棿鎺╃爜闀垮害
+ * 榛樿绀轰緥锛氬墠4浣嶅彲瑙侊紝鍚�4浣嶅彲瑙侊紝涓棿鍥哄畾4涓�*
+ */
+ STRING_MASK(s -> DesensitizedUtils.mask(s, 4, 4, 4)),
+
+ /**
+ * 楂樺畨鍏ㄧ骇鍒劚鏁忥紙Token / 绉侀挜锛夛細鍓�2浣嶅彲瑙侊紝鍚�2浣嶅彲瑙侊紝涓棿鍏ㄩ儴鎺╃爜
+ */
+ MASK_HIGH_SECURITY(s -> DesensitizedUtils.maskHighSecurity(s, 2, 2)),
+
+ /**
+ * 娓呯┖涓�""
+ */
+ CLEAR(s -> DesensitizedUtil.clear()),
+
+ /**
+ * 娓呯┖涓簄ull
+ */
+ CLEAR_TO_NULL(s -> DesensitizedUtil.clearToNull());
+
+ //鍙嚜琛屾坊鍔犲叾浠栬劚鏁忕瓥鐣�
+
+ private final Function<String, String> desensitizer;
+
+ public Function<String, String> desensitizer() {
+ return desensitizer;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java
new file mode 100755
index 0000000..d454724
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java
@@ -0,0 +1,58 @@
+package org.dromara.common.sensitive.handler;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.sensitive.annotation.Sensitive;
+import org.dromara.common.sensitive.core.SensitiveService;
+import org.dromara.common.sensitive.core.SensitiveStrategy;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * 鏁版嵁鑴辨晱json搴忓垪鍖栧伐鍏�
+ *
+ * @author Yjoioooo
+ */
+@Slf4j
+public class SensitiveHandler extends JsonSerializer<String> implements ContextualSerializer {
+
+ private SensitiveStrategy strategy;
+ private String[] roleKey;
+ private String[] perms;
+
+ @Override
+ public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ try {
+ SensitiveService sensitiveService = SpringUtils.getBean(SensitiveService.class);
+ if (ObjectUtil.isNotNull(sensitiveService) && sensitiveService.isSensitive(roleKey, perms)) {
+ gen.writeString(strategy.desensitizer().apply(value));
+ } else {
+ gen.writeString(value);
+ }
+ } catch (BeansException e) {
+ log.error("鑴辨晱瀹炵幇涓嶅瓨鍦�, 閲囩敤榛樿澶勭悊 => {}", e.getMessage());
+ gen.writeString(value);
+ }
+ }
+
+ @Override
+ public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
+ Sensitive annotation = property.getAnnotation(Sensitive.class);
+ if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
+ this.strategy = annotation.strategy();
+ this.roleKey = annotation.roleKey();
+ this.perms = annotation.perms();
+ return this;
+ }
+ return prov.findValueSerializer(property.getType(), property);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/.flattened-pom.xml
new file mode 100644
index 0000000..4b18ef6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/.flattened-pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sms</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-sms 鐭俊妯″潡</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara.sms4j</groupId>
+ <artifactId>sms4j-spring-boot-starter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/pom.xml
new file mode 100755
index 0000000..932cb9d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-sms</artifactId>
+
+ <description>
+ ruoyi-common-sms 鐭俊妯″潡
+ </description>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.dromara.sms4j</groupId>
+ <artifactId>sms4j-spring-boot-starter</artifactId>
+ </dependency>
+
+ <!-- RuoYi Common Redis-->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java
new file mode 100755
index 0000000..3a39cc2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java
@@ -0,0 +1,33 @@
+package org.dromara.common.sms.config;
+
+import org.dromara.common.sms.core.dao.PlusSmsDao;
+import org.dromara.common.sms.handler.SmsExceptionHandler;
+import org.dromara.sms4j.api.dao.SmsDao;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+
+/**
+ * 鐭俊閰嶇疆绫�
+ *
+ * @author Feng
+ */
+@AutoConfiguration(after = {RedisAutoConfiguration.class})
+public class SmsAutoConfiguration {
+
+ @Primary
+ @Bean
+ public SmsDao smsDao() {
+ return new PlusSmsDao();
+ }
+
+ /**
+ * 寮傚父澶勭悊鍣�
+ */
+ @Bean
+ public SmsExceptionHandler smsExceptionHandler() {
+ return new SmsExceptionHandler();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/core/dao/PlusSmsDao.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/core/dao/PlusSmsDao.java
new file mode 100755
index 0000000..a757655
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/core/dao/PlusSmsDao.java
@@ -0,0 +1,72 @@
+package org.dromara.common.sms.core.dao;
+
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.sms4j.api.dao.SmsDao;
+
+import java.time.Duration;
+
+/**
+ * SmsDao缂撳瓨閰嶇疆 (浣跨敤妗嗘灦鑷甫RedisUtils瀹炵幇 鍗忚缁熶竴)
+ * <p>涓昏鐢ㄤ簬鐭俊閲嶈瘯鍜屾嫤鎴殑缂撳瓨
+ *
+ * @author Feng
+ */
+public class PlusSmsDao implements SmsDao {
+
+ /**
+ * 瀛樺偍
+ *
+ * @param key 閿�
+ * @param value 鍊�
+ * @param cacheTime 缂撳瓨鏃堕棿锛堝崟浣嶏細绉�)
+ */
+ @Override
+ public void set(String key, Object value, long cacheTime) {
+ RedisUtils.setCacheObject(GlobalConstants.GLOBAL_REDIS_KEY + key, value, Duration.ofSeconds(cacheTime));
+ }
+
+ /**
+ * 瀛樺偍
+ *
+ * @param key 閿�
+ * @param value 鍊�
+ */
+ @Override
+ public void set(String key, Object value) {
+ RedisUtils.setCacheObject(GlobalConstants.GLOBAL_REDIS_KEY + key, value, true);
+ }
+
+ /**
+ * 璇诲彇
+ *
+ * @param key 閿�
+ * @return 鍊�
+ */
+ @Override
+ public Object get(String key) {
+ return RedisUtils.getCacheObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
+ }
+
+ /**
+ * remove
+ * <p> 鏍规嵁key绉婚櫎缂撳瓨
+ *
+ * @param key 缂撳瓨閿�
+ * @return 琚垹闄ょ殑value
+ * @author :Wind
+ */
+ @Override
+ public Object remove(String key) {
+ return RedisUtils.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
+ }
+
+ /**
+ * 娓呯┖
+ */
+ @Override
+ public void clean() {
+ RedisUtils.deleteKeys(GlobalConstants.GLOBAL_REDIS_KEY + "sms:*");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/handler/SmsExceptionHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/handler/SmsExceptionHandler.java
new file mode 100755
index 0000000..2c619a3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/handler/SmsExceptionHandler.java
@@ -0,0 +1,30 @@
+package org.dromara.common.sms.handler;
+
+import cn.hutool.http.HttpStatus;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.dromara.sms4j.comm.exception.SmsBlendException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * SMS寮傚父澶勭悊鍣�
+ *
+ * @author AprilWind
+ */
+@Slf4j
+@RestControllerAdvice
+public class SmsExceptionHandler {
+
+ /**
+ * sms寮傚父
+ */
+ @ExceptionHandler(SmsBlendException.class)
+ public R<Void> handleSmsBlendException(SmsBlendException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("璇锋眰鍦板潃'{}',鍙戠敓sms鐭俊寮傚父.", requestURI, e);
+ return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "鐭俊鍙戦�佸け璐ワ紝璇风◢鍚庡啀璇�...");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..5919ce3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.sms.config.SmsAutoConfiguration
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/.flattened-pom.xml
new file mode 100644
index 0000000..8ceb7e4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/.flattened-pom.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-social</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-social 鎺堟潈璁よ瘉</description>
+ <dependencies>
+ <dependency>
+ <groupId>me.zhyd.oauth</groupId>
+ <artifactId>JustAuth</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/pom.xml
new file mode 100755
index 0000000..9f9a965
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="http://maven.apache.org/POM/4.0.0"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-social</artifactId>
+
+ <description>
+ ruoyi-common-social 鎺堟潈璁よ瘉
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>me.zhyd.oauth</groupId>
+ <artifactId>JustAuth</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/me/zhyd/oauth/request/AbstractAuthWeChatEnterpriseRequest.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/me/zhyd/oauth/request/AbstractAuthWeChatEnterpriseRequest.java
new file mode 100755
index 0000000..f2a1f7b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/me/zhyd/oauth/request/AbstractAuthWeChatEnterpriseRequest.java
@@ -0,0 +1,154 @@
+package me.zhyd.oauth.request;
+
+import com.alibaba.fastjson.JSONObject;
+import me.zhyd.oauth.cache.AuthStateCache;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.config.AuthSource;
+import me.zhyd.oauth.enums.AuthResponseStatus;
+import me.zhyd.oauth.enums.AuthUserGender;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.utils.HttpUtils;
+import me.zhyd.oauth.utils.StringUtils;
+import me.zhyd.oauth.utils.UrlBuilder;
+
+/**
+ * <p>
+ * 浼佷笟寰俊鐧诲綍鐖剁被
+ * </p>
+ *
+ * @author liguanhua (347826496(a)qq.com)
+ * @since 1.15.9
+ */
+public abstract class AbstractAuthWeChatEnterpriseRequest extends AuthDefaultRequest {
+
+ public AbstractAuthWeChatEnterpriseRequest(AuthConfig config, AuthSource source) {
+ super(config,source);
+ }
+
+
+ public AbstractAuthWeChatEnterpriseRequest(AuthConfig config, AuthSource source, AuthStateCache authStateCache) {
+ super(config, source, authStateCache);
+ }
+
+ @Override
+ public AuthToken getAccessToken(AuthCallback authCallback) {
+ String response = doGetAuthorizationCode(accessTokenUrl(null));
+
+ JSONObject object = this.checkResponse(response);
+
+ return AuthToken.builder()
+ .accessToken(object.getString("access_token"))
+ .expireIn(object.getIntValue("expires_in"))
+ .code(authCallback.getCode())
+ .build();
+ }
+
+ @Override
+ public AuthUser getUserInfo(AuthToken authToken) {
+ String response = doGetUserInfo(authToken);
+ JSONObject object = this.checkResponse(response);
+
+ // 杩斿洖 OpenId 鎴栧叾浠栵紝鍧囦唬琛ㄩ潪褰撳墠浼佷笟鐢ㄦ埛锛屼笉鏀寔
+ // https://github.com/justauth/JustAuth/issues/227 淇bug
+ if (!object.containsKey("userid")) {
+ throw new AuthException(AuthResponseStatus.UNIDENTIFIED_PLATFORM, source);
+ }
+ String userId = object.getString("userid");
+ String userTicket = object.getString("user_ticket");
+ JSONObject userDetail = getUserDetail(authToken.getAccessToken(), userId, userTicket);
+
+ return AuthUser.builder()
+ .rawUserInfo(userDetail)
+ .username(userDetail.getString("name"))
+ .nickname(userDetail.getString("alias"))
+ .avatar(userDetail.getString("avatar"))
+ .location(userDetail.getString("address"))
+ .email(userDetail.getString("email"))
+ .uuid(userId)
+ .gender(AuthUserGender.getWechatRealGender(userDetail.getString("gender")))
+ .token(authToken)
+ .source(source.toString())
+ .build();
+ }
+
+ /**
+ * 鏍¢獙璇锋眰缁撴灉
+ *
+ * @param response 璇锋眰缁撴灉
+ * @return 濡傛灉璇锋眰缁撴灉姝e父锛屽垯杩斿洖JSONObject
+ */
+ private JSONObject checkResponse(String response) {
+ JSONObject object = JSONObject.parseObject(response);
+
+ if (object.containsKey("errcode") && object.getIntValue("errcode") != 0) {
+ throw new AuthException(object.getString("errmsg"), source);
+ }
+
+ return object;
+ }
+
+
+ /**
+ * 杩斿洖鑾峰彇accessToken鐨剈rl
+ *
+ * @param code 鎺堟潈鐮�
+ * @return 杩斿洖鑾峰彇accessToken鐨剈rl
+ */
+ @Override
+ protected String accessTokenUrl(String code) {
+ return UrlBuilder.fromBaseUrl(source.accessToken())
+ .queryParam("corpid", config.getClientId())
+ .queryParam("corpsecret", config.getClientSecret())
+ .build();
+ }
+
+ /**
+ * 杩斿洖鑾峰彇userInfo鐨剈rl
+ *
+ * @param authToken 鐢ㄦ埛鎺堟潈鍚庣殑token
+ * @return 杩斿洖鑾峰彇userInfo鐨剈rl
+ */
+ @Override
+ protected String userInfoUrl(AuthToken authToken) {
+ return UrlBuilder.fromBaseUrl(source.userInfo())
+ .queryParam("access_token", authToken.getAccessToken())
+ .queryParam("code", authToken.getCode())
+ .build();
+ }
+
+ /**
+ * 鐢ㄦ埛璇︽儏
+ *
+ * @param accessToken accessToken
+ * @param userId 浼佷笟鍐呯敤鎴穒d
+ * @param userTicket 鎴愬憳绁ㄦ嵁锛岀敤浜庤幏鍙栫敤鎴蜂俊鎭垨鏁忔劅淇℃伅
+ * @return 鐢ㄦ埛璇︽儏
+ */
+ private JSONObject getUserDetail(String accessToken, String userId, String userTicket) {
+ // 鐢ㄦ埛鍩虹淇℃伅
+ String userInfoUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/user/get")
+ .queryParam("access_token", accessToken)
+ .queryParam("userid", userId)
+ .build();
+ String userInfoResponse = new HttpUtils(config.getHttpConfig()).get(userInfoUrl).getBody();
+ JSONObject userInfo = checkResponse(userInfoResponse);
+
+ // 鐢ㄦ埛鏁忔劅淇℃伅
+ if (StringUtils.isNotEmpty(userTicket)) {
+ String userDetailUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail")
+ .queryParam("access_token", accessToken)
+ .build();
+ JSONObject param = new JSONObject();
+ param.put("user_ticket", userTicket);
+ String userDetailResponse = new HttpUtils(config.getHttpConfig()).post(userDetailUrl, param.toJSONString()).getBody();
+ JSONObject userDetail = checkResponse(userDetailResponse);
+
+ userInfo.putAll(userDetail);
+ }
+ return userInfo;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/me/zhyd/oauth/request/AuthDingTalkV2Request.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/me/zhyd/oauth/request/AuthDingTalkV2Request.java
new file mode 100755
index 0000000..86532d4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/me/zhyd/oauth/request/AuthDingTalkV2Request.java
@@ -0,0 +1,109 @@
+package me.zhyd.oauth.request;
+
+import com.alibaba.fastjson.JSONObject;
+import com.xkcoding.http.support.HttpHeader;
+import me.zhyd.oauth.cache.AuthStateCache;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.config.AuthDefaultSource;
+import me.zhyd.oauth.enums.scope.AuthDingTalkScope;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.utils.AuthScopeUtils;
+import me.zhyd.oauth.utils.GlobalAuthUtils;
+import me.zhyd.oauth.utils.HttpUtils;
+import me.zhyd.oauth.utils.UrlBuilder;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 鏂扮増閽夐拤浜岀淮鐮佺櫥褰�
+ *
+ * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
+ * @since 1.16.7
+ */
+public class AuthDingTalkV2Request extends AuthDefaultRequest {
+
+ public AuthDingTalkV2Request(AuthConfig config) {
+ super(config, AuthDefaultSource.DINGTALK_V2);
+ }
+
+ public AuthDingTalkV2Request(AuthConfig config, AuthStateCache authStateCache) {
+ super(config, AuthDefaultSource.DINGTALK_V2, authStateCache);
+ }
+
+ @Override
+ public String authorize(String state) {
+ return UrlBuilder.fromBaseUrl(source.authorize())
+ .queryParam("response_type", "code")
+ .queryParam("client_id", config.getClientId())
+ .queryParam("scope", this.getScopes(",", true, AuthScopeUtils.getDefaultScopes(AuthDingTalkScope.values())))
+ .queryParam("redirect_uri", GlobalAuthUtils.urlEncode(config.getRedirectUri()))
+ .queryParam("prompt", "consent")
+ .queryParam("org_type", config.getDingTalkOrgType())
+ .queryParam("corpId", config.getDingTalkCorpId())
+ .queryParam("exclusiveLogin", config.isDingTalkExclusiveLogin())
+ .queryParam("exclusiveCorpId", config.getDingTalkExclusiveCorpId())
+ .queryParam("state", getRealState(state))
+ .build();
+ }
+
+ @Override
+ public AuthToken getAccessToken(AuthCallback authCallback) {
+ Map<String, String> params = new HashMap<>();
+ params.put("grantType", "authorization_code");
+ params.put("clientId", config.getClientId());
+ params.put("clientSecret", config.getClientSecret());
+ params.put("code", authCallback.getCode());
+ String response = new HttpUtils(config.getHttpConfig()).post(this.source.accessToken(), JSONObject.toJSONString(params)).getBody();
+ JSONObject accessTokenObject = JSONObject.parseObject(response);
+ if (!accessTokenObject.containsKey("accessToken")) {
+ throw new AuthException(JSONObject.toJSONString(response), source);
+ }
+ return AuthToken.builder()
+ .accessToken(accessTokenObject.getString("accessToken"))
+ .refreshToken(accessTokenObject.getString("refreshToken"))
+ .expireIn(accessTokenObject.getIntValue("expireIn"))
+ .corpId(accessTokenObject.getString("corpId"))
+ .build();
+ }
+
+ @Override
+ public AuthUser getUserInfo(AuthToken authToken) {
+ HttpHeader header = new HttpHeader();
+ header.add("x-acs-dingtalk-access-token", authToken.getAccessToken());
+
+ String response = new HttpUtils(config.getHttpConfig()).get(this.source.userInfo(), null, header, false).getBody();
+ JSONObject object = JSONObject.parseObject(response);
+
+ authToken.setOpenId(object.getString("openId"));
+ authToken.setUnionId(object.getString("unionId"));
+ return AuthUser.builder()
+ .rawUserInfo(object)
+ .uuid(object.getString("unionId"))
+ .username(object.getString("nick"))
+ .nickname(object.getString("nick"))
+ .avatar(object.getString("avatarUrl"))
+ .snapshotUser(object.getBooleanValue("visitor"))
+ .token(authToken)
+ .source(source.toString())
+ .build();
+ }
+
+ /**
+ * 杩斿洖鑾峰彇accessToken鐨剈rl
+ *
+ * @param code 鎺堟潈鐮�
+ * @return 杩斿洖鑾峰彇accessToken鐨剈rl
+ */
+ protected String accessTokenUrl(String code) {
+ return UrlBuilder.fromBaseUrl(source.accessToken())
+ .queryParam("code", code)
+ .queryParam("clientId", config.getClientId())
+ .queryParam("clientSecret", config.getClientSecret())
+ .queryParam("grantType", "authorization_code")
+ .build();
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java
new file mode 100755
index 0000000..19b39d8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java
@@ -0,0 +1,23 @@
+package org.dromara.common.social.config;
+
+import me.zhyd.oauth.cache.AuthStateCache;
+import org.dromara.common.social.config.properties.SocialProperties;
+import org.dromara.common.social.utils.AuthRedisStateCache;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Social 閰嶇疆灞炴��
+ * @author thiszhc
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(SocialProperties.class)
+public class SocialAutoConfiguration {
+
+ @Bean
+ public AuthStateCache authStateCache() {
+ return new AuthRedisStateCache();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java
new file mode 100755
index 0000000..667be4d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java
@@ -0,0 +1,80 @@
+package org.dromara.common.social.config.properties;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 绀句氦鐧诲綍閰嶇疆
+ *
+ * @author thiszhc
+ */
+@Data
+public class SocialLoginConfigProperties {
+
+ /**
+ * 搴旂敤 ID
+ */
+ private String clientId;
+
+ /**
+ * 搴旂敤瀵嗛挜
+ */
+ private String clientSecret;
+
+ /**
+ * 鍥炶皟鍦板潃
+ */
+ private String redirectUri;
+
+ /**
+ * 鏄惁闇�瑕佺敵璇穟nionid锛岀洰鍓嶅彧閽堝qq鐧诲綍
+ */
+ private Boolean unionId;
+
+ /**
+ * Microsoft Entra ID锛堝師寰蒋 AAD锛変腑鐨勭鎴� ID
+ */
+ private String tenantId;
+
+ /**
+ * Coding 浼佷笟鍚嶇О
+ */
+ private String codingGroupName;
+
+ /**
+ * 鏀粯瀹濆叕閽�
+ */
+ private String alipayPublicKey;
+
+ /**
+ * 浼佷笟寰俊搴旂敤ID
+ */
+ private String agentId;
+
+ /**
+ * stackoverflow api key
+ */
+ private String stackOverflowKey;
+
+ /**
+ * 璁惧ID
+ */
+ private String deviceId;
+
+ /**
+ * 瀹㈡埛绔郴缁熺被鍨�
+ */
+ private String clientOsType;
+
+ /**
+ * maxkey 鏈嶅姟鍣ㄥ湴鍧�
+ */
+ private String serverUrl;
+
+ /**
+ * 璇锋眰鑼冨洿
+ */
+ private List<String> scopes;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java
new file mode 100755
index 0000000..1487a6a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java
@@ -0,0 +1,24 @@
+package org.dromara.common.social.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * Social 閰嶇疆灞炴��
+ *
+ * @author thiszhc
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "justauth")
+public class SocialProperties {
+
+ /**
+ * 鎺堟潈绫诲瀷
+ */
+ private Map<String, SocialLoginConfigProperties> type;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/gitea/AuthGiteaRequest.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/gitea/AuthGiteaRequest.java
new file mode 100755
index 0000000..d3fc751
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/gitea/AuthGiteaRequest.java
@@ -0,0 +1,92 @@
+package org.dromara.common.social.gitea;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.cache.AuthStateCache;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+
+/**
+ * @author lcry
+ */
+@Slf4j
+public class AuthGiteaRequest extends AuthDefaultRequest {
+
+ public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.gitea.server-url");
+
+ /**
+ * 璁惧畾褰掑睘鍩�
+ */
+ public AuthGiteaRequest(AuthConfig config) {
+ super(config, AuthGiteaSource.GITEA);
+ }
+
+ public AuthGiteaRequest(AuthConfig config, AuthStateCache authStateCache) {
+ super(config, AuthGiteaSource.GITEA, authStateCache);
+ }
+
+ @Override
+ public AuthToken getAccessToken(AuthCallback authCallback) {
+ String body = doPostAuthorizationCode(authCallback.getCode());
+ Dict object = JsonUtils.parseMap(body);
+ // oauth/token 楠岃瘉寮傚父
+ if (object.containsKey("error")) {
+ throw new AuthException(object.getStr("error_description"));
+ }
+ // user 楠岃瘉寮傚父
+ if (object.containsKey("message")) {
+ throw new AuthException(object.getStr("message"));
+ }
+ return AuthToken.builder()
+ .accessToken(object.getStr("access_token"))
+ .refreshToken(object.getStr("refresh_token"))
+ .idToken(object.getStr("id_token"))
+ .tokenType(object.getStr("token_type"))
+ .scope(object.getStr("scope"))
+ .build();
+ }
+
+ @Override
+ protected String doPostAuthorizationCode(String code) {
+ HttpRequest request = HttpRequest.post(source.accessToken())
+ .form("client_id", config.getClientId())
+ .form("client_secret", config.getClientSecret())
+ .form("grant_type", "authorization_code")
+ .form("code", code)
+ .form("redirect_uri", config.getRedirectUri());
+ HttpResponse response = request.execute();
+ return response.body();
+ }
+
+ @Override
+ public AuthUser getUserInfo(AuthToken authToken) {
+ String body = doGetUserInfo(authToken);
+ Dict object = JsonUtils.parseMap(body);
+ // oauth/token 楠岃瘉寮傚父
+ if (object.containsKey("error")) {
+ throw new AuthException(object.getStr("error_description"));
+ }
+ // user 楠岃瘉寮傚父
+ if (object.containsKey("message")) {
+ throw new AuthException(object.getStr("message"));
+ }
+ return AuthUser.builder()
+ .uuid(object.getStr("sub"))
+ .username(object.getStr("name"))
+ .nickname(object.getStr("preferred_username"))
+ .avatar(object.getStr("picture"))
+ .email(object.getStr("email"))
+ .token(authToken)
+ .source(source.toString())
+ .build();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/gitea/AuthGiteaSource.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/gitea/AuthGiteaSource.java
new file mode 100755
index 0000000..201b223
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/gitea/AuthGiteaSource.java
@@ -0,0 +1,50 @@
+package org.dromara.common.social.gitea;
+
+import me.zhyd.oauth.config.AuthSource;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+
+/**
+ * gitea Oauth2 榛樿鎺ュ彛璇存槑
+ *
+ * @author lcry
+ */
+public enum AuthGiteaSource implements AuthSource {
+
+ /**
+ * 鑷繁鎼缓鐨� gitea 绉佹湇
+ */
+ GITEA {
+ /**
+ * 鎺堟潈鐨刟pi
+ */
+ @Override
+ public String authorize() {
+ return AuthGiteaRequest.SERVER_URL + "/login/oauth/authorize";
+ }
+
+ /**
+ * 鑾峰彇accessToken鐨刟pi
+ */
+ @Override
+ public String accessToken() {
+ return AuthGiteaRequest.SERVER_URL + "/login/oauth/access_token";
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛淇℃伅鐨刟pi
+ */
+ @Override
+ public String userInfo() {
+ return AuthGiteaRequest.SERVER_URL + "/login/oauth/userinfo";
+ }
+
+ /**
+ * 骞冲彴瀵瑰簲鐨� AuthRequest 瀹炵幇绫伙紝蹇呴』缁ф壙鑷� {@link AuthDefaultRequest}
+ */
+ @Override
+ public Class<? extends AuthDefaultRequest> getTargetClass() {
+ return AuthGiteaRequest.class;
+ }
+
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java
new file mode 100755
index 0000000..97774ac
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java
@@ -0,0 +1,80 @@
+package org.dromara.common.social.maxkey;
+
+import cn.hutool.core.lang.Dict;
+import me.zhyd.oauth.cache.AuthStateCache;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+
+/**
+ * @author 闀挎槬鍙摜 2023骞�03鏈�26鏃�
+ */
+public class AuthMaxKeyRequest extends AuthDefaultRequest {
+
+ public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.maxkey.server-url");
+
+ /**
+ * 璁惧畾褰掑睘鍩�
+ */
+ public AuthMaxKeyRequest(AuthConfig config) {
+ super(config, AuthMaxKeySource.MAXKEY);
+ }
+
+ public AuthMaxKeyRequest(AuthConfig config, AuthStateCache authStateCache) {
+ super(config, AuthMaxKeySource.MAXKEY, authStateCache);
+ }
+
+ @Override
+ public AuthToken getAccessToken(AuthCallback authCallback) {
+ String body = doPostAuthorizationCode(authCallback.getCode());
+ Dict object = JsonUtils.parseMap(body);
+ // oauth/token 楠岃瘉寮傚父
+ if (object.containsKey("error")) {
+ throw new AuthException(object.getStr("error_description"));
+ }
+ // user 楠岃瘉寮傚父
+ if (object.containsKey("message")) {
+ throw new AuthException(object.getStr("message"));
+ }
+ return AuthToken.builder()
+ .accessToken(object.getStr("access_token"))
+ .refreshToken(object.getStr("refresh_token"))
+ .idToken(object.getStr("id_token"))
+ .tokenType(object.getStr("token_type"))
+ .scope(object.getStr("scope"))
+ .build();
+ }
+
+ @Override
+ public AuthUser getUserInfo(AuthToken authToken) {
+ String body = doGetUserInfo(authToken);
+ Dict object = JsonUtils.parseMap(body);
+ // oauth/token 楠岃瘉寮傚父
+ if (object.containsKey("error")) {
+ throw new AuthException(object.getStr("error_description"));
+ }
+ // user 楠岃瘉寮傚父
+ if (object.containsKey("message")) {
+ throw new AuthException(object.getStr("message"));
+ }
+ return AuthUser.builder()
+ .uuid(object.getStr("userId"))
+ .username(object.getStr("username"))
+ .nickname(object.getStr("displayName"))
+ .avatar(object.getStr("avatar_url"))
+ .blog(object.getStr("web_url"))
+ .company(object.getStr("organization"))
+ .location(object.getStr("location"))
+ .email(object.getStr("email"))
+ .remark(object.getStr("bio"))
+ .token(authToken)
+ .source(source.toString())
+ .build();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java
new file mode 100755
index 0000000..1ff57f7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java
@@ -0,0 +1,52 @@
+package org.dromara.common.social.maxkey;
+
+import me.zhyd.oauth.config.AuthSource;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+
+/**
+ * Oauth2 榛樿鎺ュ彛璇存槑
+ *
+ * @author 闀挎槬鍙摜 2023骞�03鏈�26鏃�
+ *
+ */
+public enum AuthMaxKeySource implements AuthSource {
+
+ /**
+ * 鑷繁鎼缓鐨� maxkey 绉佹湇
+ */
+ MAXKEY {
+
+ /**
+ * 鎺堟潈鐨刟pi
+ */
+ @Override
+ public String authorize() {
+ return AuthMaxKeyRequest.SERVER_URL + "/sign/authz/oauth/v20/authorize";
+ }
+
+ /**
+ * 鑾峰彇accessToken鐨刟pi
+ */
+ @Override
+ public String accessToken() {
+ return AuthMaxKeyRequest.SERVER_URL + "/sign/authz/oauth/v20/token";
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛淇℃伅鐨刟pi
+ */
+ @Override
+ public String userInfo() {
+ return AuthMaxKeyRequest.SERVER_URL + "/sign/api/oauth/v20/me";
+ }
+
+ /**
+ * 骞冲彴瀵瑰簲鐨� AuthRequest 瀹炵幇绫伙紝蹇呴』缁ф壙鑷� {@link AuthDefaultRequest}
+ */
+ @Override
+ public Class<? extends AuthDefaultRequest> getTargetClass() {
+ return AuthMaxKeyRequest.class;
+ }
+
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamRequest.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamRequest.java
new file mode 100755
index 0000000..080c97a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamRequest.java
@@ -0,0 +1,113 @@
+package org.dromara.common.social.topiam;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import com.xkcoding.http.support.HttpHeader;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.cache.AuthStateCache;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+import me.zhyd.oauth.utils.HttpUtils;
+import me.zhyd.oauth.utils.UrlBuilder;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+
+import static org.dromara.common.social.topiam.AuthTopIamSource.TOPIAM;
+
+/**
+ * TopIAM 璁よ瘉璇锋眰
+ *
+ * @author xlsea
+ * @since 2024-01-06
+ */
+@Slf4j
+public class AuthTopIamRequest extends AuthDefaultRequest {
+
+ public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.topiam.server-url");
+
+ /**
+ * 璁惧畾褰掑睘鍩�
+ */
+ public AuthTopIamRequest(AuthConfig config) {
+ super(config, TOPIAM);
+ }
+
+ public AuthTopIamRequest(AuthConfig config, AuthStateCache authStateCache) {
+ super(config, TOPIAM, authStateCache);
+ }
+
+ @Override
+ public AuthToken getAccessToken(AuthCallback authCallback) {
+ String body = doPostAuthorizationCode(authCallback.getCode());
+ Dict object = JsonUtils.parseMap(body);
+ checkResponse(object);
+ return AuthToken.builder()
+ .accessToken(object.getStr("access_token"))
+ .refreshToken(object.getStr("refresh_token"))
+ .idToken(object.getStr("id_token"))
+ .tokenType(object.getStr("token_type"))
+ .scope(object.getStr("scope"))
+ .build();
+ }
+
+ @Override
+ public AuthUser getUserInfo(AuthToken authToken) {
+ String body = doGetUserInfo(authToken);
+ Dict object = JsonUtils.parseMap(body);
+ checkResponse(object);
+ return AuthUser.builder()
+ .uuid(object.getStr("sub"))
+ .username(object.getStr("preferred_username"))
+ .nickname(object.getStr("nickname"))
+ .avatar(object.getStr("picture"))
+ .email(object.getStr("email"))
+ .token(authToken)
+ .source(source.toString())
+ .build();
+ }
+
+ @Override
+ protected String doPostAuthorizationCode(String code) {
+ HttpRequest request = HttpRequest.post(source.accessToken())
+ .header("Authorization", "Basic " + Base64.encode("%s:%s".formatted(config.getClientId(), config.getClientSecret())))
+ .form("grant_type", "authorization_code")
+ .form("code", code)
+ .form("redirect_uri", config.getRedirectUri());
+ HttpResponse response = request.execute();
+ return response.body();
+ }
+
+ @Override
+ protected String doGetUserInfo(AuthToken authToken) {
+ return new HttpUtils(config.getHttpConfig()).get(source.userInfo(), null, new HttpHeader()
+ .add("Content-Type", "application/json")
+ .add("Authorization", "Bearer " + authToken.getAccessToken()), false).getBody();
+ }
+
+
+ @Override
+ public String authorize(String state) {
+ return UrlBuilder.fromBaseUrl(super.authorize(state))
+ .queryParam("scope", StrUtil.join("%20", config.getScopes()))
+ .build();
+ }
+
+ private static void checkResponse(Dict object) {
+ // oauth/token 楠岃瘉寮傚父
+ if (object.containsKey("error")) {
+ throw new AuthException(object.getStr("error_description"));
+ }
+ // user 楠岃瘉寮傚父
+ if (object.containsKey("message")) {
+ throw new AuthException(object.getStr("message"));
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamSource.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamSource.java
new file mode 100755
index 0000000..852d7f5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/topiam/AuthTopIamSource.java
@@ -0,0 +1,51 @@
+package org.dromara.common.social.topiam;
+
+import me.zhyd.oauth.config.AuthSource;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+
+/**
+ * Oauth2 榛樿鎺ュ彛璇存槑
+ *
+ * @author xlsea
+ * @since 2024-01-06
+ */
+public enum AuthTopIamSource implements AuthSource {
+
+ /**
+ * 娴嬭瘯
+ */
+ TOPIAM {
+ /**
+ * 鎺堟潈鐨刟pi
+ */
+ @Override
+ public String authorize() {
+ return AuthTopIamRequest.SERVER_URL + "/oauth2/auth";
+ }
+
+ /**
+ * 鑾峰彇accessToken鐨刟pi
+ */
+ @Override
+ public String accessToken() {
+ return AuthTopIamRequest.SERVER_URL + "/oauth2/token";
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛淇℃伅鐨刟pi
+ */
+ @Override
+ public String userInfo() {
+ return AuthTopIamRequest.SERVER_URL + "/oauth2/userinfo";
+ }
+
+ /**
+ * 骞冲彴瀵瑰簲鐨� AuthRequest 瀹炵幇绫伙紝蹇呴』缁ф壙鑷� {@link AuthDefaultRequest}
+ */
+ @Override
+ public Class<? extends AuthDefaultRequest> getTargetClass() {
+ return AuthTopIamRequest.class;
+ }
+
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java
new file mode 100755
index 0000000..0b6ec20
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java
@@ -0,0 +1,61 @@
+package org.dromara.common.social.utils;
+
+import lombok.AllArgsConstructor;
+import me.zhyd.oauth.cache.AuthStateCache;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.redis.utils.RedisUtils;
+
+import java.time.Duration;
+
+/**
+ * 鎺堟潈鐘舵�佺紦瀛�
+ */
+@AllArgsConstructor
+public class AuthRedisStateCache implements AuthStateCache {
+
+ /**
+ * 瀛樺叆缂撳瓨
+ *
+ * @param key 缂撳瓨key
+ * @param value 缂撳瓨鍐呭
+ */
+ @Override
+ public void cache(String key, String value) {
+ // 鎺堟潈瓒呮椂鏃堕棿 榛樿涓夊垎閽�
+ RedisUtils.setCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key, value, Duration.ofMinutes(3));
+ }
+
+ /**
+ * 瀛樺叆缂撳瓨
+ *
+ * @param key 缂撳瓨key
+ * @param value 缂撳瓨鍐呭
+ * @param timeout 鎸囧畾缂撳瓨杩囨湡鏃堕棿(姣)
+ */
+ @Override
+ public void cache(String key, String value, long timeout) {
+ RedisUtils.setCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key, value, Duration.ofMillis(timeout));
+ }
+
+ /**
+ * 鑾峰彇缂撳瓨鍐呭
+ *
+ * @param key 缂撳瓨key
+ * @return 缂撳瓨鍐呭
+ */
+ @Override
+ public String get(String key) {
+ return RedisUtils.getCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key);
+ }
+
+ /**
+ * 鏄惁瀛樺湪key锛屽鏋滃搴攌ey鐨剉alue鍊煎凡杩囨湡锛屼篃杩斿洖false
+ *
+ * @param key 缂撳瓨key
+ * @return true锛氬瓨鍦╧ey锛屽苟涓攙alue娌¤繃鏈燂紱false锛歬ey涓嶅瓨鍦ㄦ垨鑰呭凡杩囨湡
+ */
+ @Override
+ public boolean containsKey(String key) {
+ return RedisUtils.hasKey(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java
new file mode 100755
index 0000000..df21aa8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java
@@ -0,0 +1,75 @@
+package org.dromara.common.social.utils;
+
+import cn.hutool.core.util.ObjectUtil;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.*;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
+import org.dromara.common.social.config.properties.SocialProperties;
+import org.dromara.common.social.gitea.AuthGiteaRequest;
+import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
+import org.dromara.common.social.topiam.AuthTopIamRequest;
+
+/**
+ * 璁よ瘉鎺堟潈宸ュ叿绫�
+ *
+ * @author thiszhc
+ */
+public class SocialUtils {
+
+ private static final AuthRedisStateCache STATE_CACHE = SpringUtils.getBean(AuthRedisStateCache.class);
+
+ @SuppressWarnings("unchecked")
+ public static AuthResponse<AuthUser> loginAuth(String source, String code, String state, SocialProperties socialProperties) throws AuthException {
+ AuthRequest authRequest = getAuthRequest(source, socialProperties);
+ AuthCallback callback = new AuthCallback();
+ callback.setCode(code);
+ callback.setState(state);
+ return authRequest.login(callback);
+ }
+
+ public static AuthRequest getAuthRequest(String source, SocialProperties socialProperties) throws AuthException {
+ SocialLoginConfigProperties obj = socialProperties.getType().get(source);
+ if (ObjectUtil.isNull(obj)) {
+ throw new AuthException("涓嶆敮鎸佺殑绗笁鏂圭櫥褰曠被鍨�");
+ }
+ AuthConfig.AuthConfigBuilder builder = AuthConfig.builder()
+ .clientId(obj.getClientId())
+ .clientSecret(obj.getClientSecret())
+ .redirectUri(obj.getRedirectUri())
+ .scopes(obj.getScopes());
+ return switch (source.toLowerCase()) {
+ case "dingtalk" -> new AuthDingTalkV2Request(builder.build(), STATE_CACHE);
+ case "baidu" -> new AuthBaiduRequest(builder.build(), STATE_CACHE);
+ case "github" -> new AuthGithubRequest(builder.build(), STATE_CACHE);
+ case "gitee" -> new AuthGiteeRequest(builder.build(), STATE_CACHE);
+ case "weibo" -> new AuthWeiboRequest(builder.build(), STATE_CACHE);
+ case "coding" -> new AuthCodingRequest(builder.build(), STATE_CACHE);
+ case "oschina" -> new AuthOschinaRequest(builder.build(), STATE_CACHE);
+ // 鏀粯瀹濆湪鍒涘缓鍥炶皟鍦板潃鏃讹紝涓嶅厑璁镐娇鐢╨ocalhost鎴栬��127.0.0.1锛屾墍浠ヨ繖鍎跨殑鍥炶皟鍦板潃浣跨敤鐨勫眬鍩熺綉鍐呯殑ip
+ case "alipay_wallet" -> new AuthAlipayRequest(builder.build(), socialProperties.getType().get("alipay_wallet").getAlipayPublicKey(), STATE_CACHE);
+ case "qq" -> new AuthQqRequest(builder.build(), STATE_CACHE);
+ case "wechat_open" -> new AuthWeChatOpenRequest(builder.build(), STATE_CACHE);
+ case "taobao" -> new AuthTaobaoRequest(builder.build(), STATE_CACHE);
+ case "douyin" -> new AuthDouyinRequest(builder.build(), STATE_CACHE);
+ case "linkedin" -> new AuthLinkedinRequest(builder.build(), STATE_CACHE);
+ case "microsoft" -> new AuthMicrosoftRequest(builder.tenantId(obj.getTenantId()).build(), STATE_CACHE);
+ case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE);
+ case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey(obj.getStackOverflowKey()).build(), STATE_CACHE);
+ case "huawei" -> new AuthHuaweiV3Request(builder.build(), STATE_CACHE);
+ case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeV2Request(builder.agentId(obj.getAgentId()).build(), STATE_CACHE);
+ case "gitlab" -> new AuthGitlabRequest(builder.build(), STATE_CACHE);
+ case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
+ case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
+ case "maxkey" -> new AuthMaxKeyRequest(builder.build(), STATE_CACHE);
+ case "topiam" -> new AuthTopIamRequest(builder.build(), STATE_CACHE);
+ case "gitea" -> new AuthGiteaRequest(builder.build(), STATE_CACHE);
+ default -> throw new AuthException("鏈幏鍙栧埌鏈夋晥鐨凙uth閰嶇疆");
+ };
+ }
+}
+
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..fc544a0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.social.config.SocialAutoConfiguration
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/.flattened-pom.xml
new file mode 100644
index 0000000..264eb72
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/.flattened-pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sse</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-sse 妯″潡</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-satoken</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/pom.xml
new file mode 100755
index 0000000..ae44c98
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/pom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-sse</artifactId>
+
+ <description>
+ ruoyi-common-sse 妯″潡
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-satoken</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseAutoConfiguration.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseAutoConfiguration.java
new file mode 100755
index 0000000..0cf8054
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseAutoConfiguration.java
@@ -0,0 +1,36 @@
+package org.dromara.common.sse.config;
+
+import org.dromara.common.sse.controller.SseController;
+import org.dromara.common.sse.core.SseEmitterManager;
+import org.dromara.common.sse.listener.SseTopicListener;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * SSE 鑷姩瑁呴厤
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+@ConditionalOnProperty(value = "sse.enabled", havingValue = "true")
+@EnableConfigurationProperties(SseProperties.class)
+public class SseAutoConfiguration {
+
+ @Bean
+ public SseEmitterManager sseEmitterManager() {
+ return new SseEmitterManager();
+ }
+
+ @Bean
+ public SseTopicListener sseTopicListener() {
+ return new SseTopicListener();
+ }
+
+ @Bean
+ public SseController sseController(SseEmitterManager sseEmitterManager) {
+ return new SseController(sseEmitterManager);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseProperties.java
new file mode 100755
index 0000000..ce4e173
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseProperties.java
@@ -0,0 +1,21 @@
+package org.dromara.common.sse.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * SSE 閰嶇疆椤�
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties("sse")
+public class SseProperties {
+
+ private Boolean enabled;
+
+ /**
+ * 璺緞
+ */
+ private String path;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java
new file mode 100755
index 0000000..f77b5b5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java
@@ -0,0 +1,88 @@
+package org.dromara.common.sse.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.dev33.satoken.stp.StpUtil;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.sse.core.SseEmitterManager;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+/**
+ * SSE 鎺у埗鍣�
+ *
+ * @author Lion Li
+ */
+@RestController
+@ConditionalOnProperty(value = "sse.enabled", havingValue = "true")
+@RequiredArgsConstructor
+public class SseController implements DisposableBean {
+
+ private final SseEmitterManager sseEmitterManager;
+
+ /**
+ * 寤虹珛 SSE 杩炴帴
+ */
+ @GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+ public SseEmitter connect() {
+ if (!StpUtil.isLogin()) {
+ return null;
+ }
+ String tokenValue = StpUtil.getTokenValue();
+ Long userId = LoginHelper.getUserId();
+ return sseEmitterManager.connect(userId, tokenValue);
+ }
+
+ /**
+ * 鍏抽棴 SSE 杩炴帴
+ */
+ @SaIgnore
+ @GetMapping(value = "${sse.path}/close")
+ public R<Void> close() {
+ String tokenValue = StpUtil.getTokenValue();
+ Long userId = LoginHelper.getUserId();
+ sseEmitterManager.disconnect(userId, tokenValue);
+ return R.ok();
+ }
+
+ // 浠ヤ笅涓篸emo浠呬緵鍙傝�� 绂佹浣跨敤 璇峰湪涓氬姟閫昏緫涓娇鐢ㄥ伐鍏峰彂閫佽�屼笉鏄敤鎺ュ彛鍙戦��
+// /**
+// * 鍚戠壒瀹氱敤鎴峰彂閫佹秷鎭�
+// *
+// * @param userId 鐩爣鐢ㄦ埛鐨� ID
+// * @param msg 瑕佸彂閫佺殑娑堟伅鍐呭
+// */
+// @GetMapping(value = "${sse.path}/send")
+// public R<Void> send(Long userId, String msg) {
+// SseMessageDto dto = new SseMessageDto();
+// dto.setUserIds(List.of(userId));
+// dto.setMessage(msg);
+// sseEmitterManager.publishMessage(dto);
+// return R.ok();
+// }
+//
+// /**
+// * 鍚戞墍鏈夌敤鎴峰彂閫佹秷鎭�
+// *
+// * @param msg 瑕佸彂閫佺殑娑堟伅鍐呭
+// */
+// @GetMapping(value = "${sse.path}/sendAll")
+// public R<Void> send(String msg) {
+// sseEmitterManager.publishAll(msg);
+// return R.ok();
+// }
+
+ /**
+ * 娓呯悊璧勬簮銆傛鏂规硶鐩墠涓嶆墽琛屼换浣曟搷浣滐紝浣嗛伩鍏嶅洜鏈疄鐜拌�屽鑷撮敊璇�
+ */
+ @Override
+ public void destroy() throws Exception {
+ // 閿�姣佹椂涓嶉渶瑕佸仛浠�涔� 姝ゆ柟娉曢伩鍏嶆棤鐢ㄦ搷浣滄姤閿�
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java
new file mode 100755
index 0000000..b80e561
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java
@@ -0,0 +1,230 @@
+package org.dromara.common.sse.core;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.sse.dto.SseMessageDto;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * 绠$悊 Server-Sent Events (SSE) 杩炴帴
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class SseEmitterManager {
+
+ /**
+ * 璁㈤槄鐨勯閬�
+ */
+ private final static String SSE_TOPIC = "global:sse";
+
+ private final static Map<Long, Map<String, SseEmitter>> USER_TOKEN_EMITTERS = new ConcurrentHashMap<>();
+
+ public SseEmitterManager() {
+ // 瀹氭椂鎵ц SSE 蹇冭烦妫�娴�
+ SpringUtils.getBean(ScheduledExecutorService.class)
+ .scheduleWithFixedDelay(this::sseMonitor, 60L, 60L, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 寤虹珛涓庢寚瀹氱敤鎴风殑 SSE 杩炴帴
+ *
+ * @param userId 鐢ㄦ埛鐨勫敮涓�鏍囪瘑绗︼紝鐢ㄤ簬鍖哄垎涓嶅悓鐢ㄦ埛鐨勮繛鎺�
+ * @param token 鐢ㄦ埛鐨勫敮涓�浠ょ墝锛岀敤浜庤瘑鍒叿浣撶殑杩炴帴
+ * @return 杩斿洖涓�涓� SseEmitter 瀹炰緥锛屽鎴风鍙互閫氳繃璇ュ疄渚嬫帴鏀� SSE 浜嬩欢
+ */
+ public SseEmitter connect(Long userId, String token) {
+ // 浠� USER_TOKEN_EMITTERS 涓幏鍙栨垨鍒涘缓褰撳墠鐢ㄦ埛鐨� SseEmitter 鏄犲皠琛紙ConcurrentHashMap锛�
+ // 姣忎釜鐢ㄦ埛鍙互鏈夊涓� SSE 杩炴帴锛岄�氳繃 token 杩涜鍖哄垎
+ Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.computeIfAbsent(userId, k -> new ConcurrentHashMap<>());
+
+ // 鍏抽棴宸插瓨鍦ㄧ殑SseEmitter锛岄槻姝㈣秴杩囨渶澶ц繛鎺ユ暟
+ SseEmitter oldEmitter = emitters.remove(token);
+ if (oldEmitter != null) {
+ oldEmitter.complete();
+ }
+
+ // 鍒涘缓涓�涓柊鐨� SseEmitter 瀹炰緥锛岃秴鏃舵椂闂磋缃负涓�澶� 閬垮厤杩炴帴涔嬪悗鐩存帴鍏抽棴娴忚鍣ㄥ鑷磋繛鎺ュ仠婊�
+ SseEmitter emitter = new SseEmitter(86400000L);
+
+ emitters.put(token, emitter);
+
+ // 褰� emitter 瀹屾垚銆佽秴鏃舵垨鍙戠敓閿欒鏃讹紝浠庢槧灏勮〃涓Щ闄ゅ搴旂殑 token
+ emitter.onCompletion(() -> {
+ SseEmitter remove = emitters.remove(token);
+ if (remove != null) {
+ remove.complete();
+ }
+ });
+ emitter.onTimeout(() -> {
+ SseEmitter remove = emitters.remove(token);
+ if (remove != null) {
+ remove.complete();
+ }
+ });
+ emitter.onError((e) -> {
+ SseEmitter remove = emitters.remove(token);
+ if (remove != null) {
+ remove.complete();
+ }
+ });
+
+ try {
+ // 鍚戝鎴风鍙戦�佷竴鏉¤繛鎺ユ垚鍔熺殑浜嬩欢
+ emitter.send(SseEmitter.event().comment("connected"));
+ } catch (IOException e) {
+ // 濡傛灉鍙戦�佹秷鎭け璐ワ紝鍒欎粠鏄犲皠琛ㄤ腑绉婚櫎 emitter
+ emitters.remove(token);
+ }
+ return emitter;
+ }
+
+ /**
+ * 鏂紑鎸囧畾鐢ㄦ埛鐨� SSE 杩炴帴
+ *
+ * @param userId 鐢ㄦ埛鐨勫敮涓�鏍囪瘑绗︼紝鐢ㄤ簬鍖哄垎涓嶅悓鐢ㄦ埛鐨勮繛鎺�
+ * @param token 鐢ㄦ埛鐨勫敮涓�浠ょ墝锛岀敤浜庤瘑鍒叿浣撶殑杩炴帴
+ */
+ public void disconnect(Long userId, String token) {
+ if (userId == null || token == null) {
+ return;
+ }
+ Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
+ if (MapUtil.isNotEmpty(emitters)) {
+ try {
+ SseEmitter sseEmitter = emitters.get(token);
+ sseEmitter.send(SseEmitter.event().comment("disconnected"));
+ sseEmitter.complete();
+ } catch (Exception ignore) {
+ }
+ emitters.remove(token);
+ } else {
+ USER_TOKEN_EMITTERS.remove(userId);
+ }
+ }
+
+ /**
+ * SSE 蹇冭烦妫�娴嬶紝鍏抽棴鏃犳晥杩炴帴
+ */
+ public void sseMonitor() {
+ final SseEmitter.SseEventBuilder heartbeat = SseEmitter.event().comment("heartbeat");
+ // 璁板綍闇�瑕佺Щ闄ょ殑鐢ㄦ埛ID
+ List<Long> toRemoveUsers = new ArrayList<>();
+
+ USER_TOKEN_EMITTERS.forEach((userId, emitterMap) -> {
+ if (CollUtil.isEmpty(emitterMap)) {
+ toRemoveUsers.add(userId);
+ return;
+ }
+
+ emitterMap.entrySet().removeIf(entry -> {
+ try {
+ entry.getValue().send(heartbeat);
+ return false;
+ } catch (Exception ex) {
+ try {
+ entry.getValue().complete();
+ } catch (Exception ignore) {
+ // 蹇界暐閲嶅鍏抽棴寮傚父
+ }
+ return true; // 鍙戦�佸け璐� 鈫� 绉婚櫎璇ヨ繛鎺�
+ }
+ });
+
+ // 绉婚櫎绌鸿繛鎺ョ敤鎴�
+ if (emitterMap.isEmpty()) {
+ toRemoveUsers.add(userId);
+ }
+ });
+
+ // 寰幆缁撴潫鍚庣粺涓�娓呯悊绌虹敤鎴凤紝閬垮厤骞跺彂淇敼寮傚父
+ toRemoveUsers.forEach(USER_TOKEN_EMITTERS::remove);
+ }
+
+ /**
+ * 璁㈤槄SSE娑堟伅涓婚锛屽苟鎻愪緵涓�涓秷璐硅�呭嚱鏁版潵澶勭悊鎺ユ敹鍒扮殑娑堟伅
+ *
+ * @param consumer 澶勭悊SSE娑堟伅鐨勬秷璐硅�呭嚱鏁�
+ */
+ public void subscribeMessage(Consumer<SseMessageDto> consumer) {
+ RedisUtils.subscribe(SSE_TOPIC, SseMessageDto.class, consumer);
+ }
+
+ /**
+ * 鍚戞寚瀹氱殑鐢ㄦ埛浼氳瘽鍙戦�佹秷鎭�
+ *
+ * @param userId 瑕佸彂閫佹秷鎭殑鐢ㄦ埛id
+ * @param message 瑕佸彂閫佺殑娑堟伅鍐呭
+ */
+ public void sendMessage(Long userId, String message) {
+ Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
+ if (MapUtil.isNotEmpty(emitters)) {
+ for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) {
+ try {
+ entry.getValue().send(SseEmitter.event()
+ .name("message")
+ .data(message));
+ } catch (Exception e) {
+ SseEmitter remove = emitters.remove(entry.getKey());
+ if (remove != null) {
+ remove.complete();
+ }
+ }
+ }
+ } else {
+ USER_TOKEN_EMITTERS.remove(userId);
+ }
+ }
+
+ /**
+ * 鏈満鍏ㄧ敤鎴蜂細璇濆彂閫佹秷鎭�
+ *
+ * @param message 瑕佸彂閫佺殑娑堟伅鍐呭
+ */
+ public void sendMessage(String message) {
+ for (Long userId : USER_TOKEN_EMITTERS.keySet()) {
+ sendMessage(userId, message);
+ }
+ }
+
+ /**
+ * 鍙戝竷SSE璁㈤槄娑堟伅
+ *
+ * @param sseMessageDto 瑕佸彂甯冪殑SSE娑堟伅瀵硅薄
+ */
+ public void publishMessage(SseMessageDto sseMessageDto) {
+ SseMessageDto broadcastMessage = new SseMessageDto();
+ broadcastMessage.setMessage(sseMessageDto.getMessage());
+ broadcastMessage.setUserIds(sseMessageDto.getUserIds());
+ RedisUtils.publish(SSE_TOPIC, broadcastMessage, consumer -> {
+ log.info("SSE鍙戦�佷富棰樿闃呮秷鎭痶opic:{} session keys:{} message:{}",
+ SSE_TOPIC, sseMessageDto.getUserIds(), sseMessageDto.getMessage());
+ });
+ }
+
+ /**
+ * 鍚戞墍鏈夌殑鐢ㄦ埛鍙戝竷璁㈤槄鐨勬秷鎭�(缇ゅ彂)
+ *
+ * @param message 瑕佸彂甯冪殑娑堟伅鍐呭
+ */
+ public void publishAll(String message) {
+ SseMessageDto broadcastMessage = new SseMessageDto();
+ broadcastMessage.setMessage(message);
+ RedisUtils.publish(SSE_TOPIC, broadcastMessage, consumer -> {
+ log.info("SSE鍙戦�佷富棰樿闃呮秷鎭痶opic:{} message:{}", SSE_TOPIC, message);
+ });
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/dto/SseMessageDto.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/dto/SseMessageDto.java
new file mode 100755
index 0000000..a2e1210
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/dto/SseMessageDto.java
@@ -0,0 +1,29 @@
+package org.dromara.common.sse.dto;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 娑堟伅鐨刣to
+ *
+ * @author zendwang
+ */
+@Data
+public class SseMessageDto implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 闇�瑕佹帹閫佸埌鐨剆ession key 鍒楄〃
+ */
+ private List<Long> userIds;
+
+ /**
+ * 闇�瑕佸彂閫佺殑娑堟伅
+ */
+ private String message;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/listener/SseTopicListener.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/listener/SseTopicListener.java
new file mode 100755
index 0000000..7a4dff1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/listener/SseTopicListener.java
@@ -0,0 +1,48 @@
+package org.dromara.common.sse.listener;
+
+import cn.hutool.core.collection.CollUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.sse.core.SseEmitterManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.Ordered;
+
+/**
+ * SSE 涓婚璁㈤槄鐩戝惉鍣�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class SseTopicListener implements ApplicationRunner, Ordered {
+
+ @Autowired
+ private SseEmitterManager sseEmitterManager;
+
+ /**
+ * 鍦⊿pring Boot搴旂敤绋嬪簭鍚姩鏃跺垵濮嬪寲SSE涓婚璁㈤槄鐩戝惉鍣�
+ *
+ * @param args 搴旂敤绋嬪簭鍙傛暟
+ * @throws Exception 鍒濆鍖栬繃绋嬩腑鍙兘鎶涘嚭鐨勫紓甯�
+ */
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ sseEmitterManager.subscribeMessage((message) -> {
+ log.info("SSE涓婚璁㈤槄鏀跺埌娑堟伅session keys={} message={}", message.getUserIds(), message.getMessage());
+ // 濡傛灉key涓嶄负绌哄氨鎸夌収key鍙戞秷鎭� 濡傛灉涓虹┖灏辩兢鍙�
+ if (CollUtil.isNotEmpty(message.getUserIds())) {
+ message.getUserIds().forEach(key -> {
+ sseEmitterManager.sendMessage(key, message.getMessage());
+ });
+ } else {
+ sseEmitterManager.sendMessage(message.getMessage());
+ }
+ });
+ log.info("鍒濆鍖朣SE涓婚璁㈤槄鐩戝惉鍣ㄦ垚鍔�");
+ }
+
+ @Override
+ public int getOrder() {
+ return -1;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java
new file mode 100755
index 0000000..ce3aad4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java
@@ -0,0 +1,84 @@
+package org.dromara.common.sse.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.sse.core.SseEmitterManager;
+import org.dromara.common.sse.dto.SseMessageDto;
+
+/**
+ * SSE宸ュ叿绫�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class SseMessageUtils {
+
+ private final static Boolean SSE_ENABLE = SpringUtils.getProperty("sse.enabled", Boolean.class, true);
+ private static SseEmitterManager MANAGER;
+
+ static {
+ if (isEnable() && MANAGER == null) {
+ MANAGER = SpringUtils.getBean(SseEmitterManager.class);
+ }
+ }
+
+ /**
+ * 鍚戞寚瀹氱殑SSE浼氳瘽鍙戦�佹秷鎭�
+ *
+ * @param userId 瑕佸彂閫佹秷鎭殑鐢ㄦ埛id
+ * @param message 瑕佸彂閫佺殑娑堟伅鍐呭
+ */
+ public static void sendMessage(Long userId, String message) {
+ if (!isEnable()) {
+ return;
+ }
+ MANAGER.sendMessage(userId, message);
+ }
+
+ /**
+ * 鏈満鍏ㄧ敤鎴蜂細璇濆彂閫佹秷鎭�
+ *
+ * @param message 瑕佸彂閫佺殑娑堟伅鍐呭
+ */
+ public static void sendMessage(String message) {
+ if (!isEnable()) {
+ return;
+ }
+ MANAGER.sendMessage(message);
+ }
+
+ /**
+ * 鍙戝竷SSE璁㈤槄娑堟伅
+ *
+ * @param sseMessageDto 瑕佸彂甯冪殑SSE娑堟伅瀵硅薄
+ */
+ public static void publishMessage(SseMessageDto sseMessageDto) {
+ if (!isEnable()) {
+ return;
+ }
+ MANAGER.publishMessage(sseMessageDto);
+ }
+
+ /**
+ * 鍚戞墍鏈夌殑鐢ㄦ埛鍙戝竷璁㈤槄鐨勬秷鎭�(缇ゅ彂)
+ *
+ * @param message 瑕佸彂甯冪殑娑堟伅鍐呭
+ */
+ public static void publishAll(String message) {
+ if (!isEnable()) {
+ return;
+ }
+ MANAGER.publishAll(message);
+ }
+
+ /**
+ * 鏄惁寮�鍚�
+ */
+ public static Boolean isEnable() {
+ return SSE_ENABLE;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..b809713
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-sse/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.sse.config.SseAutoConfiguration
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/.flattened-pom.xml
new file mode 100644
index 0000000..da7da3a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/.flattened-pom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-tenant</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-tenant 绉熸埛妯″潡</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/pom.xml
new file mode 100755
index 0000000..8e1a6ab
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-tenant</artifactId>
+
+ <description>
+ ruoyi-common-tenant 绉熸埛妯″潡
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfig.java
new file mode 100755
index 0000000..3767fa2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfig.java
@@ -0,0 +1,86 @@
+package org.dromara.common.tenant.config;
+
+import cn.dev33.satoken.dao.SaTokenDao;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import org.dromara.common.redis.config.RedisConfig;
+import org.dromara.common.redis.config.properties.RedissonProperties;
+import org.dromara.common.tenant.core.TenantSaTokenDao;
+import org.dromara.common.tenant.handle.PlusTenantLineHandler;
+import org.dromara.common.tenant.handle.TenantKeyPrefixHandler;
+import org.dromara.common.tenant.manager.TenantSpringCacheManager;
+import org.dromara.common.tenant.properties.TenantProperties;
+import org.redisson.config.ClusterServersConfig;
+import org.redisson.config.SingleServerConfig;
+import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cache.CacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+
+/**
+ * 绉熸埛閰嶇疆绫�
+ *
+ * @author Lion Li
+ */
+@EnableConfigurationProperties(TenantProperties.class)
+@AutoConfiguration(after = {RedisConfig.class})
+@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
+public class TenantConfig {
+
+ @ConditionalOnClass(TenantLineInnerInterceptor.class)
+ @AutoConfiguration
+ static class MybatisPlusConfiguration {
+
+ /**
+ * 澶氱鎴锋彃浠�
+ */
+ @Bean
+ public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) {
+ return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties));
+ }
+
+ }
+
+ @Bean
+ public RedissonAutoConfigurationCustomizer tenantRedissonCustomizer(RedissonProperties redissonProperties) {
+ return config -> {
+ TenantKeyPrefixHandler nameMapper = new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix());
+ SingleServerConfig singleServerConfig = ReflectUtils.invokeGetter(config, "singleServerConfig");
+ if (ObjectUtil.isNotNull(singleServerConfig)) {
+ // 浣跨敤鍗曟満妯″紡
+ // 璁剧疆澶氱鎴� redis key鍓嶇紑
+ singleServerConfig.setNameMapper(nameMapper);
+ }
+ ClusterServersConfig clusterServersConfig = ReflectUtils.invokeGetter(config, "clusterServersConfig");
+ // 闆嗙兢閰嶇疆鏂瑰紡 鍙傝�冧笅鏂规敞閲�
+ if (ObjectUtil.isNotNull(clusterServersConfig)) {
+ // 璁剧疆澶氱鎴� redis key鍓嶇紑
+ clusterServersConfig.setNameMapper(nameMapper);
+ }
+ };
+ }
+
+ /**
+ * 澶氱鎴风紦瀛樼鐞嗗櫒
+ */
+ @Primary
+ @Bean
+ public CacheManager tenantCacheManager() {
+ return new TenantSpringCacheManager();
+ }
+
+ /**
+ * 澶氱鎴烽壌鏉僤ao瀹炵幇
+ */
+ @Primary
+ @Bean
+ public SaTokenDao tenantSaTokenDao() {
+ return new TenantSaTokenDao();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantEntity.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantEntity.java
new file mode 100755
index 0000000..8ad0d2c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantEntity.java
@@ -0,0 +1,21 @@
+package org.dromara.common.tenant.core;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 绉熸埛鍩虹被
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TenantEntity extends BaseEntity {
+
+ /**
+ * 绉熸埛缂栧彿
+ */
+ private String tenantId;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantSaTokenDao.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantSaTokenDao.java
new file mode 100755
index 0000000..9aaa753
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantSaTokenDao.java
@@ -0,0 +1,158 @@
+package org.dromara.common.tenant.core;
+
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.core.dao.PlusSaTokenDao;
+
+import java.time.Duration;
+import java.util.List;
+
+/**
+ * SaToken 璁よ瘉鏁版嵁鎸佷箙灞� 閫傞厤澶氱鎴�
+ *
+ * @author Lion Li
+ */
+public class TenantSaTokenDao extends PlusSaTokenDao {
+
+ @Override
+ public String get(String key) {
+ return super.get(GlobalConstants.GLOBAL_REDIS_KEY + key);
+ }
+
+ @Override
+ public void set(String key, String value, long timeout) {
+ super.set(GlobalConstants.GLOBAL_REDIS_KEY + key, value, timeout);
+ }
+
+ /**
+ * 淇慨鏀规寚瀹歬ey-value閿�煎 (杩囨湡鏃堕棿涓嶅彉)
+ */
+ @Override
+ public void update(String key, String value) {
+ long expire = getTimeout(key);
+ // -2 = 鏃犳閿�
+ if (expire == NOT_VALUE_EXPIRE) {
+ return;
+ }
+ this.set(key, value, expire);
+ }
+
+ /**
+ * 鍒犻櫎Value
+ */
+ @Override
+ public void delete(String key) {
+ super.delete(GlobalConstants.GLOBAL_REDIS_KEY + key);
+ }
+
+ /**
+ * 鑾峰彇Value鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+ */
+ @Override
+ public long getTimeout(String key) {
+ return super.getTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
+ }
+
+ /**
+ * 淇敼Value鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+ */
+ @Override
+ public void updateTimeout(String key, long timeout) {
+ // 鍒ゆ柇鏄惁鎯宠璁剧疆涓烘案涔�
+ if (timeout == NEVER_EXPIRE) {
+ long expire = getTimeout(key);
+ if (expire == NEVER_EXPIRE) {
+ // 濡傛灉鍏跺凡缁忚璁剧疆涓烘案涔咃紝鍒欎笉浣滀换浣曞鐞�
+ } else {
+ // 濡傛灉灏氭湭琚缃负姘镐箙锛岄偅涔堝啀娆et涓�娆�
+ this.set(key, this.get(key), timeout);
+ }
+ return;
+ }
+ RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
+ }
+
+
+ /**
+ * 鑾峰彇Object锛屽鏃犺繑绌�
+ */
+ @Override
+ public Object getObject(String key) {
+ return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
+ }
+
+ /**
+ * 鑾峰彇 Object (鎸囧畾鍙嶅簭鍒楀寲绫诲瀷)锛屽鏃犺繑绌�
+ *
+ * @param key 閿悕绉�
+ * @return object
+ */
+ @Override
+ public <T> T getObject(String key, Class<T> classType) {
+ return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key, classType);
+ }
+
+ /**
+ * 鍐欏叆Object锛屽苟璁惧畾瀛樻椿鏃堕棿 (鍗曚綅: 绉�)
+ */
+ @Override
+ public void setObject(String key, Object object, long timeout) {
+ super.setObject(GlobalConstants.GLOBAL_REDIS_KEY + key, object, timeout);
+ }
+
+ /**
+ * 鏇存柊Object (杩囨湡鏃堕棿涓嶅彉)
+ */
+ @Override
+ public void updateObject(String key, Object object) {
+ long expire = getObjectTimeout(key);
+ // -2 = 鏃犳閿�
+ if (expire == NOT_VALUE_EXPIRE) {
+ return;
+ }
+ this.setObject(key, object, expire);
+ }
+
+ /**
+ * 鍒犻櫎Object
+ */
+ @Override
+ public void deleteObject(String key) {
+ super.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
+ }
+
+ /**
+ * 鑾峰彇Object鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+ */
+ @Override
+ public long getObjectTimeout(String key) {
+ return super.getObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
+ }
+
+ /**
+ * 淇敼Object鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+ */
+ @Override
+ public void updateObjectTimeout(String key, long timeout) {
+ // 鍒ゆ柇鏄惁鎯宠璁剧疆涓烘案涔�
+ if (timeout == NEVER_EXPIRE) {
+ long expire = getObjectTimeout(key);
+ if (expire == NEVER_EXPIRE) {
+ // 濡傛灉鍏跺凡缁忚璁剧疆涓烘案涔咃紝鍒欎笉浣滀换浣曞鐞�
+ } else {
+ // 濡傛灉灏氭湭琚缃负姘镐箙锛岄偅涔堝啀娆et涓�娆�
+ this.setObject(key, this.getObject(key), timeout);
+ }
+ return;
+ }
+ RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
+ }
+
+ /**
+ * 鎼滅储鏁版嵁
+ */
+ @Override
+ public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
+ return super.searchData(GlobalConstants.GLOBAL_REDIS_KEY + prefix, keyword, start, size, sortType);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/exception/TenantException.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/exception/TenantException.java
new file mode 100755
index 0000000..ee2bc97
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/exception/TenantException.java
@@ -0,0 +1,20 @@
+package org.dromara.common.tenant.exception;
+
+import org.dromara.common.core.exception.base.BaseException;
+
+import java.io.Serial;
+
+/**
+ * 绉熸埛寮傚父绫�
+ *
+ * @author Lion Li
+ */
+public class TenantException extends BaseException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public TenantException(String code, Object... args) {
+ super("tenant", code, args, null);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/PlusTenantLineHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/PlusTenantLineHandler.java
new file mode 100755
index 0000000..f6d224b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/PlusTenantLineHandler.java
@@ -0,0 +1,56 @@
+package org.dromara.common.tenant.handle;
+
+import cn.hutool.core.collection.ListUtil;
+import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.NullValue;
+import net.sf.jsqlparser.expression.StringValue;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.common.tenant.properties.TenantProperties;
+
+import java.util.List;
+
+/**
+ * 鑷畾涔夌鎴峰鐞嗗櫒
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@AllArgsConstructor
+public class PlusTenantLineHandler implements TenantLineHandler {
+
+ private final TenantProperties tenantProperties;
+
+ @Override
+ public Expression getTenantId() {
+ String tenantId = TenantHelper.getTenantId();
+ if (StringUtils.isBlank(tenantId)) {
+ log.error("鏃犳硶鑾峰彇鏈夋晥鐨勭鎴穒d -> Null");
+ return new NullValue();
+ }
+ // 杩斿洖鍥哄畾绉熸埛
+ return new StringValue(tenantId);
+ }
+
+ @Override
+ public boolean ignoreTable(String tableName) {
+ String tenantId = TenantHelper.getTenantId();
+ // 鍒ゆ柇鏄惁鏈夌鎴�
+ if (StringUtils.isNotBlank(tenantId)) {
+ // 涓嶉渶瑕佽繃婊ょ鎴风殑琛�
+ List<String> excludes = tenantProperties.getExcludes();
+ // 闈炰笟鍔¤〃
+ List<String> tables = ListUtil.toList(
+ "gen_table",
+ "gen_table_column"
+ );
+ tables.addAll(excludes);
+ return StringUtils.equalsAnyIgnoreCase(tableName, tables.toArray(new String[0]));
+ }
+ return true;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java
new file mode 100755
index 0000000..dcdef6e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java
@@ -0,0 +1,83 @@
+package org.dromara.common.tenant.handle;
+
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.redis.handler.KeyPrefixHandler;
+import org.dromara.common.tenant.helper.TenantHelper;
+
+/**
+ * 澶氱鎴穜edis缂撳瓨key鍓嶇紑澶勭悊
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class TenantKeyPrefixHandler extends KeyPrefixHandler {
+
+ public TenantKeyPrefixHandler(String keyPrefix) {
+ super(keyPrefix);
+ }
+
+ /**
+ * 澧炲姞鍓嶇紑
+ */
+ @Override
+ public String map(String name) {
+ if (StringUtils.isBlank(name)) {
+ return null;
+ }
+ try {
+ if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
+ return super.map(name);
+ }
+ } catch (NoClassDefFoundError ignore) {
+ // 鏈変簺鏈嶅姟涓嶉渶瑕乵p瀵艰嚧绫讳笉瀛樺湪 蹇界暐鍗冲彲
+ }
+ if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
+ return super.map(name);
+ }
+ String tenantId = TenantHelper.getTenantId();
+ if (StringUtils.isBlank(tenantId)) {
+ log.debug("鏃犳硶鑾峰彇鏈夋晥鐨勭鎴穒d -> Null");
+ return super.map(name);
+ }
+ if (StringUtils.startsWith(name, tenantId + "")) {
+ // 濡傛灉瀛樺湪鍒欑洿鎺ヨ繑鍥�
+ return super.map(name);
+ }
+ return super.map(tenantId + ":" + name);
+ }
+
+ /**
+ * 鍘婚櫎鍓嶇紑
+ */
+ @Override
+ public String unmap(String name) {
+ String unmap = super.unmap(name);
+ if (StringUtils.isBlank(unmap)) {
+ return null;
+ }
+ try {
+ if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
+ return unmap;
+ }
+ } catch (NoClassDefFoundError ignore) {
+ // 鏈変簺鏈嶅姟涓嶉渶瑕乵p瀵艰嚧绫讳笉瀛樺湪 蹇界暐鍗冲彲
+ }
+ if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
+ return unmap;
+ }
+ String tenantId = TenantHelper.getTenantId();
+ if (StringUtils.isBlank(tenantId)) {
+ log.debug("鏃犳硶鑾峰彇鏈夋晥鐨勭鎴穒d -> Null");
+ return unmap;
+ }
+ if (StringUtils.startsWith(unmap, tenantId + "")) {
+ // 濡傛灉瀛樺湪鍒欏垹闄�
+ return unmap.substring((tenantId + ":").length());
+ }
+ return unmap;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java
new file mode 100755
index 0000000..98f03f3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java
@@ -0,0 +1,231 @@
+package org.dromara.common.tenant.helper;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.context.model.SaStorage;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+
+import java.util.Stack;
+import java.util.function.Supplier;
+
+/**
+ * 绉熸埛鍔╂墜
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class TenantHelper {
+
+ private static final String DYNAMIC_TENANT_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant";
+
+ private static final ThreadLocal<String> TEMP_DYNAMIC_TENANT = new ThreadLocal<>();
+
+ private static final ThreadLocal<Stack<Integer>> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new);
+
+ /**
+ * 绉熸埛鍔熻兘鏄惁鍚敤
+ */
+ public static boolean isEnable() {
+ return Convert.toBool(SpringUtils.getProperty("tenant.enable"), false);
+ }
+
+ private static IgnoreStrategy getIgnoreStrategy() {
+ Object ignoreStrategyLocal = ReflectUtils.getStaticFieldValue(ReflectUtils.getField(InterceptorIgnoreHelper.class, "IGNORE_STRATEGY_LOCAL"));
+ if (ignoreStrategyLocal instanceof ThreadLocal<?> IGNORE_STRATEGY_LOCAL) {
+ if (IGNORE_STRATEGY_LOCAL.get() instanceof IgnoreStrategy ignoreStrategy) {
+ return ignoreStrategy;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 寮�鍚拷鐣ョ鎴�(寮�鍚悗闇�鎵嬪姩璋冪敤 {@link #disableIgnore()} 鍏抽棴)
+ */
+ private static void enableIgnore() {
+ IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
+ if (ObjectUtil.isNull(ignoreStrategy)) {
+ InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
+ } else {
+ ignoreStrategy.setTenantLine(true);
+ }
+ Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
+ reentrantStack.push(reentrantStack.size() + 1);
+ }
+
+ /**
+ * 鍏抽棴蹇界暐绉熸埛
+ */
+ private static void disableIgnore() {
+ IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
+ if (ObjectUtil.isNotNull(ignoreStrategy)) {
+ boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName())
+ && !Boolean.TRUE.equals(ignoreStrategy.getBlockAttack())
+ && !Boolean.TRUE.equals(ignoreStrategy.getIllegalSql())
+ && !Boolean.TRUE.equals(ignoreStrategy.getDataPermission())
+ && CollectionUtil.isEmpty(ignoreStrategy.getOthers());
+ Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
+ boolean empty = reentrantStack.isEmpty() || reentrantStack.pop() == 1;
+ if (noOtherIgnoreStrategy && empty) {
+ InterceptorIgnoreHelper.clearIgnoreStrategy();
+ } else if (empty) {
+ ignoreStrategy.setTenantLine(false);
+ }
+ }
+ }
+
+ /**
+ * 鍦ㄥ拷鐣ョ鎴蜂腑鎵ц
+ *
+ * @param handle 澶勭悊鎵ц鏂规硶
+ */
+ public static void ignore(Runnable handle) {
+ enableIgnore();
+ try {
+ handle.run();
+ } finally {
+ disableIgnore();
+ }
+ }
+
+ /**
+ * 鍦ㄥ拷鐣ョ鎴蜂腑鎵ц
+ *
+ * @param handle 澶勭悊鎵ц鏂规硶
+ */
+ public static <T> T ignore(Supplier<T> handle) {
+ enableIgnore();
+ try {
+ return handle.get();
+ } finally {
+ disableIgnore();
+ }
+ }
+
+ public static void setDynamic(String tenantId) {
+ setDynamic(tenantId, false);
+ }
+
+ /**
+ * 璁剧疆鍔ㄦ�佺鎴�(涓�鐩存湁鏁� 闇�瑕佹墜鍔ㄦ竻鐞�)
+ * <p>
+ * 濡傛灉涓烘湭鐧诲綍鐘舵�佷笅 閭d箞鍙湪褰撳墠绾跨▼鍐呯敓鏁�
+ *
+ * @param tenantId 绉熸埛id
+ * @param global 鏄惁鍏ㄥ眬鐢熸晥
+ */
+ public static void setDynamic(String tenantId, boolean global) {
+ if (!isEnable()) {
+ return;
+ }
+ if (!LoginHelper.isLogin() || !global) {
+ TEMP_DYNAMIC_TENANT.set(tenantId);
+ return;
+ }
+ String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
+ RedisUtils.setCacheObject(cacheKey, tenantId);
+ SaHolder.getStorage().set(cacheKey, tenantId);
+ }
+
+ /**
+ * 鑾峰彇鍔ㄦ�佺鎴�(涓�鐩存湁鏁� 闇�瑕佹墜鍔ㄦ竻鐞�)
+ * <p>
+ * 濡傛灉涓烘湭鐧诲綍鐘舵�佷笅 閭d箞鍙湪褰撳墠绾跨▼鍐呯敓鏁�
+ */
+ public static String getDynamic() {
+ if (!isEnable()) {
+ return null;
+ }
+ if (!LoginHelper.isLogin()) {
+ return TEMP_DYNAMIC_TENANT.get();
+ }
+ // 濡傛灉绾跨▼鍐呮湁鍊� 浼樺厛杩斿洖
+ String tenantId = TEMP_DYNAMIC_TENANT.get();
+ if (StringUtils.isNotBlank(tenantId)) {
+ return tenantId;
+ }
+ SaStorage storage = SaHolder.getStorage();
+ String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
+ tenantId = storage.getString(cacheKey);
+ // 濡傛灉涓� -1 璇存槑宸茬粡鏌ヨ繃redis骞朵笖涓嶅瓨鍦ㄥ�� 鍒欑洿鎺ヨ繑鍥瀗ull
+ if (StringUtils.isNotBlank(tenantId)) {
+ return tenantId.equals("-1") ? null : tenantId;
+ }
+ tenantId = RedisUtils.getCacheObject(cacheKey);
+ storage.set(cacheKey, StringUtils.isBlank(tenantId) ? "-1" : tenantId);
+ return tenantId;
+ }
+
+ /**
+ * 娓呴櫎鍔ㄦ�佺鎴�
+ */
+ public static void clearDynamic() {
+ if (!isEnable()) {
+ return;
+ }
+ if (!LoginHelper.isLogin()) {
+ TEMP_DYNAMIC_TENANT.remove();
+ return;
+ }
+ TEMP_DYNAMIC_TENANT.remove();
+ String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
+ RedisUtils.deleteObject(cacheKey);
+ SaHolder.getStorage().delete(cacheKey);
+ }
+
+ /**
+ * 鍦ㄥ姩鎬佺鎴蜂腑鎵ц
+ *
+ * @param handle 澶勭悊鎵ц鏂规硶
+ */
+ public static void dynamic(String tenantId, Runnable handle) {
+ setDynamic(tenantId);
+ try {
+ handle.run();
+ } finally {
+ clearDynamic();
+ }
+ }
+
+ /**
+ * 鍦ㄥ姩鎬佺鎴蜂腑鎵ц
+ *
+ * @param handle 澶勭悊鎵ц鏂规硶
+ */
+ public static <T> T dynamic(String tenantId, Supplier<T> handle) {
+ setDynamic(tenantId);
+ try {
+ return handle.get();
+ } finally {
+ clearDynamic();
+ }
+ }
+
+ /**
+ * 鑾峰彇褰撳墠绉熸埛id(鍔ㄦ�佺鎴蜂紭鍏�)
+ */
+ public static String getTenantId() {
+ if (!isEnable()) {
+ return null;
+ }
+ String tenantId = TenantHelper.getDynamic();
+ if (StringUtils.isBlank(tenantId)) {
+ tenantId = LoginHelper.getTenantId();
+ }
+ return tenantId;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/manager/TenantSpringCacheManager.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/manager/TenantSpringCacheManager.java
new file mode 100755
index 0000000..346e36f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/manager/TenantSpringCacheManager.java
@@ -0,0 +1,41 @@
+package org.dromara.common.tenant.manager;
+
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.redis.manager.PlusSpringCacheManager;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.springframework.cache.Cache;
+
+/**
+ * 閲嶅啓 cacheName 澶勭悊鏂规硶 鏀寔澶氱鎴�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class TenantSpringCacheManager extends PlusSpringCacheManager {
+
+ public TenantSpringCacheManager() {
+ }
+
+ @Override
+ public Cache getCache(String name) {
+ if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
+ return super.getCache(name);
+ }
+ if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
+ return super.getCache(name);
+ }
+ String tenantId = TenantHelper.getTenantId();
+ if (StringUtils.isBlank(tenantId)) {
+ log.error("鏃犳硶鑾峰彇鏈夋晥鐨勭鎴穒d -> Null");
+ }
+ if (StringUtils.startsWith(name, tenantId)) {
+ // 濡傛灉瀛樺湪鍒欑洿鎺ヨ繑鍥�
+ return super.getCache(name);
+ }
+ return super.getCache(tenantId + ":" + name);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/properties/TenantProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/properties/TenantProperties.java
new file mode 100755
index 0000000..1675ccf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/properties/TenantProperties.java
@@ -0,0 +1,27 @@
+package org.dromara.common.tenant.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.List;
+
+/**
+ * 绉熸埛 閰嶇疆灞炴��
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "tenant")
+public class TenantProperties {
+
+ /**
+ * 鏄惁鍚敤
+ */
+ private Boolean enable;
+
+ /**
+ * 鎺掗櫎琛�
+ */
+ private List<String> excludes;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..f837191
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.tenant.config.TenantConfig
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/.flattened-pom.xml
new file mode 100644
index 0000000..5fdd7c6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/.flattened-pom.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-translation</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-translation 閫氱敤缈昏瘧鍔熻兘</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/pom.xml
new file mode 100755
index 0000000..e77b868
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/pom.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-translation</artifactId>
+
+ <description>
+ ruoyi-common-translation 閫氱敤缈昏瘧鍔熻兘
+ </description>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java
new file mode 100755
index 0000000..c24aa6f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java
@@ -0,0 +1,39 @@
+package org.dromara.common.translation.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.dromara.common.translation.core.handler.TranslationHandler;
+
+import java.lang.annotation.*;
+
+/**
+ * 閫氱敤缈昏瘧娉ㄨВ
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+@Documented
+@JacksonAnnotationsInside
+@JsonSerialize(using = TranslationHandler.class)
+public @interface Translation {
+
+ /**
+ * 绫诲瀷 (闇�涓庡疄鐜扮被涓婄殑 {@link TranslationType} 娉ㄨВtype瀵瑰簲)
+ * <p>
+ * 榛樿鍙栧綋鍓嶅瓧娈电殑鍊� 濡傛灉璁剧疆浜� @{@link Translation#mapper()} 鍒欏彇鏄犲皠瀛楁鐨勫��
+ */
+ String type();
+
+ /**
+ * 鏄犲皠瀛楁 (濡傛灉涓嶄负绌哄垯鍙栨瀛楁鐨勫��)
+ */
+ String mapper() default "";
+
+ /**
+ * 鍏朵粬鏉′欢 渚嬪: 瀛楀吀type(sys_user_sex)
+ */
+ String other() default "";
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/TranslationType.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/TranslationType.java
new file mode 100755
index 0000000..43bfab0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/TranslationType.java
@@ -0,0 +1,23 @@
+package org.dromara.common.translation.annotation;
+
+import org.dromara.common.translation.core.TranslationInterface;
+
+import java.lang.annotation.*;
+
+/**
+ * 缈昏瘧绫诲瀷娉ㄨВ (鏍囨敞鍒皗@link TranslationInterface} 鐨勫疄鐜扮被)
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Documented
+public @interface TranslationType {
+
+ /**
+ * 绫诲瀷
+ */
+ String type();
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java
new file mode 100755
index 0000000..5dcd0c1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java
@@ -0,0 +1,50 @@
+package org.dromara.common.translation.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.core.TranslationInterface;
+import org.dromara.common.translation.core.handler.TranslationBeanSerializerModifier;
+import org.dromara.common.translation.core.handler.TranslationHandler;
+import jakarta.annotation.PostConstruct;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 缈昏瘧妯″潡閰嶇疆绫�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@AutoConfiguration
+public class TranslationConfig {
+
+ @Autowired
+ private List<TranslationInterface<?>> list;
+
+ @Autowired
+ private ObjectMapper objectMapper;
+
+ @PostConstruct
+ public void init() {
+ Map<String, TranslationInterface<?>> map = new HashMap<>(list.size());
+ for (TranslationInterface<?> trans : list) {
+ if (trans.getClass().isAnnotationPresent(TranslationType.class)) {
+ TranslationType annotation = trans.getClass().getAnnotation(TranslationType.class);
+ map.put(annotation.type(), trans);
+ } else {
+ log.warn(trans.getClass().getName() + " 缈昏瘧瀹炵幇绫绘湭鏍囨敞 TranslationType 娉ㄨВ!");
+ }
+ }
+ TranslationHandler.TRANSLATION_MAPPER.putAll(map);
+ // 璁剧疆 Bean 搴忓垪鍖栦慨鏀瑰櫒
+ objectMapper.setSerializerFactory(
+ objectMapper.getSerializerFactory()
+ .withSerializerModifier(new TranslationBeanSerializerModifier()));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java
new file mode 100755
index 0000000..c084ea1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java
@@ -0,0 +1,35 @@
+package org.dromara.common.translation.constant;
+
+/**
+ * 缈昏瘧甯搁噺
+ *
+ * @author Lion Li
+ */
+public interface TransConstant {
+
+ /**
+ * 鐢ㄦ埛id杞处鍙�
+ */
+ String USER_ID_TO_NAME = "user_id_to_name";
+
+ /**
+ * 鐢ㄦ埛id杞敤鎴峰悕绉�
+ */
+ String USER_ID_TO_NICKNAME = "user_id_to_nickname";
+
+ /**
+ * 閮ㄩ棬id杞悕绉�
+ */
+ String DEPT_ID_TO_NAME = "dept_id_to_name";
+
+ /**
+ * 瀛楀吀type杞琹abel
+ */
+ String DICT_TYPE_TO_LABEL = "dict_type_to_label";
+
+ /**
+ * ossId杞瑄rl
+ */
+ String OSS_ID_TO_URL = "oss_id_to_url";
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java
new file mode 100755
index 0000000..e4d6dd3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java
@@ -0,0 +1,20 @@
+package org.dromara.common.translation.core;
+
+import org.dromara.common.translation.annotation.TranslationType;
+
+/**
+ * 缈昏瘧鎺ュ彛 (瀹炵幇绫婚渶鏍囨敞 {@link TranslationType} 娉ㄨВ鏍囨槑缈昏瘧绫诲瀷)
+ *
+ * @author Lion Li
+ */
+public interface TranslationInterface<T> {
+
+ /**
+ * 缈昏瘧
+ *
+ * @param key 闇�瑕佽缈昏瘧鐨勯敭(涓嶄负绌�)
+ * @param other 鍏朵粬鍙傛暟
+ * @return 杩斿洖閿搴旂殑鍊�
+ */
+ T translation(Object key, String other);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationBeanSerializerModifier.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationBeanSerializerModifier.java
new file mode 100755
index 0000000..727672f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationBeanSerializerModifier.java
@@ -0,0 +1,29 @@
+package org.dromara.common.translation.core.handler;
+
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
+
+import java.util.List;
+
+/**
+ * Bean 搴忓垪鍖栦慨鏀瑰櫒 瑙e喅 Null 琚崟鐙鐞嗛棶棰�
+ *
+ * @author Lion Li
+ */
+public class TranslationBeanSerializerModifier extends BeanSerializerModifier {
+
+ @Override
+ public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
+ List<BeanPropertyWriter> beanProperties) {
+ for (BeanPropertyWriter writer : beanProperties) {
+ // 濡傛灉搴忓垪鍖栧櫒涓� TranslationHandler 鐨勮瘽 灏� Null 鍊间篃浜ょ粰浠栧鐞�
+ if (writer.getSerializer() instanceof TranslationHandler serializer) {
+ writer.assignNullSerializer(serializer);
+ }
+ }
+ return beanProperties;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java
new file mode 100755
index 0000000..e8c03ac
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java
@@ -0,0 +1,71 @@
+package org.dromara.common.translation.core.handler;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.core.TranslationInterface;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 缈昏瘧澶勭悊鍣�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class TranslationHandler extends JsonSerializer<Object> implements ContextualSerializer {
+
+ /**
+ * 鍏ㄥ眬缈昏瘧瀹炵幇绫绘槧灏勫櫒
+ */
+ public static final Map<String, TranslationInterface<?>> TRANSLATION_MAPPER = new ConcurrentHashMap<>();
+
+ private Translation translation;
+
+ @Override
+ public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ TranslationInterface<?> trans = TRANSLATION_MAPPER.get(translation.type());
+ if (ObjectUtil.isNotNull(trans)) {
+ // 濡傛灉鏄犲皠瀛楁涓嶄负绌� 鍒欏彇鏄犲皠瀛楁鐨勫��
+ if (StringUtils.isNotBlank(translation.mapper())) {
+ value = ReflectUtils.invokeGetter(gen.currentValue(), translation.mapper());
+ }
+ // 濡傛灉涓� null 鐩存帴鍐欏嚭
+ if (ObjectUtil.isNull(value)) {
+ gen.writeNull();
+ return;
+ }
+ try {
+ Object result = trans.translation(value, translation.other());
+ gen.writeObject(result);
+ } catch (Exception e) {
+ log.error("缈昏瘧澶勭悊寮傚父锛宼ype: {}, value: {}", translation.type(), value, e);
+ // 鍑虹幇寮傚父鏃惰緭鍑哄師濮嬪�艰�屼笉鏄腑鏂簭鍒楀寲
+ gen.writeObject(value);
+ }
+ } else {
+ gen.writeObject(value);
+ }
+ }
+
+ @Override
+ public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
+ Translation translation = property.getAnnotation(Translation.class);
+ if (Objects.nonNull(translation)) {
+ this.translation = translation;
+ return this;
+ }
+ return prov.findValueSerializer(property.getType(), property);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java
new file mode 100755
index 0000000..c391437
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java
@@ -0,0 +1,29 @@
+package org.dromara.common.translation.core.impl;
+
+import org.dromara.common.core.service.DeptService;
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.common.translation.core.TranslationInterface;
+import lombok.AllArgsConstructor;
+
+/**
+ * 閮ㄩ棬缈昏瘧瀹炵幇
+ *
+ * @author Lion Li
+ */
+@AllArgsConstructor
+@TranslationType(type = TransConstant.DEPT_ID_TO_NAME)
+public class DeptNameTranslationImpl implements TranslationInterface<String> {
+
+ private final DeptService deptService;
+
+ @Override
+ public String translation(Object key, String other) {
+ if (key instanceof String ids) {
+ return deptService.selectDeptNameByIds(ids);
+ } else if (key instanceof Long id) {
+ return deptService.selectDeptNameByIds(id.toString());
+ }
+ return null;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java
new file mode 100755
index 0000000..859a93e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java
@@ -0,0 +1,28 @@
+package org.dromara.common.translation.core.impl;
+
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.common.translation.core.TranslationInterface;
+import lombok.AllArgsConstructor;
+
+/**
+ * 瀛楀吀缈昏瘧瀹炵幇
+ *
+ * @author Lion Li
+ */
+@AllArgsConstructor
+@TranslationType(type = TransConstant.DICT_TYPE_TO_LABEL)
+public class DictTypeTranslationImpl implements TranslationInterface<String> {
+
+ private final DictService dictService;
+
+ @Override
+ public String translation(Object key, String other) {
+ if (key instanceof String dictValue && StringUtils.isNotBlank(other)) {
+ return dictService.getDictLabel(other, dictValue);
+ }
+ return null;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java
new file mode 100755
index 0000000..d1720f7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java
@@ -0,0 +1,29 @@
+package org.dromara.common.translation.core.impl;
+
+import lombok.AllArgsConstructor;
+import org.dromara.common.core.service.UserService;
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.common.translation.core.TranslationInterface;
+
+/**
+ * 鐢ㄦ埛鍚嶇О缈昏瘧瀹炵幇
+ *
+ * @author may
+ */
+@AllArgsConstructor
+@TranslationType(type = TransConstant.USER_ID_TO_NICKNAME)
+public class NicknameTranslationImpl implements TranslationInterface<String> {
+
+ private final UserService userService;
+
+ @Override
+ public String translation(Object key, String other) {
+ if (key instanceof Long id) {
+ return userService.selectNicknameById(id);
+ } else if (key instanceof String ids) {
+ return userService.selectNicknameByIds(ids);
+ }
+ return null;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java
new file mode 100755
index 0000000..fc6f6df
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java
@@ -0,0 +1,29 @@
+package org.dromara.common.translation.core.impl;
+
+import org.dromara.common.core.service.OssService;
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.common.translation.core.TranslationInterface;
+import lombok.AllArgsConstructor;
+
+/**
+ * OSS缈昏瘧瀹炵幇
+ *
+ * @author Lion Li
+ */
+@AllArgsConstructor
+@TranslationType(type = TransConstant.OSS_ID_TO_URL)
+public class OssUrlTranslationImpl implements TranslationInterface<String> {
+
+ private final OssService ossService;
+
+ @Override
+ public String translation(Object key, String other) {
+ if (key instanceof String ids) {
+ return ossService.selectUrlByIds(ids);
+ } else if (key instanceof Long id) {
+ return ossService.selectUrlByIds(id.toString());
+ }
+ return null;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java
new file mode 100755
index 0000000..5fc7773
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java
@@ -0,0 +1,25 @@
+package org.dromara.common.translation.core.impl;
+
+import cn.hutool.core.convert.Convert;
+import org.dromara.common.core.service.UserService;
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.common.translation.core.TranslationInterface;
+import lombok.AllArgsConstructor;
+
+/**
+ * 鐢ㄦ埛鍚嶇炕璇戝疄鐜�
+ *
+ * @author Lion Li
+ */
+@AllArgsConstructor
+@TranslationType(type = TransConstant.USER_ID_TO_NAME)
+public class UserNameTranslationImpl implements TranslationInterface<String> {
+
+ private final UserService userService;
+
+ @Override
+ public String translation(Object key, String other) {
+ return userService.selectUserNameById(Convert.toLong(key));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..ad40205
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-translation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,6 @@
+org.dromara.common.translation.config.TranslationConfig
+org.dromara.common.translation.core.impl.DeptNameTranslationImpl
+org.dromara.common.translation.core.impl.DictTypeTranslationImpl
+org.dromara.common.translation.core.impl.OssUrlTranslationImpl
+org.dromara.common.translation.core.impl.UserNameTranslationImpl
+org.dromara.common.translation.core.impl.NicknameTranslationImpl
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/.flattened-pom.xml
new file mode 100644
index 0000000..560a3cf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/.flattened-pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-web</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-web web鏈嶅姟</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-tomcat</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-undertow</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-actuator</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-captcha</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-crypto</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/pom.xml
new file mode 100755
index 0000000..c687ad5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/pom.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-web</artifactId>
+
+ <description>
+ ruoyi-common-web web鏈嶅姟
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+
+ <!-- SpringBoot Web瀹瑰櫒 -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <exclusions>
+ <exclusion>
+ <artifactId>spring-boot-starter-tomcat</artifactId>
+ <groupId>org.springframework.boot</groupId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <!-- web 瀹瑰櫒浣跨敤 undertow 鎬ц兘鏇村己 -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-undertow</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-actuator</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-captcha</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-crypto</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/CaptchaConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/CaptchaConfig.java
new file mode 100755
index 0000000..7140db9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/CaptchaConfig.java
@@ -0,0 +1,16 @@
+package org.dromara.common.web.config;
+
+import org.dromara.common.web.config.properties.CaptchaProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+/**
+ * 楠岃瘉鐮侀厤缃�
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(CaptchaProperties.class)
+public class CaptchaConfig {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java
new file mode 100755
index 0000000..155a8d0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java
@@ -0,0 +1,41 @@
+package org.dromara.common.web.config;
+
+import jakarta.servlet.DispatcherType;
+import org.dromara.common.web.config.properties.XssProperties;
+import org.dromara.common.web.filter.RepeatableFilter;
+import org.dromara.common.web.filter.XssFilter;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistration;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Filter閰嶇疆
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(XssProperties.class)
+public class FilterConfig {
+
+ @Bean
+ @ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
+ @FilterRegistration(
+ name = "xssFilter",
+ urlPatterns = "/*",
+ order = FilterRegistrationBean.HIGHEST_PRECEDENCE + 1,
+ dispatcherTypes = DispatcherType.REQUEST
+ )
+ public XssFilter xssFilter() {
+ return new XssFilter();
+ }
+
+ @Bean
+ @FilterRegistration(name = "repeatableFilter", urlPatterns = "/*")
+ public RepeatableFilter repeatableFilter() {
+ return new RepeatableFilter();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/I18nConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/I18nConfig.java
new file mode 100755
index 0000000..4e212cb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/I18nConfig.java
@@ -0,0 +1,22 @@
+package org.dromara.common.web.config;
+
+import org.dromara.common.web.core.I18nLocaleResolver;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.servlet.LocaleResolver;
+
+/**
+ * 鍥介檯鍖栭厤缃�
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration(before = WebMvcAutoConfiguration.class)
+public class I18nConfig {
+
+ @Bean
+ public LocaleResolver localeResolver() {
+ return new I18nLocaleResolver();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java
new file mode 100755
index 0000000..81ec905
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java
@@ -0,0 +1,79 @@
+package org.dromara.common.web.config;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import org.dromara.common.core.utils.ObjectUtils;
+import org.dromara.common.web.handler.GlobalExceptionHandler;
+import org.dromara.common.web.interceptor.PlusWebInvokeTimeInterceptor;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.format.FormatterRegistry;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.Date;
+
+/**
+ * 閫氱敤閰嶇疆
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+public class ResourcesConfig implements WebMvcConfigurer {
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ // 鍏ㄥ眬璁块棶鎬ц兘鎷︽埅
+ registry.addInterceptor(new PlusWebInvokeTimeInterceptor());
+ }
+
+ @Override
+ public void addFormatters(FormatterRegistry registry) {
+ // 鍏ㄥ眬鏃ユ湡鏍煎紡杞崲閰嶇疆
+ registry.addConverter(String.class, Date.class, source -> {
+ DateTime parse = DateUtil.parse(source);
+ if (ObjectUtils.isNull(parse)) {
+ return null;
+ }
+ return parse.toJdkDate();
+ });
+ }
+
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ }
+
+ /**
+ * 璺ㄥ煙閰嶇疆
+ */
+ @Bean
+ public CorsFilter corsFilter() {
+ CorsConfiguration config = new CorsConfiguration();
+ config.setAllowCredentials(true);
+ // 璁剧疆璁块棶婧愬湴鍧�
+ config.addAllowedOriginPattern("*");
+ // 璁剧疆璁块棶婧愯姹傚ご
+ config.addAllowedHeader("*");
+ // 璁剧疆璁块棶婧愯姹傛柟娉�
+ config.addAllowedMethod("*");
+ // 鏈夋晥鏈� 1800绉�
+ config.setMaxAge(1800L);
+ // 娣诲姞鏄犲皠璺緞锛屾嫤鎴竴鍒囪姹�
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", config);
+ // 杩斿洖鏂扮殑CorsFilter
+ return new CorsFilter(source);
+ }
+
+ /**
+ * 鍏ㄥ眬寮傚父澶勭悊鍣�
+ */
+ @Bean
+ public GlobalExceptionHandler globalExceptionHandler() {
+ return new GlobalExceptionHandler();
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java
new file mode 100755
index 0000000..84f88ff
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java
@@ -0,0 +1,63 @@
+package org.dromara.common.web.config;
+
+import io.undertow.server.DefaultByteBufferPool;
+import io.undertow.server.handlers.DisallowedMethodsHandler;
+import io.undertow.util.HttpString;
+import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
+import org.dromara.common.core.utils.SpringUtils;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
+import org.springframework.boot.web.server.WebServerFactoryCustomizer;
+import org.springframework.core.task.VirtualThreadTaskExecutor;
+
+/**
+ * Undertow 鑷畾涔夐厤缃�
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
+
+ /**
+ * 鑷畾涔� Undertow 閰嶇疆
+ * <p>
+ * 涓昏閰嶇疆鍐呭鍖呮嫭锛�
+ * 1. 閰嶇疆 WebSocket 閮ㄧ讲淇℃伅
+ * 2. 鍦ㄨ櫄鎷熺嚎绋嬫ā寮忎笅浣跨敤铏氭嫙绾跨▼姹�
+ * 3. 绂佺敤涓嶅畨鍏ㄧ殑 HTTP 鏂规硶锛屽 CONNECT銆乀RACE銆乀RACK
+ * </p>
+ *
+ * @param factory Undertow 鐨� Web 鏈嶅姟鍣ㄥ伐鍘�
+ */
+ @Override
+ public void customize(UndertowServletWebServerFactory factory) {
+ factory.addDeploymentInfoCustomizers(deploymentInfo -> {
+ // 閰嶇疆 WebSocket 閮ㄧ讲淇℃伅锛岃缃� WebSocket 浣跨敤鐨勭紦鍐插尯姹�
+ WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
+ webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 1024));
+ deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
+
+ // 濡傛灉鍚敤浜嗚櫄鎷熺嚎绋嬶紝閰嶇疆 Undertow 浣跨敤铏氭嫙绾跨▼姹�
+ if (SpringUtils.isVirtual()) {
+ // 鍒涘缓铏氭嫙绾跨▼姹狅紝绾跨▼姹犲墠缂�涓� "undertow-"
+ VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("undertow-");
+ // 璁剧疆铏氭嫙绾跨▼姹犱负鎵ц鍣ㄥ拰寮傛鎵ц鍣�
+ deploymentInfo.setExecutor(executor);
+ deploymentInfo.setAsyncExecutor(executor);
+ }
+
+ // 閰嶇疆绂佹鏌愪簺涓嶅畨鍏ㄧ殑 HTTP 鏂规硶锛堝 CONNECT銆乀RACE銆乀RACK锛�
+ deploymentInfo.addInitialHandlerChainWrapper(handler -> {
+ // 绂佹涓変釜鏂规硶 CONNECT/TRACE/TRACK 涔熸槸涓嶅畨鍏ㄧ殑 閬垮厤鐖櫕楠氭壈
+ HttpString[] disallowedHttpMethods = {
+ HttpString.tryFromString("CONNECT"),
+ HttpString.tryFromString("TRACE"),
+ HttpString.tryFromString("TRACK")
+ };
+ // 浣跨敤 DisallowedMethodsHandler 鎷︽埅骞舵嫆缁濊繖浜涙柟娉曠殑璇锋眰
+ return new DisallowedMethodsHandler(handler, disallowedHttpMethods);
+ });
+ });
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/CaptchaProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/CaptchaProperties.java
new file mode 100755
index 0000000..6dcfe64
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/CaptchaProperties.java
@@ -0,0 +1,31 @@
+package org.dromara.common.web.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 楠岃瘉鐮� 閰嶇疆灞炴��
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "captcha")
+public class CaptchaProperties {
+
+ private Boolean enable;
+
+ /**
+ * 楠岃瘉鐮佺被鍨�
+ */
+ private String type;
+
+ /**
+ * 鏁板瓧楠岃瘉鐮佷綅鏁�
+ */
+ private Integer numberLength;
+
+ /**
+ * 瀛楃楠岃瘉鐮侀暱搴�
+ */
+ private Integer charLength;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/XssProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/XssProperties.java
new file mode 100755
index 0000000..bd3e59b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/XssProperties.java
@@ -0,0 +1,28 @@
+package org.dromara.common.web.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * xss杩囨护 閰嶇疆灞炴��
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "xss")
+public class XssProperties {
+
+ /**
+ * Xss寮�鍏�
+ */
+ private Boolean enabled;
+
+ /**
+ * 鎺掗櫎璺緞
+ */
+ private List<String> excludeUrls = new ArrayList<>();
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/BaseController.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/BaseController.java
new file mode 100755
index 0000000..fd01dda
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/BaseController.java
@@ -0,0 +1,40 @@
+package org.dromara.common.web.core;
+
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.StringUtils;
+
+/**
+ * web灞傞�氱敤鏁版嵁澶勭悊
+ *
+ * @author Lion Li
+ */
+public class BaseController {
+
+ /**
+ * 鍝嶅簲杩斿洖缁撴灉
+ *
+ * @param rows 褰卞搷琛屾暟
+ * @return 鎿嶄綔缁撴灉
+ */
+ protected R<Void> toAjax(int rows) {
+ return rows > 0 ? R.ok() : R.fail();
+ }
+
+ /**
+ * 鍝嶅簲杩斿洖缁撴灉
+ *
+ * @param result 缁撴灉
+ * @return 鎿嶄綔缁撴灉
+ */
+ protected R<Void> toAjax(boolean result) {
+ return result ? R.ok() : R.fail();
+ }
+
+ /**
+ * 椤甸潰璺宠浆
+ */
+ public String redirect(String url) {
+ return StringUtils.format("redirect:{}", url);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/I18nLocaleResolver.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/I18nLocaleResolver.java
new file mode 100755
index 0000000..98ddd06
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/I18nLocaleResolver.java
@@ -0,0 +1,31 @@
+package org.dromara.common.web.core;
+
+import org.springframework.web.servlet.LocaleResolver;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.util.Locale;
+
+/**
+ * 鑾峰彇璇锋眰澶村浗闄呭寲淇℃伅
+ *
+ * @author Lion Li
+ */
+public class I18nLocaleResolver implements LocaleResolver {
+
+ @Override
+ public Locale resolveLocale(HttpServletRequest httpServletRequest) {
+ String language = httpServletRequest.getHeader("content-language");
+ Locale locale = Locale.getDefault();
+ if (language != null && language.length() > 0) {
+ String[] split = language.split("_");
+ locale = new Locale(split[0], split[1]);
+ }
+ return locale;
+ }
+
+ @Override
+ public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
+
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/WaveAndCircleCaptcha.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/WaveAndCircleCaptcha.java
new file mode 100755
index 0000000..8b37be4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/WaveAndCircleCaptcha.java
@@ -0,0 +1,197 @@
+package org.dromara.common.web.core;
+
+import cn.hutool.captcha.AbstractCaptcha;
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.captcha.generator.RandomGenerator;
+import cn.hutool.core.img.GraphicsUtil;
+import cn.hutool.core.img.ImgUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.RandomUtil;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.Serial;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * 甯﹀共鎵扮嚎銆佹尝娴�佸渾鐨勯獙璇佺爜
+ *
+ * @author Lion Li
+ */
+public class WaveAndCircleCaptcha extends AbstractCaptcha {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ // 鏋勯�犳柟娉曪紙鐣ワ紝涓庝箣鍓嶄竴鑷达級
+ public WaveAndCircleCaptcha(int width, int height) {
+ this(width, height, 4);
+ }
+
+ public WaveAndCircleCaptcha(int width, int height, int codeCount) {
+ this(width, height, codeCount, 6);
+ }
+
+ public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount) {
+ this(width, height, new RandomGenerator(codeCount), interfereCount);
+ }
+
+ public WaveAndCircleCaptcha(int width, int height, CodeGenerator generator, int interfereCount) {
+ super(width, height, generator, interfereCount);
+ }
+
+ public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount, float size) {
+ super(width, height, new RandomGenerator(codeCount), interfereCount, size);
+ }
+
+ @Override
+ public Image createImage(String code) {
+ final BufferedImage image = new BufferedImage(
+ width,
+ height,
+ (null == this.background) ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_INT_RGB
+ );
+ final Graphics2D g = ImgUtil.createGraphics(image, this.background);
+
+ try {
+ drawString(g, code);
+ // 鎵洸
+ shear(g, this.width, this.height, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
+ drawInterfere(g);
+ } finally {
+ g.dispose();
+ }
+
+ return image;
+ }
+
+ private void drawString(Graphics2D g, String code) {
+ // 璁剧疆鎶楅敮榻匡紙璁╁瓧浣撴覆鏌撴洿娓呮櫚锛�
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+ if (this.textAlpha != null) {
+ g.setComposite(this.textAlpha);
+ }
+
+ GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
+ }
+
+ protected void drawInterfere(Graphics2D g) {
+ ThreadLocalRandom random = RandomUtil.getRandom();
+ int circleCount = Math.max(0, this.interfereCount - 1);
+
+ // 鍦堝湀
+ for (int i = 0; i < circleCount; i++) {
+ g.setColor(ImgUtil.randomColor(random));
+ int x = random.nextInt(width);
+ int y = random.nextInt(height);
+ int w = random.nextInt(height >> 1);
+ int h = random.nextInt(height >> 1);
+ g.drawOval(x, y, w, h);
+ }
+
+ // 浠� 1 鏉″钩婊戞尝娴嚎
+ if (this.interfereCount >= 1) {
+ g.setColor(getRandomColor(120, 230, random));
+ drawSmoothWave(g, random);
+ }
+ }
+
+ private void drawSmoothWave(Graphics2D g, ThreadLocalRandom random) {
+ int amplitude = random.nextInt(8) + 5; // 娉㈠姩骞呭害
+ int wavelength = random.nextInt(40) + 30; // 娉㈤暱
+ double phase = random.nextDouble() * Math.PI * 2;
+
+ // 鉁� 鍏抽敭锛氶檺鍒� baseY 鍦ㄤ腑闂村尯鍩�
+ int centerY = height / 2;
+ int verticalJitter = Math.max(5, height / 6); // 鑷冲皯鍋忕Щ5鍍忕礌
+ int baseY = centerY - verticalJitter + random.nextInt(verticalJitter * 2);
+
+ g.setStroke(new BasicStroke(2.5f)); // 绾垮
+
+ int[] xPoints = new int[width];
+ int[] yPoints = new int[width];
+ for (int x = 0; x < width; x++) {
+ int y = baseY + (int) (amplitude * Math.sin((double) x / wavelength * 2 * Math.PI + phase));
+ // 闄愬埗 y 涓嶈瓒呭嚭鍥惧儚杈圭晫锛堝彲閫夛級
+ y = Math.max(amplitude, Math.min(y, height - amplitude));
+ xPoints[x] = x;
+ yPoints[x] = y;
+ }
+ g.drawPolyline(xPoints, yPoints, width);
+ }
+
+ private Color getRandomColor(int min, int max, ThreadLocalRandom random) {
+ int range = max - min;
+ return new Color(
+ min + random.nextInt(range),
+ min + random.nextInt(range),
+ min + random.nextInt(range)
+ );
+ }
+
+ /**
+ * 鎵洸
+ *
+ * @param g {@link Graphics}
+ * @param w1 w1
+ * @param h1 h1
+ * @param color 棰滆壊
+ */
+ private void shear(Graphics g, int w1, int h1, Color color) {
+ shearX(g, w1, h1, color);
+ shearY(g, w1, h1, color);
+ }
+
+ /**
+ * X鍧愭爣鎵洸
+ *
+ * @param g {@link Graphics}
+ * @param w1 瀹�
+ * @param h1 楂�
+ * @param color 棰滆壊
+ */
+ private void shearX(Graphics g, int w1, int h1, Color color) {
+
+ int period = RandomUtil.randomInt(this.width);
+
+ int frames = 1;
+ int phase = RandomUtil.randomInt(2);
+
+ for (int i = 0; i < h1; i++) {
+ double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
+ g.copyArea(0, i, w1, 1, (int) d, 0);
+ g.setColor(color);
+ g.drawLine((int) d, i, 0, i);
+ g.drawLine((int) d + w1, i, w1, i);
+ }
+
+ }
+
+ /**
+ * Y鍧愭爣鎵洸
+ *
+ * @param g {@link Graphics}
+ * @param w1 瀹�
+ * @param h1 楂�
+ * @param color 棰滆壊
+ */
+ private void shearY(Graphics g, int w1, int h1, Color color) {
+
+ int period = RandomUtil.randomInt(this.height >> 1);
+
+ int frames = 20;
+ int phase = 7;
+ for (int i = 0; i < w1; i++) {
+ double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
+ g.copyArea(i, 0, 1, h1, 0, (int) d);
+ g.setColor(color);
+ // 鎿﹂櫎鍘熶綅缃殑鐥曡抗
+ g.drawLine(i, (int) d, i, 0);
+ g.drawLine(i, (int) d + h1, i, h1);
+ }
+
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/RepeatableFilter.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/RepeatableFilter.java
new file mode 100755
index 0000000..e0a3bf2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/RepeatableFilter.java
@@ -0,0 +1,40 @@
+package org.dromara.common.web.filter;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.springframework.http.MediaType;
+
+import jakarta.servlet.*;
+import jakarta.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * Repeatable 杩囨护鍣�
+ *
+ * @author ruoyi
+ */
+public class RepeatableFilter implements Filter {
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ ServletRequest requestWrapper = null;
+ if (request instanceof HttpServletRequest
+ && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
+ requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
+ }
+ if (null == requestWrapper) {
+ chain.doFilter(request, response);
+ } else {
+ chain.doFilter(requestWrapper, response);
+ }
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/RepeatedlyRequestWrapper.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/RepeatedlyRequestWrapper.java
new file mode 100755
index 0000000..8933225
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/RepeatedlyRequestWrapper.java
@@ -0,0 +1,68 @@
+package org.dromara.common.web.filter;
+
+import cn.hutool.core.io.IoUtil;
+import org.dromara.common.core.constant.Constants;
+
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * 鏋勫缓鍙噸澶嶈鍙杋nputStream鐨剅equest
+ *
+ * @author ruoyi
+ */
+public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
+ private final byte[] body;
+
+ public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
+ super(request);
+ request.setCharacterEncoding(Constants.UTF8);
+ response.setCharacterEncoding(Constants.UTF8);
+
+ body = IoUtil.readBytes(request.getInputStream(), false);
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ return new BufferedReader(new InputStreamReader(getInputStream()));
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ final ByteArrayInputStream bais = new ByteArrayInputStream(body);
+ return new ServletInputStream() {
+ @Override
+ public int read() throws IOException {
+ return bais.read();
+ }
+
+ @Override
+ public int available() throws IOException {
+ return body.length;
+ }
+
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return false;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+
+ }
+ };
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/XssFilter.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/XssFilter.java
new file mode 100755
index 0000000..95bcdd9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/XssFilter.java
@@ -0,0 +1,59 @@
+package org.dromara.common.web.filter;
+
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.web.config.properties.XssProperties;
+import org.springframework.http.HttpMethod;
+
+import jakarta.servlet.*;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 闃叉XSS鏀诲嚮鐨勮繃婊ゅ櫒
+ *
+ * @author ruoyi
+ */
+public class XssFilter implements Filter {
+ /**
+ * 鎺掗櫎閾炬帴
+ */
+ public List<String> excludes = new ArrayList<>();
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ XssProperties properties = SpringUtils.getBean(XssProperties.class);
+ excludes.addAll(properties.getExcludeUrls());
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ HttpServletRequest req = (HttpServletRequest) request;
+ HttpServletResponse resp = (HttpServletResponse) response;
+ if (handleExcludeURL(req, resp)) {
+ chain.doFilter(request, response);
+ return;
+ }
+ XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
+ chain.doFilter(xssRequest, response);
+ }
+
+ private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
+ String url = request.getServletPath();
+ String method = request.getMethod();
+ // GET DELETE 涓嶈繃婊�
+ if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) {
+ return true;
+ }
+ return StringUtils.matches(url, excludes);
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/XssHttpServletRequestWrapper.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/XssHttpServletRequestWrapper.java
new file mode 100755
index 0000000..914e549
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/filter/XssHttpServletRequestWrapper.java
@@ -0,0 +1,134 @@
+package org.dromara.common.web.filter;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HtmlUtil;
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import org.dromara.common.core.utils.StringUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * XSS杩囨护澶勭悊
+ *
+ * @author ruoyi
+ */
+public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
+ /**
+ * @param request
+ */
+ public XssHttpServletRequestWrapper(HttpServletRequest request) {
+ super(request);
+ }
+
+ @Override
+ public String getParameter(String name) {
+ String value = super.getParameter(name);
+ if (value == null) {
+ return null;
+ }
+ return HtmlUtil.cleanHtmlTag(value).trim();
+ }
+
+ @Override
+ public Map<String, String[]> getParameterMap() {
+ Map<String, String[]> valueMap = super.getParameterMap();
+ if (MapUtil.isEmpty(valueMap)) {
+ return valueMap;
+ }
+ // 閬垮厤鏌愪簺瀹瑰櫒涓嶅厑璁告敼鍙傛暟鐨勬儏鍐� copy涓�浠介噸鏂版敼
+ Map<String, String[]> map = new HashMap<>(valueMap.size());
+ map.putAll(valueMap);
+ for (Map.Entry<String, String[]> entry : map.entrySet()) {
+ String[] values = entry.getValue();
+ if (values != null) {
+ int length = values.length;
+ String[] escapseValues = new String[length];
+ for (int i = 0; i < length; i++) {
+ // 闃瞲ss鏀诲嚮鍜岃繃婊ゅ墠鍚庣┖鏍�
+ escapseValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim();
+ }
+ map.put(entry.getKey(), escapseValues);
+ }
+ }
+ return map;
+ }
+
+ @Override
+ public String[] getParameterValues(String name) {
+ String[] values = super.getParameterValues(name);
+ if (ArrayUtil.isEmpty(values)) {
+ return values;
+ }
+ int length = values.length;
+ String[] escapseValues = new String[length];
+ for (int i = 0; i < length; i++) {
+ // 闃瞲ss鏀诲嚮鍜岃繃婊ゅ墠鍚庣┖鏍�
+ escapseValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim();
+ }
+ return escapseValues;
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ // 闈瀓son绫诲瀷锛岀洿鎺ヨ繑鍥�
+ if (!isJsonRequest()) {
+ return super.getInputStream();
+ }
+
+ // 涓虹┖锛岀洿鎺ヨ繑鍥�
+ String json = StrUtil.str(IoUtil.readBytes(super.getInputStream(), false), StandardCharsets.UTF_8);
+ if (StringUtils.isEmpty(json)) {
+ return super.getInputStream();
+ }
+
+ // xss杩囨护
+ json = HtmlUtil.cleanHtmlTag(json).trim();
+ byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
+ final ByteArrayInputStream bis = IoUtil.toStream(jsonBytes);
+ return new ServletInputStream() {
+ @Override
+ public boolean isFinished() {
+ return true;
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return jsonBytes.length;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+ }
+
+ @Override
+ public int read() throws IOException {
+ return bis.read();
+ }
+ };
+ }
+
+ /**
+ * 鏄惁鏄疛son璇锋眰
+ */
+ public boolean isJsonRequest() {
+ String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
+ return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java
new file mode 100755
index 0000000..7a5d82c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java
@@ -0,0 +1,235 @@
+package org.dromara.common.web.handler;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpStatus;
+import com.fasterxml.jackson.core.JsonParseException;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.exception.SseException;
+import org.dromara.common.core.exception.base.BaseException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.springframework.context.MessageSourceResolvable;
+import org.springframework.context.support.DefaultMessageSourceResolvable;
+import org.springframework.expression.ExpressionException;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.validation.BindException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingPathVariableException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
+import org.springframework.web.method.annotation.HandlerMethodValidationException;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+import org.springframework.web.servlet.NoHandlerFoundException;
+
+import java.io.IOException;
+
+/**
+ * 鍏ㄥ眬寮傚父澶勭悊鍣�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+ /**
+ * 璇锋眰鏂瑰紡涓嶆敮鎸�
+ */
+ @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+ public R<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
+ HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("璇锋眰鍦板潃'{}',涓嶆敮鎸�'{}'璇锋眰", requestURI, e.getMethod());
+ return R.fail(HttpStatus.HTTP_BAD_METHOD, e.getMessage());
+ }
+
+ /**
+ * 涓氬姟寮傚父
+ */
+ @ExceptionHandler(ServiceException.class)
+ public R<Void> handleServiceException(ServiceException e, HttpServletRequest request) {
+ log.error(e.getMessage());
+ Integer code = e.getCode();
+ return ObjectUtil.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage());
+ }
+
+ /**
+ * 璁よ瘉澶辫触
+ */
+ @ResponseStatus(org.springframework.http.HttpStatus.UNAUTHORIZED)
+ @ExceptionHandler(SseException.class)
+ public String handleNotLoginException(SseException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.debug("璇锋眰鍦板潃'{}',璁よ瘉澶辫触'{}',鏃犳硶璁块棶绯荤粺璧勬簮", requestURI, e.getMessage());
+ return JsonUtils.toJsonString(R.fail(HttpStatus.HTTP_UNAUTHORIZED, "璁よ瘉澶辫触锛屾棤娉曡闂郴缁熻祫婧�"));
+ }
+
+ /**
+ * servlet寮傚父
+ */
+ @ExceptionHandler(ServletException.class)
+ public R<Void> handleServletException(ServletException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("璇锋眰鍦板潃'{}',鍙戠敓鏈煡寮傚父.", requestURI, e);
+ return R.fail(e.getMessage());
+ }
+
+ /**
+ * 涓氬姟寮傚父
+ */
+ @ExceptionHandler(BaseException.class)
+ public R<Void> handleBaseException(BaseException e, HttpServletRequest request) {
+ log.error(e.getMessage());
+ return R.fail(e.getMessage());
+ }
+
+ /**
+ * 璇锋眰璺緞涓己灏戝繀闇�鐨勮矾寰勫彉閲�
+ */
+ @ExceptionHandler(MissingPathVariableException.class)
+ public R<Void> handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("璇锋眰璺緞涓己灏戝繀闇�鐨勮矾寰勫彉閲�'{}',鍙戠敓绯荤粺寮傚父.", requestURI);
+ return R.fail(String.format("璇锋眰璺緞涓己灏戝繀闇�鐨勮矾寰勫彉閲廩%s]", e.getVariableName()));
+ }
+
+ /**
+ * 璇锋眰鍙傛暟绫诲瀷涓嶅尮閰�
+ */
+ @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+ public R<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("璇锋眰鍙傛暟绫诲瀷涓嶅尮閰�'{}',鍙戠敓绯荤粺寮傚父.", requestURI);
+ return R.fail(String.format("璇锋眰鍙傛暟绫诲瀷涓嶅尮閰嶏紝鍙傛暟[%s]瑕佹眰绫诲瀷涓猴細'%s'锛屼絾杈撳叆鍊间负锛�'%s'", e.getName(), e.getRequiredType().getName(), e.getValue()));
+ }
+
+ /**
+ * 鎵句笉鍒拌矾鐢�
+ */
+ @ExceptionHandler(NoHandlerFoundException.class)
+ public R<Void> handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("璇锋眰鍦板潃'{}'涓嶅瓨鍦�.", requestURI);
+ return R.fail(HttpStatus.HTTP_NOT_FOUND, e.getMessage());
+ }
+
+ /**
+ * 鎷︽埅鏈煡鐨勮繍琛屾椂寮傚父
+ */
+ @ResponseStatus(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR)
+ @ExceptionHandler(IOException.class)
+ public void handleIoException(IOException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ if (requestURI.contains("sse")) {
+ // sse 缁忓父鎬ц繛鎺ヤ腑鏂� 渚嬪鍏抽棴娴忚鍣� 鐩存帴灞忚斀
+ return;
+ }
+ log.error("璇锋眰鍦板潃'{}',杩炴帴涓柇", requestURI, e);
+ }
+
+ /**
+ * sse 杩炴帴瓒呮椂寮傚父 涓嶉渶瑕佸鐞�
+ */
+ @ExceptionHandler(AsyncRequestTimeoutException.class)
+ public void handleRuntimeException(AsyncRequestTimeoutException e) {
+ }
+
+ /**
+ * 鎷︽埅鏈煡鐨勮繍琛屾椂寮傚父
+ */
+ @ExceptionHandler(RuntimeException.class)
+ public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("璇锋眰鍦板潃'{}',鍙戠敓鏈煡寮傚父.", requestURI, e);
+ return R.fail(e.getMessage());
+ }
+
+ /**
+ * 绯荤粺寮傚父
+ */
+ @ExceptionHandler(Exception.class)
+ public R<Void> handleException(Exception e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("璇锋眰鍦板潃'{}',鍙戠敓绯荤粺寮傚父.", requestURI, e);
+ return R.fail(e.getMessage());
+ }
+
+ /**
+ * 鑷畾涔夐獙璇佸紓甯�
+ */
+ @ExceptionHandler(BindException.class)
+ public R<Void> handleBindException(BindException e) {
+ log.error(e.getMessage());
+ String message = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", ");
+ return R.fail(message);
+ }
+
+ /**
+ * 鑷畾涔夐獙璇佸紓甯�
+ */
+ @ExceptionHandler(ConstraintViolationException.class)
+ public R<Void> constraintViolationException(ConstraintViolationException e) {
+ log.error(e.getMessage());
+ String message = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, ", ");
+ return R.fail(message);
+ }
+
+ /**
+ * 鑷畾涔夐獙璇佸紓甯�
+ */
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+ log.error(e.getMessage());
+ String message = StreamUtils.join(e.getBindingResult().getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", ");
+ return R.fail(message);
+ }
+
+ /**
+ * 鏂规硶鍙傛暟鏍¢獙寮傚父 鐢ㄤ簬澶勭悊 @Validated 娉ㄨВ
+ */
+ @ExceptionHandler(HandlerMethodValidationException.class)
+ public R<Void> handlerMethodValidationException(HandlerMethodValidationException e) {
+ log.error(e.getMessage());
+ String message = StreamUtils.join(e.getAllErrors(), MessageSourceResolvable::getDefaultMessage, ", ");
+ return R.fail(message);
+ }
+
+ /**
+ * JSON 瑙f瀽寮傚父锛圝ackson 鍦ㄥ鐞� JSON 鏍煎紡鍑洪敊鏃舵姏鍑猴級
+ * 鍙兘鏄姹備綋鏍煎紡闈炴硶锛屼篃鍙兘鏄湇鍔$鍙嶅簭鍒楀寲澶辫触
+ */
+ @ExceptionHandler(JsonParseException.class)
+ public R<Void> handleJsonParseException(JsonParseException e, HttpServletRequest request) {
+ String requestURI = request.getRequestURI();
+ log.error("璇锋眰鍦板潃'{}' 鍙戠敓 JSON 瑙f瀽寮傚父: {}", requestURI, e.getMessage());
+ return R.fail(HttpStatus.HTTP_BAD_REQUEST, "璇锋眰鏁版嵁鏍煎紡閿欒锛圝SON 瑙f瀽澶辫触锛夛細" + e.getMessage());
+ }
+
+ /**
+ * 璇锋眰浣撹鍙栧紓甯革紙閫氬父鏄姹傚弬鏁版牸寮忛潪娉曘�佸瓧娈电被鍨嬩笉鍖归厤绛夛級
+ */
+ @ExceptionHandler(HttpMessageNotReadableException.class)
+ public R<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {
+ log.error("璇锋眰鍦板潃'{}', 鍙傛暟瑙f瀽澶辫触: {}", request.getRequestURI(), e.getMessage());
+ return R.fail(HttpStatus.HTTP_BAD_REQUEST, "璇锋眰鍙傛暟鏍煎紡閿欒锛�" + e.getMostSpecificCause().getMessage());
+ }
+
+ /**
+ * SpEL 琛ㄨ揪寮忕浉鍏冲紓甯�
+ */
+ @ExceptionHandler(ExpressionException.class)
+ public R<Void> handleSpelException(ExpressionException e, HttpServletRequest request) {
+ log.error("璇锋眰鍦板潃'{}'锛孲pEL瑙f瀽寮傚父: {}", request.getRequestURI(), e.getMessage());
+ return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "SpEL瑙f瀽澶辫触锛�" + e.getMessage());
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/interceptor/PlusWebInvokeTimeInterceptor.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/interceptor/PlusWebInvokeTimeInterceptor.java
new file mode 100755
index 0000000..b56cec1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/interceptor/PlusWebInvokeTimeInterceptor.java
@@ -0,0 +1,127 @@
+package org.dromara.common.web.interceptor;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.time.StopWatch;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.web.filter.RepeatedlyRequestWrapper;
+import org.springframework.http.MediaType;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * web鐨勮皟鐢ㄦ椂闂寸粺璁℃嫤鎴櫒
+ *
+ * @author Lion Li
+ * @since 3.3.0
+ */
+@Slf4j
+public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
+
+ private final static ThreadLocal<StopWatch> KEY_CACHE = new ThreadLocal<>();
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ String url = request.getMethod() + " " + request.getRequestURI();
+ // 鎵撳嵃璇锋眰鍙傛暟
+ if (isJsonRequest(request)) {
+ String jsonParam = "";
+ if (request instanceof RepeatedlyRequestWrapper) {
+ jsonParam = IoUtil.read(request.getReader());
+ if (StringUtils.isNotBlank(jsonParam)) {
+ ObjectMapper objectMapper = JsonUtils.getObjectMapper();
+ JsonNode rootNode = objectMapper.readTree(jsonParam);
+ removeSensitiveFields(rootNode, SystemConstants.EXCLUDE_PROPERTIES);
+ jsonParam = rootNode.toString();
+ }
+ }
+ log.info("[PLUS]寮�濮嬭姹� => URL[{}],鍙傛暟绫诲瀷[json],鍙傛暟:[{}]", url, jsonParam);
+ } else {
+ Map<String, String[]> parameterMap = request.getParameterMap();
+ if (MapUtil.isNotEmpty(parameterMap)) {
+ Map<String, String[]> map = new LinkedHashMap<>(parameterMap);
+ MapUtil.removeAny(map, SystemConstants.EXCLUDE_PROPERTIES);
+ String parameters = JsonUtils.toJsonString(map);
+ log.info("[PLUS]寮�濮嬭姹� => URL[{}],鍙傛暟绫诲瀷[param],鍙傛暟:[{}]", url, parameters);
+ } else {
+ log.info("[PLUS]寮�濮嬭姹� => URL[{}],鏃犲弬鏁�", url);
+ }
+ }
+
+ StopWatch stopWatch = new StopWatch();
+ KEY_CACHE.set(stopWatch);
+ stopWatch.start();
+
+ return true;
+ }
+
+ private void removeSensitiveFields(JsonNode node, String[] excludeProperties) {
+ if (node == null) {
+ return;
+ }
+ if (node.isObject()) {
+ ObjectNode objectNode = (ObjectNode) node;
+ // 鏀堕泦瑕佸垹闄ょ殑瀛楁鍚嶏紙閬垮厤 ConcurrentModification锛�
+ Set<String> fieldsToRemove = new HashSet<>();
+ objectNode.fieldNames().forEachRemaining(fieldName -> {
+ if (ArrayUtil.contains(excludeProperties, fieldName)) {
+ fieldsToRemove.add(fieldName);
+ }
+ });
+ fieldsToRemove.forEach(objectNode::remove);
+ // 閫掑綊澶勭悊瀛愯妭鐐�
+ objectNode.elements().forEachRemaining(child -> removeSensitiveFields(child, excludeProperties));
+ } else if (node.isArray()) {
+ ArrayNode arrayNode = (ArrayNode) node;
+ for (JsonNode child : arrayNode) {
+ removeSensitiveFields(child, excludeProperties);
+ }
+ }
+ }
+
+ @Override
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
+
+ }
+
+ @Override
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+ StopWatch stopWatch = KEY_CACHE.get();
+ if (ObjectUtil.isNotNull(stopWatch)) {
+ stopWatch.stop();
+ log.info("[PLUS]缁撴潫璇锋眰 => URL[{}],鑰楁椂:[{}]姣", request.getMethod() + " " + request.getRequestURI(), stopWatch.getDuration().toMillis());
+ KEY_CACHE.remove();
+ }
+ }
+
+ /**
+ * 鍒ゆ柇鏈璇锋眰鐨勬暟鎹被鍨嬫槸鍚︿负json
+ *
+ * @param request request
+ * @return boolean
+ */
+ private boolean isJsonRequest(HttpServletRequest request) {
+ String contentType = request.getContentType();
+ if (contentType != null) {
+ return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE);
+ }
+ return false;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..fc10a36
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,5 @@
+org.dromara.common.web.config.CaptchaConfig
+org.dromara.common.web.config.FilterConfig
+org.dromara.common.web.config.I18nConfig
+org.dromara.common.web.config.ResourcesConfig
+org.dromara.common.web.config.UndertowConfig
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/.flattened-pom.xml
new file mode 100644
index 0000000..792d520
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/.flattened-pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-websocket</artifactId>
+ <version>5.5.3</version>
+ <description>ruoyi-common-websocket 妯″潡</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-satoken</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-websocket</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-tomcat</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/pom.xml b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/pom.xml
new file mode 100755
index 0000000..0587cd7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-common-websocket</artifactId>
+
+ <description>
+ ruoyi-common-websocket 妯″潡
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-satoken</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-websocket</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-tomcat</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/WebSocketConfig.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/WebSocketConfig.java
new file mode 100755
index 0000000..ef5cfc9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/WebSocketConfig.java
@@ -0,0 +1,63 @@
+package org.dromara.common.websocket.config;
+
+import cn.hutool.core.util.StrUtil;
+import org.dromara.common.websocket.config.properties.WebSocketProperties;
+import org.dromara.common.websocket.handler.PlusWebSocketHandler;
+import org.dromara.common.websocket.interceptor.PlusWebSocketInterceptor;
+import org.dromara.common.websocket.listener.WebSocketTopicListener;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
+import org.springframework.web.socket.server.HandshakeInterceptor;
+
+/**
+ * WebSocket 閰嶇疆
+ *
+ * @author zendwang
+ */
+@AutoConfiguration
+@ConditionalOnProperty(value = "websocket.enabled", havingValue = "true")
+@EnableConfigurationProperties(WebSocketProperties.class)
+@EnableWebSocket
+public class WebSocketConfig {
+
+ @Bean
+ public WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor handshakeInterceptor,
+ WebSocketHandler webSocketHandler, WebSocketProperties webSocketProperties) {
+ // 濡傛灉WebSocket鐨勮矾寰勪负绌猴紝鍒欒缃粯璁よ矾寰勪负 "/websocket"
+ if (StrUtil.isBlank(webSocketProperties.getPath())) {
+ webSocketProperties.setPath("/websocket");
+ }
+
+ // 濡傛灉鍏佽璺ㄥ煙璁块棶鐨勫湴鍧�涓虹┖锛屽垯璁剧疆涓� "*"锛岃〃绀哄厑璁告墍鏈夋潵婧愮殑璺ㄥ煙璇锋眰
+ if (StrUtil.isBlank(webSocketProperties.getAllowedOrigins())) {
+ webSocketProperties.setAllowedOrigins("*");
+ }
+
+ // 杩斿洖涓�涓猈ebSocketConfigurer瀵硅薄锛岀敤浜庨厤缃甒ebSocket
+ return registry -> registry
+ // 娣诲姞WebSocket澶勭悊绋嬪簭鍜屾嫤鎴櫒鍒版寚瀹氳矾寰勶紝璁剧疆鍏佽鐨勮法鍩熸潵婧�
+ .addHandler(webSocketHandler, webSocketProperties.getPath())
+ .addInterceptors(handshakeInterceptor)
+ .setAllowedOrigins(webSocketProperties.getAllowedOrigins());
+ }
+
+ @Bean
+ public HandshakeInterceptor handshakeInterceptor() {
+ return new PlusWebSocketInterceptor();
+ }
+
+ @Bean
+ public WebSocketHandler webSocketHandler() {
+ return new PlusWebSocketHandler();
+ }
+
+ @Bean
+ public WebSocketTopicListener topicListener() {
+ return new WebSocketTopicListener();
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/properties/WebSocketProperties.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/properties/WebSocketProperties.java
new file mode 100755
index 0000000..d629fe5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/properties/WebSocketProperties.java
@@ -0,0 +1,26 @@
+package org.dromara.common.websocket.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * WebSocket 閰嶇疆椤�
+ *
+ * @author zendwang
+ */
+@ConfigurationProperties("websocket")
+@Data
+public class WebSocketProperties {
+
+ private Boolean enabled;
+
+ /**
+ * 璺緞
+ */
+ private String path;
+
+ /**
+ * 璁剧疆璁块棶婧愬湴鍧�
+ */
+ private String allowedOrigins;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/constant/WebSocketConstants.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/constant/WebSocketConstants.java
new file mode 100755
index 0000000..e243279
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/constant/WebSocketConstants.java
@@ -0,0 +1,29 @@
+package org.dromara.common.websocket.constant;
+
+/**
+ * websocket鐨勫父閲忛厤缃�
+ *
+ * @author zendwang
+ */
+public interface WebSocketConstants {
+
+ /**
+ * websocketSession涓殑鍙傛暟鐨刱ey
+ */
+ String LOGIN_USER_KEY = "loginUser";
+
+ /**
+ * 璁㈤槄鐨勯閬�
+ */
+ String WEB_SOCKET_TOPIC = "global:websocket";
+
+ /**
+ * 鍓嶇蹇冭烦妫�鏌ョ殑鍛戒护
+ */
+ String PING = "ping";
+
+ /**
+ * 鏈嶅姟绔績璺虫仮澶嶇殑瀛楃涓�
+ */
+ String PONG = "pong";
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/dto/WebSocketMessageDto.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/dto/WebSocketMessageDto.java
new file mode 100755
index 0000000..e2d4456
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/dto/WebSocketMessageDto.java
@@ -0,0 +1,29 @@
+package org.dromara.common.websocket.dto;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 娑堟伅鐨刣to
+ *
+ * @author zendwang
+ */
+@Data
+public class WebSocketMessageDto implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 闇�瑕佹帹閫佸埌鐨剆ession key 鍒楄〃
+ */
+ private List<Long> sessionKeys;
+
+ /**
+ * 闇�瑕佸彂閫佺殑娑堟伅
+ */
+ private String message;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/handler/PlusWebSocketHandler.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/handler/PlusWebSocketHandler.java
new file mode 100755
index 0000000..3b6e076
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/handler/PlusWebSocketHandler.java
@@ -0,0 +1,123 @@
+package org.dromara.common.websocket.handler;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.websocket.dto.WebSocketMessageDto;
+import org.dromara.common.websocket.holder.WebSocketSessionHolder;
+import org.dromara.common.websocket.utils.WebSocketUtils;
+import org.springframework.web.socket.*;
+import org.springframework.web.socket.handler.AbstractWebSocketHandler;
+import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.dromara.common.websocket.constant.WebSocketConstants.LOGIN_USER_KEY;
+
+/**
+ * WebSocketHandler 瀹炵幇绫�
+ *
+ * @author zendwang
+ */
+@Slf4j
+public class PlusWebSocketHandler extends AbstractWebSocketHandler {
+
+ /**
+ * 杩炴帴鎴愬姛鍚�
+ */
+ @Override
+ public void afterConnectionEstablished(WebSocketSession session) throws IOException {
+ LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
+ if (ObjectUtil.isNull(loginUser)) {
+ session.close(CloseStatus.BAD_DATA);
+ log.info("[connect] invalid token received. sessionId: {}", session.getId());
+ return;
+ }
+ WebSocketSessionHolder.addSession(loginUser.getUserId(), new ConcurrentWebSocketSessionDecorator(session, 10 * 1000, 64000));
+ log.info("[connect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
+ }
+
+ /**
+ * 澶勭悊鎺ユ敹鍒扮殑鏂囨湰娑堟伅
+ *
+ * @param session WebSocket浼氳瘽
+ * @param message 鎺ユ敹鍒扮殑鏂囨湰娑堟伅
+ * @throws Exception 澶勭悊娑堟伅杩囩▼涓彲鑳芥姏鍑虹殑寮傚父
+ */
+ @Override
+ protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+ // 浠嶹ebSocket浼氳瘽涓幏鍙栫櫥褰曠敤鎴蜂俊鎭�
+ LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
+
+ // 鍒涘缓WebSocket娑堟伅DTO瀵硅薄
+ WebSocketMessageDto webSocketMessageDto = new WebSocketMessageDto();
+ webSocketMessageDto.setSessionKeys(List.of(loginUser.getUserId()));
+ webSocketMessageDto.setMessage(message.getPayload());
+ WebSocketUtils.publishMessage(webSocketMessageDto);
+ }
+
+ /**
+ * 澶勭悊鎺ユ敹鍒扮殑浜岃繘鍒舵秷鎭�
+ *
+ * @param session WebSocket浼氳瘽
+ * @param message 鎺ユ敹鍒扮殑浜岃繘鍒舵秷鎭�
+ * @throws Exception 澶勭悊娑堟伅杩囩▼涓彲鑳芥姏鍑虹殑寮傚父
+ */
+ @Override
+ protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
+ super.handleBinaryMessage(session, message);
+ }
+
+ /**
+ * 澶勭悊鎺ユ敹鍒扮殑Pong娑堟伅锛堝績璺崇洃娴嬶級
+ *
+ * @param session WebSocket浼氳瘽
+ * @param message 鎺ユ敹鍒扮殑Pong娑堟伅
+ * @throws Exception 澶勭悊娑堟伅杩囩▼涓彲鑳芥姏鍑虹殑寮傚父
+ */
+ @Override
+ protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
+ WebSocketUtils.sendPongMessage(session);
+ }
+
+ /**
+ * 澶勭悊WebSocket浼犺緭閿欒
+ *
+ * @param session WebSocket浼氳瘽
+ * @param exception 鍙戠敓鐨勫紓甯�
+ * @throws Exception 澶勭悊杩囩▼涓彲鑳芥姏鍑虹殑寮傚父
+ */
+ @Override
+ public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
+ log.error("[transport error] sessionId: {} , exception:{}", session.getId(), exception.getMessage());
+ }
+
+ /**
+ * 鍦╓ebSocket杩炴帴鍏抽棴鍚庢墽琛屾竻鐞嗘搷浣�
+ *
+ * @param session WebSocket浼氳瘽
+ * @param status 鍏抽棴鐘舵�佷俊鎭�
+ */
+ @Override
+ public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
+ LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
+ if (ObjectUtil.isNull(loginUser)) {
+ log.info("[disconnect] invalid token received. sessionId: {}", session.getId());
+ return;
+ }
+ WebSocketSessionHolder.removeSession(loginUser.getUserId());
+ log.info("[disconnect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
+ }
+
+ /**
+ * 鎸囩ず澶勭悊绋嬪簭鏄惁鏀寔鎺ユ敹閮ㄥ垎娑堟伅
+ *
+ * @return 濡傛灉鏀寔鎺ユ敹閮ㄥ垎娑堟伅锛屽垯杩斿洖true锛涘惁鍒欒繑鍥瀎alse
+ */
+ @Override
+ public boolean supportsPartialMessages() {
+ return false;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/holder/WebSocketSessionHolder.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/holder/WebSocketSessionHolder.java
new file mode 100755
index 0000000..9c2372b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/holder/WebSocketSessionHolder.java
@@ -0,0 +1,74 @@
+package org.dromara.common.websocket.holder;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.WebSocketSession;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * WebSocketSession 鐢ㄤ簬淇濆瓨褰撳墠鎵�鏈夊湪绾跨殑浼氳瘽淇℃伅
+ *
+ * @author zendwang
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class WebSocketSessionHolder {
+
+ private static final Map<Long, WebSocketSession> USER_SESSION_MAP = new ConcurrentHashMap<>();
+
+ /**
+ * 灏哤ebSocket浼氳瘽娣诲姞鍒扮敤鎴蜂細璇滿ap涓�
+ *
+ * @param sessionKey 浼氳瘽閿紝鐢ㄤ簬妫�绱細璇�
+ * @param session 瑕佹坊鍔犵殑WebSocket浼氳瘽
+ */
+ public static void addSession(Long sessionKey, WebSocketSession session) {
+ removeSession(sessionKey);
+ USER_SESSION_MAP.put(sessionKey, session);
+ }
+
+ /**
+ * 浠庣敤鎴蜂細璇滿ap涓Щ闄ゆ寚瀹氫細璇濋敭瀵瑰簲鐨刉ebSocket浼氳瘽
+ *
+ * @param sessionKey 瑕佺Щ闄ょ殑浼氳瘽閿�
+ */
+ public static void removeSession(Long sessionKey) {
+ WebSocketSession session = USER_SESSION_MAP.remove(sessionKey);
+ try {
+ session.close(CloseStatus.BAD_DATA);
+ } catch (Exception ignored) {
+ }
+ }
+
+ /**
+ * 鏍规嵁浼氳瘽閿粠鐢ㄦ埛浼氳瘽Map涓幏鍙朩ebSocket浼氳瘽
+ *
+ * @param sessionKey 瑕佽幏鍙栫殑浼氳瘽閿�
+ * @return 涓庣粰瀹氫細璇濋敭瀵瑰簲鐨刉ebSocket浼氳瘽锛屽鏋滀笉瀛樺湪鍒欒繑鍥瀗ull
+ */
+ public static WebSocketSession getSessions(Long sessionKey) {
+ return USER_SESSION_MAP.get(sessionKey);
+ }
+
+ /**
+ * 鑾峰彇瀛樺偍鍦ㄧ敤鎴蜂細璇滿ap涓墍鏈塛ebSocket浼氳瘽鐨勪細璇濋敭闆嗗悎
+ *
+ * @return 鎵�鏈塛ebSocket浼氳瘽鐨勪細璇濋敭闆嗗悎
+ */
+ public static Set<Long> getSessionsAll() {
+ return USER_SESSION_MAP.keySet();
+ }
+
+ /**
+ * 妫�鏌ョ粰瀹氱殑浼氳瘽閿槸鍚﹀瓨鍦ㄤ簬鐢ㄦ埛浼氳瘽Map涓�
+ *
+ * @param sessionKey 瑕佹鏌ョ殑浼氳瘽閿�
+ * @return 濡傛灉瀛樺湪瀵瑰簲鐨勪細璇濋敭锛屽垯杩斿洖true锛涘惁鍒欒繑鍥瀎alse
+ */
+ public static Boolean existSession(Long sessionKey) {
+ return USER_SESSION_MAP.containsKey(sessionKey);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/interceptor/PlusWebSocketInterceptor.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/interceptor/PlusWebSocketInterceptor.java
new file mode 100755
index 0000000..c70e377
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/interceptor/PlusWebSocketInterceptor.java
@@ -0,0 +1,75 @@
+package org.dromara.common.websocket.interceptor;
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.stp.StpUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.server.HandshakeInterceptor;
+
+import java.util.Map;
+
+import static org.dromara.common.websocket.constant.WebSocketConstants.LOGIN_USER_KEY;
+
+/**
+ * WebSocket鎻℃墜璇锋眰鐨勬嫤鎴櫒
+ *
+ * @author zendwang
+ */
+@Slf4j
+public class PlusWebSocketInterceptor implements HandshakeInterceptor {
+
+ /**
+ * WebSocket鎻℃墜涔嬪墠鎵ц鐨勫墠缃鐞嗘柟娉�
+ *
+ * @param request WebSocket鎻℃墜璇锋眰
+ * @param response WebSocket鎻℃墜鍝嶅簲
+ * @param wsHandler WebSocket澶勭悊绋嬪簭
+ * @param attributes 涓嶹ebSocket浼氳瘽鍏宠仈鐨勫睘鎬�
+ * @return 濡傛灉鍏佽鎻℃墜缁х画杩涜锛屽垯杩斿洖true锛涘惁鍒欒繑鍥瀎alse
+ */
+ @Override
+ public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
+ try {
+ // 妫�鏌ユ槸鍚︾櫥褰� 鏄惁鏈塼oken
+ LoginUser loginUser = LoginHelper.getLoginUser();
+
+ // 瑙e喅 ws 涓嶈蛋 mvc 鎷︽埅鍣ㄩ棶棰�(cloud 鐗堟湰涓嶅彈褰卞搷)
+ // 妫�鏌� header 涓� param 閲岀殑 clientid 涓� token 閲岀殑鏄惁涓�鑷�
+ String headerCid = ServletUtils.getRequest().getHeader(LoginHelper.CLIENT_KEY);
+ String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY);
+ String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString();
+ if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) {
+ // token 鏃犳晥
+ throw NotLoginException.newInstance(StpUtil.getLoginType(),
+ "-100", "瀹㈡埛绔疘D涓嶵oken涓嶅尮閰�",
+ StpUtil.getTokenValue());
+ }
+
+ attributes.put(LOGIN_USER_KEY, loginUser);
+ return true;
+ } catch (NotLoginException e) {
+ log.error("WebSocket 璁よ瘉澶辫触'{}',鏃犳硶璁块棶绯荤粺璧勬簮", e.getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * WebSocket鎻℃墜鎴愬姛鍚庢墽琛岀殑鍚庣疆澶勭悊鏂规硶
+ *
+ * @param request WebSocket鎻℃墜璇锋眰
+ * @param response WebSocket鎻℃墜鍝嶅簲
+ * @param wsHandler WebSocket澶勭悊绋嬪簭
+ * @param exception 鎻℃墜杩囩▼涓彲鑳藉嚭鐜扮殑寮傚父
+ */
+ @Override
+ public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
+ // 鍦ㄨ繖涓柟娉曚腑鍙互鎵ц涓�浜涙彙鎵嬫垚鍔熷悗鐨勫悗缁鐞嗛�昏緫锛屾瘮濡傝褰曟棩蹇楁垨鑰呭叾浠栨搷浣�
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/listener/WebSocketTopicListener.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/listener/WebSocketTopicListener.java
new file mode 100755
index 0000000..0ad39af
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/listener/WebSocketTopicListener.java
@@ -0,0 +1,50 @@
+package org.dromara.common.websocket.listener;
+
+import cn.hutool.core.collection.CollUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.websocket.holder.WebSocketSessionHolder;
+import org.dromara.common.websocket.utils.WebSocketUtils;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.Ordered;
+
+/**
+ * WebSocket 涓婚璁㈤槄鐩戝惉鍣�
+ *
+ * @author zendwang
+ */
+@Slf4j
+public class WebSocketTopicListener implements ApplicationRunner, Ordered {
+
+ /**
+ * 鍦⊿pring Boot搴旂敤绋嬪簭鍚姩鏃跺垵濮嬪寲WebSocket涓婚璁㈤槄鐩戝惉鍣�
+ *
+ * @param args 搴旂敤绋嬪簭鍙傛暟
+ * @throws Exception 鍒濆鍖栬繃绋嬩腑鍙兘鎶涘嚭鐨勫紓甯�
+ */
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ // 璁㈤槄WebSocket娑堟伅
+ WebSocketUtils.subscribeMessage((message) -> {
+ log.info("WebSocket涓婚璁㈤槄鏀跺埌娑堟伅session keys={} message={}", message.getSessionKeys(), message.getMessage());
+ // 濡傛灉key涓嶄负绌哄氨鎸夌収key鍙戞秷鎭� 濡傛灉涓虹┖灏辩兢鍙�
+ if (CollUtil.isNotEmpty(message.getSessionKeys())) {
+ message.getSessionKeys().forEach(key -> {
+ if (WebSocketSessionHolder.existSession(key)) {
+ WebSocketUtils.sendMessage(key, message.getMessage());
+ }
+ });
+ } else {
+ WebSocketSessionHolder.getSessionsAll().forEach(key -> {
+ WebSocketUtils.sendMessage(key, message.getMessage());
+ });
+ }
+ });
+ log.info("鍒濆鍖朩ebSocket涓婚璁㈤槄鐩戝惉鍣ㄦ垚鍔�");
+ }
+
+ @Override
+ public int getOrder() {
+ return -1;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/utils/WebSocketUtils.java b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/utils/WebSocketUtils.java
new file mode 100755
index 0000000..afe76e0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/utils/WebSocketUtils.java
@@ -0,0 +1,127 @@
+package org.dromara.common.websocket.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.websocket.dto.WebSocketMessageDto;
+import org.dromara.common.websocket.holder.WebSocketSessionHolder;
+import org.springframework.web.socket.PongMessage;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketMessage;
+import org.springframework.web.socket.WebSocketSession;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static org.dromara.common.websocket.constant.WebSocketConstants.WEB_SOCKET_TOPIC;
+
+/**
+ * 宸ュ叿绫�
+ *
+ * @author zendwang
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class WebSocketUtils {
+
+ /**
+ * 鍚戞寚瀹氱殑WebSocket浼氳瘽鍙戦�佹秷鎭�
+ *
+ * @param sessionKey 瑕佸彂閫佹秷鎭殑鐢ㄦ埛id
+ * @param message 瑕佸彂閫佺殑娑堟伅鍐呭
+ */
+ public static void sendMessage(Long sessionKey, String message) {
+ WebSocketSession session = WebSocketSessionHolder.getSessions(sessionKey);
+ sendMessage(session, message);
+ }
+
+ /**
+ * 璁㈤槄WebSocket娑堟伅涓婚锛屽苟鎻愪緵涓�涓秷璐硅�呭嚱鏁版潵澶勭悊鎺ユ敹鍒扮殑娑堟伅
+ *
+ * @param consumer 澶勭悊WebSocket娑堟伅鐨勬秷璐硅�呭嚱鏁�
+ */
+ public static void subscribeMessage(Consumer<WebSocketMessageDto> consumer) {
+ RedisUtils.subscribe(WEB_SOCKET_TOPIC, WebSocketMessageDto.class, consumer);
+ }
+
+ /**
+ * 鍙戝竷WebSocket璁㈤槄娑堟伅
+ *
+ * @param webSocketMessage 瑕佸彂甯冪殑WebSocket娑堟伅瀵硅薄
+ */
+ public static void publishMessage(WebSocketMessageDto webSocketMessage) {
+ List<Long> unsentSessionKeys = new ArrayList<>();
+ // 褰撳墠鏈嶅姟鍐卻ession,鐩存帴鍙戦�佹秷鎭�
+ for (Long sessionKey : webSocketMessage.getSessionKeys()) {
+ if (WebSocketSessionHolder.existSession(sessionKey)) {
+ WebSocketUtils.sendMessage(sessionKey, webSocketMessage.getMessage());
+ continue;
+ }
+ unsentSessionKeys.add(sessionKey);
+ }
+ // 涓嶅湪褰撳墠鏈嶅姟鍐卻ession,鍙戝竷璁㈤槄娑堟伅
+ if (CollUtil.isNotEmpty(unsentSessionKeys)) {
+ WebSocketMessageDto broadcastMessage = new WebSocketMessageDto();
+ broadcastMessage.setMessage(webSocketMessage.getMessage());
+ broadcastMessage.setSessionKeys(unsentSessionKeys);
+ RedisUtils.publish(WEB_SOCKET_TOPIC, broadcastMessage, consumer -> {
+ log.info(" WebSocket鍙戦�佷富棰樿闃呮秷鎭痶opic:{} session keys:{} message:{}",
+ WEB_SOCKET_TOPIC, unsentSessionKeys, webSocketMessage.getMessage());
+ });
+ }
+ }
+
+ /**
+ * 鍚戞墍鏈夌殑WebSocket浼氳瘽鍙戝竷璁㈤槄鐨勬秷鎭�(缇ゅ彂)
+ *
+ * @param message 瑕佸彂甯冪殑娑堟伅鍐呭
+ */
+ public static void publishAll(String message) {
+ WebSocketMessageDto broadcastMessage = new WebSocketMessageDto();
+ broadcastMessage.setMessage(message);
+ RedisUtils.publish(WEB_SOCKET_TOPIC, broadcastMessage, consumer -> {
+ log.info("WebSocket鍙戦�佷富棰樿闃呮秷鎭痶opic:{} message:{}", WEB_SOCKET_TOPIC, message);
+ });
+ }
+
+ /**
+ * 鍚戞寚瀹氱殑WebSocket浼氳瘽鍙戦�丳ong娑堟伅
+ *
+ * @param session 瑕佸彂閫丳ong娑堟伅鐨刉ebSocket浼氳瘽
+ */
+ public static void sendPongMessage(WebSocketSession session) {
+ sendMessage(session, new PongMessage());
+ }
+
+ /**
+ * 鍚戞寚瀹氱殑WebSocket浼氳瘽鍙戦�佹枃鏈秷鎭�
+ *
+ * @param session WebSocket浼氳瘽
+ * @param message 瑕佸彂閫佺殑鏂囨湰娑堟伅鍐呭
+ */
+ public static void sendMessage(WebSocketSession session, String message) {
+ sendMessage(session, new TextMessage(message));
+ }
+
+ /**
+ * 鍚戞寚瀹氱殑WebSocket浼氳瘽鍙戦�乄ebSocket娑堟伅瀵硅薄
+ *
+ * @param session WebSocket浼氳瘽
+ * @param message 瑕佸彂閫佺殑WebSocket娑堟伅瀵硅薄
+ */
+ private static void sendMessage(WebSocketSession session, WebSocketMessage<?> message) {
+ if (session == null || !session.isOpen()) {
+ log.warn("[send] session浼氳瘽宸茬粡鍏抽棴");
+ } else {
+ try {
+ session.sendMessage(message);
+ } catch (IOException e) {
+ log.error("[send] session({}) 鍙戦�佹秷鎭�({}) 寮傚父", session, message, e);
+ }
+ }
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100755
index 0000000..c3a7305
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.websocket.config.WebSocketConfig
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/.DS_Store b/RuoYi-Vue-Plus/ruoyi-extend/.DS_Store
new file mode 100755
index 0000000..f199592
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/.DS_Store
Binary files differ
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-extend/.flattened-pom.xml
new file mode 100644
index 0000000..cdf8a6a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/.flattened-pom.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-vue-plus</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-extend</artifactId>
+ <version>5.5.3</version>
+ <packaging>pom</packaging>
+ <modules>
+ <module>ruoyi-monitor-admin</module>
+ <module>ruoyi-snailjob-server</module>
+ </modules>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/pom.xml b/RuoYi-Vue-Plus/ruoyi-extend/pom.xml
new file mode 100755
index 0000000..d7280ce
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/pom.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>ruoyi-vue-plus</artifactId>
+ <groupId>org.dromara</groupId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>ruoyi-extend</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>ruoyi-monitor-admin</module>
+ <module>ruoyi-snailjob-server</module>
+ </modules>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/.flattened-pom.xml
new file mode 100644
index 0000000..5f3fe13
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/.flattened-pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-extend</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-monitor-admin</artifactId>
+ <version>5.5.3</version>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-tomcat</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-undertow</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-security</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </dependency>
+ </dependencies>
+ <build>
+ <finalName>${project.artifactId}</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring-boot.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/Dockerfile b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/Dockerfile
new file mode 100755
index 0000000..f39ef90
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/Dockerfile
@@ -0,0 +1,22 @@
+# 璐濆皵瀹為獙瀹� Spring 瀹樻柟鎺ㄨ崘闀滃儚 JDK涓嬭浇鍦板潃 https://bell-sw.com/pages/downloads/
+FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
+#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
+#FROM findepi/graalvm:java17-native
+
+LABEL maintainer="Lion Li"
+
+RUN mkdir -p /ruoyi/monitor/logs
+
+WORKDIR /ruoyi/monitor
+
+ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
+
+EXPOSE 9090
+
+ADD ./target/ruoyi-monitor-admin.jar ./app.jar
+
+SHELL ["/bin/bash", "-c"]
+
+ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom \
+ -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
+ -jar app.jar
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/pom.xml b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/pom.xml
new file mode 100755
index 0000000..538ffbd
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>ruoyi-extend</artifactId>
+ <groupId>org.dromara</groupId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>jar</packaging>
+ <artifactId>ruoyi-monitor-admin</artifactId>
+
+ <dependencies>
+ <!-- SpringBoot Web瀹瑰櫒 -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <exclusions>
+ <exclusion>
+ <artifactId>spring-boot-starter-tomcat</artifactId>
+ <groupId>org.springframework.boot</groupId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <!-- web 瀹瑰櫒浣跨敤 undertow 鎬ц兘鏇村己 -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-undertow</artifactId>
+ </dependency>
+
+ <!-- spring security 瀹夊叏璁よ瘉 -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-security</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-server</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-client</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <finalName>${project.artifactId}</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring-boot.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/MonitorAdminApplication.java b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/MonitorAdminApplication.java
new file mode 100755
index 0000000..a733561
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/MonitorAdminApplication.java
@@ -0,0 +1,21 @@
+package org.dromara.monitor.admin;
+
+import de.codecentric.boot.admin.server.config.EnableAdminServer;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Admin 鐩戞帶鍚姩绋嬪簭
+ *
+ * @author Lion Li
+ */
+@EnableAdminServer
+@SpringBootApplication
+public class MonitorAdminApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(MonitorAdminApplication.class, args);
+ System.out.println("Admin 鐩戞帶鍚姩鎴愬姛");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/SecurityConfig.java b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/SecurityConfig.java
new file mode 100755
index 0000000..41372f2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/SecurityConfig.java
@@ -0,0 +1,54 @@
+package org.dromara.monitor.admin.config;
+
+import de.codecentric.boot.admin.server.config.AdminServerProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
+import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
+
+/**
+ * admin 鐩戞帶 瀹夊叏閰嶇疆
+ *
+ * @author Lion Li
+ */
+@EnableWebSecurity
+@Configuration
+public class SecurityConfig {
+
+ private final String adminContextPath;
+
+ public SecurityConfig(AdminServerProperties adminServerProperties) {
+ this.adminContextPath = adminServerProperties.getContextPath();
+ }
+
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
+ SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
+ successHandler.setTargetUrlParameter("redirectTo");
+ successHandler.setDefaultTargetUrl(adminContextPath + "/");
+ PathPatternRequestMatcher.Builder mvc = PathPatternRequestMatcher.withDefaults();
+ return httpSecurity
+ .headers((header) ->
+ header.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
+ .authorizeHttpRequests((authorize) ->
+ authorize.requestMatchers(
+ mvc.matcher(adminContextPath + "/assets/**"),
+ mvc.matcher(adminContextPath + "/login")
+ ).permitAll()
+ .anyRequest().authenticated())
+ .formLogin((formLogin) ->
+ formLogin.loginPage(adminContextPath + "/login").successHandler(successHandler))
+ .logout((logout) ->
+ logout.logoutUrl(adminContextPath + "/logout"))
+ .httpBasic(Customizer.withDefaults())
+ .csrf(AbstractHttpConfigurer::disable)
+ .build();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/notifier/CustomNotifier.java b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/notifier/CustomNotifier.java
new file mode 100755
index 0000000..838eefc
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/notifier/CustomNotifier.java
@@ -0,0 +1,55 @@
+package org.dromara.monitor.admin.notifier;
+
+import de.codecentric.boot.admin.server.domain.entities.Instance;
+import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
+import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
+import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
+import de.codecentric.boot.admin.server.notify.AbstractEventNotifier;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Mono;
+
+import static de.codecentric.boot.admin.server.domain.values.StatusInfo.*;
+
+/**
+ * 鑷畾涔変簨浠堕�氱煡澶勭悊
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Component
+public class CustomNotifier extends AbstractEventNotifier {
+
+ protected CustomNotifier(InstanceRepository repository) {
+ super(repository);
+ }
+
+ @Override
+ @SuppressWarnings("all")
+ protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
+ return Mono.fromRunnable(() -> {
+ // 瀹炰緥鐘舵�佹敼鍙樹簨浠�
+ if (event instanceof InstanceStatusChangedEvent) {
+ // 鑾峰彇瀹炰緥娉ㄥ唽鍚嶇О
+ String registName = instance.getRegistration().getName();
+ // 鑾峰彇瀹炰緥ID
+ String instanceId = event.getInstance().getValue();
+ // 鑾峰彇瀹炰緥鐘舵��
+ String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();
+ // 鑾峰彇鏈嶅姟URL
+ String serviceUrl = instance.getRegistration().getServiceUrl();
+ String statusName = switch (status) {
+ case STATUS_UP -> "鏈嶅姟涓婄嚎"; // 瀹炰緥鎴愬姛鍚姩骞跺彲浠ユ甯稿鐞嗚姹�
+ case STATUS_OFFLINE -> "鏈嶅姟绂荤嚎"; //瀹炰緥琚墜鍔ㄦ垨鑷姩鍦颁粠鏈嶅姟涓Щ闄�
+ case STATUS_RESTRICTED -> "鏈嶅姟鍙楅檺"; //琛ㄧず瀹炰緥鍦ㄦ煇浜涙柟闈㈠彈闄愶紝鍙兘鏃犳硶瀹屽叏鎻愪緵鎵�鏈夋湇鍔�
+ case STATUS_OUT_OF_SERVICE -> "鍋滄鏈嶅姟鐘舵��"; //琛ㄧず瀹炰緥宸茶鏍囪涓哄仠姝㈡彁渚涙湇鍔★紝鍙兘鏄鍒掑唴缁存姢鎴栨祴璇�
+ case STATUS_DOWN -> "鏈嶅姟涓嬬嚎"; //瀹炰緥鍥犲穿婧冦�侀敊璇垨鍏朵粬鍘熷洜鍋滄杩愯
+ case STATUS_UNKNOWN -> "鏈嶅姟鏈煡寮傚父"; //鐩戞帶绯荤粺鏃犳硶纭畾瀹炰緥鐨勫綋鍓嶇姸鎬�
+ default -> "鏈煡鐘舵��"; //娌℃湁鍖归厤鐨勭姸鎬�
+ };
+ log.info("Instance Status Change: 鐘舵�佸悕绉般�恵}銆�, 娉ㄥ唽鍚嶇О銆恵}銆�, 瀹炰緥ID銆恵}銆�, 鐘舵�併�恵}銆�, 鏈嶅姟URL銆恵}銆�",
+ statusName, registName, instanceId, status, serviceUrl);
+ }
+ });
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml
new file mode 100755
index 0000000..1dd5a83
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml
@@ -0,0 +1,51 @@
+server:
+ port: 9090
+spring:
+ application:
+ name: ruoyi-monitor-admin
+ profiles:
+ active: @profiles.active@
+
+logging:
+ config: classpath:logback-plus.xml
+
+--- # 鐩戞帶涓績鏈嶅姟绔厤缃�
+spring:
+ security:
+ user:
+ name: @monitor.username@
+ password: @monitor.password@
+ boot:
+ admin:
+ ui:
+ title: RuoYi-Vue-Plus鏈嶅姟鐩戞帶涓績
+ context-path: /admin
+ # 蹇界暐鏃犵敤璀﹀憡
+ thymeleaf:
+ check-template-location: false
+
+--- # Actuator 鐩戞帶绔偣鐨勯厤缃」
+management:
+ endpoints:
+ web:
+ exposure:
+ include: '*'
+ endpoint:
+ health:
+ show-details: ALWAYS
+ logfile:
+ external-file: ./logs/ruoyi-monitor-admin.log
+
+--- # 鐩戞帶閰嶇疆
+spring.boot.admin.client:
+ # 澧炲姞瀹㈡埛绔紑鍏�
+ enabled: true
+ # 璁剧疆 Spring Boot Admin Server 鍦板潃
+ url: http://localhost:9090/admin
+ instance:
+ service-host-type: IP
+ metadata:
+ username: ${spring.boot.admin.client.username}
+ userpassword: ${spring.boot.admin.client.password}
+ username: @monitor.username@
+ password: @monitor.password@
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/banner.txt b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/banner.txt
new file mode 100755
index 0000000..5d24152
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/banner.txt
@@ -0,0 +1,8 @@
+Application Version: ${revision}
+Spring Boot Version: ${spring-boot.version}
+ __ __ _ _ _ _
+| \/ | (_) | /\ | | (_)
+| \ / | ___ _ __ _| |_ ___ _ __ ______ / \ __| |_ __ ___ _ _ __
+| |\/| |/ _ \| '_ \| | __/ _ \| '__|______/ /\ \ / _` | '_ ` _ \| | '_ \
+| | | | (_) | | | | | || (_) | | / ____ \ (_| | | | | | | | | | |
+|_| |_|\___/|_| |_|_|\__\___/|_| /_/ \_\__,_|_| |_| |_|_|_| |_|
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/logback-plus.xml b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/logback-plus.xml
new file mode 100755
index 0000000..45cbbba
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/logback-plus.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration debug="false" scan="true" scanPeriod="1 seconds">
+
+ <contextName>logback</contextName>
+ <property name="log.path" value="./logs/ruoyi-monitor-admin"/>
+ <property name="console.log.pattern"
+ value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
+ <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
+
+ <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>${console.log.pattern}</pattern>
+ <charset>utf-8</charset>
+ </encoder>
+ </appender>
+
+ <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${log.path}.log</file>
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <fileNamePattern>${log.path}.%d{yyyy-MM-dd}.log</fileNamePattern>
+ <!-- 鏃ュ織鏈�澶х殑鍘嗗彶 60澶� -->
+ <maxHistory>60</maxHistory>
+ </rollingPolicy>
+ <encoder>
+ <pattern>${log.pattern}</pattern>
+ </encoder>
+ </appender>
+
+ <root level="info">
+ <appender-ref ref="console"/>
+ <appender-ref ref="file"/>
+ </root>
+
+</configuration>
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/.flattened-pom.xml
new file mode 100644
index 0000000..86240f5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/.flattened-pom.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-extend</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-snailjob-server</artifactId>
+ <version>5.5.3</version>
+ <dependencies>
+ <dependency>
+ <groupId>com.aizuda</groupId>
+ <artifactId>snail-job-server-starter</artifactId>
+ <version>${snailjob.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-library</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-library</artifactId>
+ <version>2.13.9</version>
+ </dependency>
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-client</artifactId>
+ <version>${spring-boot-admin.version}</version>
+ </dependency>
+ </dependencies>
+ <build>
+ <finalName>${project.artifactId}</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring-boot.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/Dockerfile b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/Dockerfile
new file mode 100755
index 0000000..e10ca18
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/Dockerfile
@@ -0,0 +1,23 @@
+# 璐濆皵瀹為獙瀹� Spring 瀹樻柟鎺ㄨ崘闀滃儚 JDK涓嬭浇鍦板潃 https://bell-sw.com/pages/downloads/
+FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
+#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
+#FROM findepi/graalvm:java17-native
+
+LABEL maintainer="Lion Li"
+
+RUN mkdir -p /ruoyi/snailjob/logs
+
+WORKDIR /ruoyi/snailjob
+
+ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
+
+EXPOSE 8800
+EXPOSE 17888
+
+ADD ./target/ruoyi-snailjob-server.jar ./app.jar
+
+SHELL ["/bin/bash", "-c"]
+
+ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom \
+ -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
+ -jar app.jar
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/pom.xml b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/pom.xml
new file mode 100755
index 0000000..0b3afd3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-extend</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>jar</packaging>
+ <artifactId>ruoyi-snailjob-server</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.aizuda</groupId>
+ <artifactId>snail-job-server-starter</artifactId>
+ <version>${snailjob.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-library</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-library</artifactId>
+ <version>2.13.9</version>
+ </dependency>
+
+ <dependency>
+ <groupId>de.codecentric</groupId>
+ <artifactId>spring-boot-admin-starter-client</artifactId>
+ <version>${spring-boot-admin.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>${project.artifactId}</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring-boot.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/ActuatorAuthFilter.java b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/ActuatorAuthFilter.java
new file mode 100755
index 0000000..00f6eee
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/ActuatorAuthFilter.java
@@ -0,0 +1,64 @@
+package com.aizuda.snailjob.server.starter.filter;
+
+import jakarta.servlet.*;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+public class ActuatorAuthFilter implements Filter {
+
+ private final String username;
+ private final String password;
+
+ public ActuatorAuthFilter(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ HttpServletResponse response = (HttpServletResponse) servletResponse;
+
+ // 鑾峰彇 Authorization 澶�
+ String authHeader = request.getHeader("Authorization");
+
+ if (authHeader == null || !authHeader.startsWith("Basic ")) {
+ // 濡傛灉娌℃湁鎻愪緵 Authorization 鎴栬�呮牸寮忎笉瀵癸紝鍒欒繑鍥� 401
+ response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
+ return;
+ }
+
+ // 瑙g爜 Base64 缂栫爜鐨勭敤鎴峰悕鍜屽瘑鐮�
+ String base64Credentials = authHeader.substring("Basic ".length());
+ byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
+ String credentials = new String(credDecoded, StandardCharsets.UTF_8);
+ String[] split = credentials.split(":");
+ if (split.length != 2) {
+ response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
+ return;
+ }
+ // 楠岃瘉鐢ㄦ埛鍚嶅拰瀵嗙爜
+ if (!username.equals(split[0]) || !password.equals(split[1])) {
+ response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
+ return;
+ }
+ // 濡傛灉璁よ瘉鎴愬姛锛岀户缁鐞嗚姹�
+ filterChain.doFilter(request, response);
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) {
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/SecurityConfig.java b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/SecurityConfig.java
new file mode 100755
index 0000000..5196c77
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/starter/filter/SecurityConfig.java
@@ -0,0 +1,29 @@
+package com.aizuda.snailjob.server.starter.filter;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 鏉冮檺瀹夊叏閰嶇疆
+ *
+ * @author Lion Li
+ */
+@Configuration
+public class SecurityConfig {
+
+ @Value("${spring.boot.admin.client.username}")
+ private String username;
+ @Value("${spring.boot.admin.client.password}")
+ private String password;
+
+ @Bean
+ public FilterRegistrationBean<ActuatorAuthFilter> actuatorFilterRegistrationBean() {
+ FilterRegistrationBean<ActuatorAuthFilter> registrationBean = new FilterRegistrationBean<>();
+ registrationBean.setFilter(new ActuatorAuthFilter(username, password));
+ registrationBean.addUrlPatterns("/actuator", "/actuator/*");
+ return registrationBean;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/java/org/dromara/snailjob/SnailJobServerApplication.java b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/java/org/dromara/snailjob/SnailJobServerApplication.java
new file mode 100755
index 0000000..dfab068
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/java/org/dromara/snailjob/SnailJobServerApplication.java
@@ -0,0 +1,19 @@
+package org.dromara.snailjob;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * SnailJob Server 鍚姩绋嬪簭
+ *
+ * @author opensnail
+ * @date 2024-05-17
+ */
+@SpringBootApplication
+public class SnailJobServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(com.aizuda.snailjob.server.SnailJobServerApplication.class, args);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml
new file mode 100755
index 0000000..18a851c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml
@@ -0,0 +1,51 @@
+spring:
+ datasource:
+ type: com.zaxxer.hikari.HikariDataSource
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+ username: root
+ password: root
+ hikari:
+ connection-timeout: 30000
+ validation-timeout: 5000
+ minimum-idle: 10
+ maximum-pool-size: 20
+ idle-timeout: 600000
+ max-lifetime: 900000
+ keepaliveTime: 30000
+
+--- # snail-job 鏈嶅姟绔厤缃�
+snail-job:
+ # 鏈嶅姟绔妭鐐笽P(榛樿鎸夌収`NetUtil.getLocalIpStr()`)
+ server-host:
+ # 鏈嶅姟绔鍙e彿
+ server-port: 17888
+ # 鍚堝苟鏃ュ織榛樿淇濆瓨澶╂暟
+ merge-Log-days: 1
+ # 鍚堝苟鏃ュ織榛樿鐨勬潯鏁�
+ merge-Log-num: 500
+ # 閰嶇疆姣忔壒娆℃媺鍙栭噸璇曟暟鎹殑澶у皬
+ retry-pull-page-size: 100
+ # 閰嶇疆鏃ュ織淇濆瓨鏃堕棿锛堝崟浣嶏細澶╋級
+ log-storage: 7
+ # bucket鐨勬�绘暟閲�
+ bucket-total: 128
+ # Dashboard 浠诲姟瀹归敊澶╂暟
+ summary-day: 7
+ # 閰嶇疆璐熻浇鍧囪 鍛ㄦ湡鏃堕棿
+ load-balance-cycle-time: 10
+ # 閲嶈瘯浠诲姟鎷夊彇鐨勫苟琛屽害
+ retry-max-pull-parallel: 2
+
+--- # 鐩戞帶涓績閰嶇疆
+spring.boot.admin.client:
+ # 澧炲姞瀹㈡埛绔紑鍏�
+ enabled: true
+ url: http://localhost:9090/admin
+ instance:
+ service-host-type: IP
+ metadata:
+ username: ${spring.boot.admin.client.username}
+ userpassword: ${spring.boot.admin.client.password}
+ username: @monitor.username@
+ password: @monitor.password@
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml
new file mode 100755
index 0000000..18a851c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml
@@ -0,0 +1,51 @@
+spring:
+ datasource:
+ type: com.zaxxer.hikari.HikariDataSource
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+ username: root
+ password: root
+ hikari:
+ connection-timeout: 30000
+ validation-timeout: 5000
+ minimum-idle: 10
+ maximum-pool-size: 20
+ idle-timeout: 600000
+ max-lifetime: 900000
+ keepaliveTime: 30000
+
+--- # snail-job 鏈嶅姟绔厤缃�
+snail-job:
+ # 鏈嶅姟绔妭鐐笽P(榛樿鎸夌収`NetUtil.getLocalIpStr()`)
+ server-host:
+ # 鏈嶅姟绔鍙e彿
+ server-port: 17888
+ # 鍚堝苟鏃ュ織榛樿淇濆瓨澶╂暟
+ merge-Log-days: 1
+ # 鍚堝苟鏃ュ織榛樿鐨勬潯鏁�
+ merge-Log-num: 500
+ # 閰嶇疆姣忔壒娆℃媺鍙栭噸璇曟暟鎹殑澶у皬
+ retry-pull-page-size: 100
+ # 閰嶇疆鏃ュ織淇濆瓨鏃堕棿锛堝崟浣嶏細澶╋級
+ log-storage: 7
+ # bucket鐨勬�绘暟閲�
+ bucket-total: 128
+ # Dashboard 浠诲姟瀹归敊澶╂暟
+ summary-day: 7
+ # 閰嶇疆璐熻浇鍧囪 鍛ㄦ湡鏃堕棿
+ load-balance-cycle-time: 10
+ # 閲嶈瘯浠诲姟鎷夊彇鐨勫苟琛屽害
+ retry-max-pull-parallel: 2
+
+--- # 鐩戞帶涓績閰嶇疆
+spring.boot.admin.client:
+ # 澧炲姞瀹㈡埛绔紑鍏�
+ enabled: true
+ url: http://localhost:9090/admin
+ instance:
+ service-host-type: IP
+ metadata:
+ username: ${spring.boot.admin.client.username}
+ userpassword: ${spring.boot.admin.client.password}
+ username: @monitor.username@
+ password: @monitor.password@
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application.yml b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application.yml
new file mode 100755
index 0000000..ba6df45
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application.yml
@@ -0,0 +1,39 @@
+server:
+ port: 8800
+ servlet:
+ context-path: /snail-job
+
+spring:
+ application:
+ name: ruoyi-snailjob-server
+ profiles:
+ active: @profiles.active@
+ web:
+ resources:
+ static-locations: classpath:admin/
+
+mybatis-plus:
+ typeAliasesPackage: com.aizuda.snailjob.template.datasource.persistence.po
+ global-config:
+ db-config:
+ where-strategy: NOT_EMPTY
+ capital-mode: false
+ logic-delete-value: 1
+ logic-not-delete-value: 0
+ configuration:
+ map-underscore-to-camel-case: true
+ cache-enabled: true
+
+logging:
+ config: classpath:logback-plus.xml
+
+management:
+ endpoints:
+ web:
+ exposure:
+ include: '*'
+ endpoint:
+ health:
+ show-details: ALWAYS
+ logfile:
+ external-file: ./logs/ruoyi-snailjob-server/console.log
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/banner.txt b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/banner.txt
new file mode 100755
index 0000000..adb96c5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/banner.txt
@@ -0,0 +1,11 @@
+Application Version: ${revision}
+Spring Boot Version: ${spring-boot.version}
+ _ _ _ _
+ (_) (_) | |
+ ___ _ __ __ _ _| |_ ___ | |__ ______ ___ ___ _ ____ _____ _ __
+/ __| '_ \ / _` | | | |/ _ \| '_ \______/ __|/ _ \ '__\ \ / / _ \ '__|
+\__ \ | | | (_| | | | | (_) | |_) | \__ \ __/ | \ V / __/ |
+|___/_| |_|\__,_|_|_| |\___/|_.__/ |___/\___|_| \_/ \___|_|
+ _/ |
+ |__/
+
diff --git a/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/logback-plus.xml b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/logback-plus.xml
new file mode 100755
index 0000000..ed733cb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/logback-plus.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+ <property name="log.path" value="./logs/ruoyi-snailjob-server" />
+ <property name="console.log.pattern"
+ value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
+ <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
+
+
+ <!-- 鎺у埗鍙拌緭鍑� -->
+ <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>${console.log.pattern}</pattern>
+ <charset>utf-8</charset>
+ </encoder>
+ </appender>
+
+ <!-- 鎺у埗鍙拌緭鍑� -->
+ <appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${log.path}/console.log</file>
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <!-- 鏃ュ織鏂囦欢鍚嶆牸寮� -->
+ <fileNamePattern>${log.path}/console.%d{yyyy-MM-dd}.log</fileNamePattern>
+ <!-- 鏃ュ織鏈�澶� 1澶� -->
+ <maxHistory>1</maxHistory>
+ </rollingPolicy>
+ <encoder>
+ <pattern>${log.pattern}</pattern>
+ <charset>utf-8</charset>
+ </encoder>
+ <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+ <!-- 杩囨护鐨勭骇鍒� -->
+ <level>INFO</level>
+ </filter>
+ </appender>
+
+ <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${log.path}/info.log</file>
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <FileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</FileNamePattern>
+ <MaxHistory>60</MaxHistory>
+ </rollingPolicy>
+ <encoder>
+ <pattern>${log.pattern}</pattern>
+ </encoder>
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>INFO</level>
+ <onMatch>ACCEPT</onMatch>
+ <onMismatch>DENY</onMismatch>
+ </filter>
+ </appender>
+
+ <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${log.path}/error.log</file>
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <FileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log
+ </FileNamePattern>
+ <MaxHistory>60</MaxHistory>
+ </rollingPolicy>
+ <encoder>
+ <pattern>${log.pattern}</pattern>
+ </encoder>
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>ERROR</level>
+ <onMatch>ACCEPT</onMatch>
+ <onMismatch>DENY</onMismatch>
+ </filter>
+ </appender>
+
+ <appender name ="async_info" class= "ch.qos.logback.classic.AsyncAppender">
+ <discardingThreshold >100</discardingThreshold>
+ <queueSize>1024</queueSize>
+ <appender-ref ref ="file_info"/>
+ </appender>
+
+ <appender name ="async_error" class= "ch.qos.logback.classic.AsyncAppender">
+ <discardingThreshold >100</discardingThreshold>
+ <queueSize>1024</queueSize>
+ <appender-ref ref ="file_error"/>
+ </appender>
+
+ <!-- SnailJob appender -->
+ <appender name="snail_log_server_appender" class="com.aizuda.snailjob.server.common.appender.SnailJobServerLogbackAppender">
+ </appender>
+
+ <!-- 鎺у埗鍙拌緭鍑烘棩蹇楃骇鍒� -->
+ <root level="info">
+ <appender-ref ref="console" />
+ <appender-ref ref="file_console" />
+ <appender-ref ref="async_info" />
+ <appender-ref ref="async_error" />
+ <appender-ref ref="snail_log_server_appender" />
+ </root>
+</configuration>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/.DS_Store b/RuoYi-Vue-Plus/ruoyi-modules/.DS_Store
new file mode 100755
index 0000000..662039c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/.DS_Store
Binary files differ
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/.flattened-pom.xml
new file mode 100644
index 0000000..01e849a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/.flattened-pom.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-vue-plus</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-modules</artifactId>
+ <version>5.5.3</version>
+ <packaging>pom</packaging>
+ <description>ruoyi-modules 涓氬姟妯″潡</description>
+ <modules>
+ <module>ruoyi-demo</module>
+ <module>ruoyi-qa</module>
+ <module>ruoyi-generator</module>
+ <module>ruoyi-job</module>
+ <module>ruoyi-system</module>
+ <module>ruoyi-workflow</module>
+ </modules>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/pom.xml
new file mode 100755
index 0000000..4d2a73b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/pom.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>ruoyi-vue-plus</artifactId>
+ <groupId>org.dromara</groupId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <modules>
+ <module>ruoyi-demo</module>
+ <module>ruoyi-qa</module>
+ <module>ruoyi-generator</module>
+ <module>ruoyi-job</module>
+ <module>ruoyi-system</module>
+ <module>ruoyi-workflow</module>
+ </modules>
+
+ <artifactId>ruoyi-modules</artifactId>
+ <packaging>pom</packaging>
+
+ <description>
+ ruoyi-modules 涓氬姟妯″潡
+ </description>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/.flattened-pom.xml
new file mode 100644
index 0000000..b7b84bc
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/.flattened-pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-modules</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-demo</artifactId>
+ <version>5.5.3</version>
+ <description>demo妯″潡</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sms</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mail</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-idempotent</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-log</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-excel</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-security</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-ratelimiter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-translation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sensitive</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-encrypt</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-tenant</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-websocket</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/pom.xml
new file mode 100755
index 0000000..119fe61
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/pom.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-modules</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-demo</artifactId>
+
+ <description>
+ demo妯″潡
+ </description>
+
+ <dependencies>
+
+ <!-- 閫氱敤宸ュ叿-->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sms</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mail</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-idempotent</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-log</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-excel</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-security</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-web</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-ratelimiter</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-translation</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sensitive</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-encrypt</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-tenant</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-websocket</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailSendController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailSendController.java
new file mode 100755
index 0000000..48365a3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailSendController.java
@@ -0,0 +1,69 @@
+package org.dromara.demo.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mail.utils.MailUtils;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+/**
+ * 閭欢鍙戦�佹渚�
+ *
+ * @author Michelle.Chung
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/mail")
+public class MailSendController {
+
+ /**
+ * 鍙戦�侀偖浠�
+ *
+ * @param to 鎺ユ敹浜�
+ * @param subject 鏍囬
+ * @param text 鍐呭
+ */
+ @GetMapping("/sendSimpleMessage")
+ public R<Void> sendSimpleMessage(String to, String subject, String text) {
+ MailUtils.sendText(to, subject, text);
+ return R.ok();
+ }
+
+ /**
+ * 鍙戦�侀偖浠讹紙甯﹂檮浠讹級
+ *
+ * @param to 鎺ユ敹浜�
+ * @param subject 鏍囬
+ * @param text 鍐呭
+ */
+ @GetMapping("/sendMessageWithAttachment")
+ public R<Void> sendMessageWithAttachment(String to, String subject, String text) {
+ // 闄勪欢璺緞 绂佹鍓嶇浼犻�� 鏈変换鎰忚鍙栫郴缁熸枃浠堕闄�
+ MailUtils.sendText(to, subject, text, new File("/xxx/xxx"));
+ return R.ok();
+ }
+
+ /**
+ * 鍙戦�侀偖浠讹紙澶氶檮浠讹級
+ *
+ * @param to 鎺ユ敹浜�
+ * @param subject 鏍囬
+ * @param text 鍐呭
+ */
+ @GetMapping("/sendMessageWithAttachments")
+ public R<Void> sendMessageWithAttachments(String to, String subject, String text) {
+ // 闄勪欢璺緞 绂佹鍓嶇浼犻�� 鏈変换鎰忚鍙栫郴缁熸枃浠堕闄�
+ String[] paths = new String[]{"/xxx/xxx", "/xxx/xxx"};
+ File[] array = Arrays.stream(paths).map(File::new).toArray(File[]::new);
+ MailUtils.sendText(to, subject, text, array);
+ return R.ok();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisCacheController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisCacheController.java
new file mode 100755
index 0000000..2335da4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisCacheController.java
@@ -0,0 +1,92 @@
+package org.dromara.demo.controller;
+
+import cn.hutool.core.thread.ThreadUtil;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.redis.utils.RedisUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.Duration;
+
+/**
+ * spring-cache 婕旂ず妗堜緥
+ *
+ * @author Lion Li
+ */
+// 绫荤骇鍒� 缂撳瓨缁熶竴閰嶇疆
+//@CacheConfig(cacheNames = CacheNames.DEMO_CACHE)
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/cache")
+public class RedisCacheController {
+
+ /**
+ * 娴嬭瘯 @Cacheable
+ * <p>
+ * 琛ㄧず杩欎釜鏂规硶鏈変簡缂撳瓨鐨勫姛鑳�,鏂规硶鐨勮繑鍥炲�间細琚紦瀛樹笅鏉�
+ * 涓嬩竴娆¤皟鐢ㄨ鏂规硶鍓�,浼氬幓妫�鏌ユ槸鍚︾紦瀛樹腑宸茬粡鏈夊��
+ * 濡傛灉鏈夊氨鐩存帴杩斿洖,涓嶈皟鐢ㄦ柟娉�
+ * 濡傛灉娌℃湁,灏辫皟鐢ㄦ柟娉�,鐒跺悗鎶婄粨鏋滅紦瀛樿捣鏉�
+ * 杩欎釜娉ㄨВ銆屼竴鑸敤鍦ㄦ煡璇㈡柟娉曚笂銆�
+ * <p>
+ * 閲嶇偣璇存槑: 缂撳瓨娉ㄨВ涓ヨ皑涓庡叾浠栫瓫閫夋暟鎹姛鑳戒竴璧蜂娇鐢�
+ * 渚嬪: 鏁版嵁鏉冮檺娉ㄨВ 浼氶�犳垚 缂撳瓨鍑荤┛ 涓� 鏁版嵁涓嶄竴鑷撮棶棰�
+ * <p>
+ * cacheNames 鍛藉悕瑙勫垯 鏌ョ湅 {@link CacheNames} 娉ㄩ噴 鏀寔澶氬弬鏁�
+ */
+ @Cacheable(cacheNames = "demo:cache#60s#10m#20#1", key = "#key", condition = "#key != null")
+ @GetMapping("/test1")
+ public R<String> test1(String key, String value) {
+ return R.ok("鎿嶄綔鎴愬姛", value);
+ }
+
+ /**
+ * 娴嬭瘯 @CachePut
+ * <p>
+ * 鍔犱簡@CachePut娉ㄨВ鐨勬柟娉�,浼氭妸鏂规硶鐨勮繑鍥炲�紁ut鍒扮紦瀛橀噷闈㈢紦瀛樿捣鏉�,渚涘叾瀹冨湴鏂逛娇鐢�
+ * 瀹冦�岄�氬父鐢ㄥ湪鏂板鎴栬�呭疄鏃舵洿鏂版柟娉曚笂銆�
+ * <p>
+ * cacheNames 鍛藉悕瑙勫垯 鏌ョ湅 {@link CacheNames} 娉ㄩ噴 鏀寔澶氬弬鏁�
+ */
+ @CachePut(cacheNames = CacheNames.DEMO_CACHE, key = "#key", condition = "#key != null")
+ @GetMapping("/test2")
+ public R<String> test2(String key, String value) {
+ return R.ok("鎿嶄綔鎴愬姛", value);
+ }
+
+ /**
+ * 娴嬭瘯 @CacheEvict
+ * <p>
+ * 浣跨敤浜咰acheEvict娉ㄨВ鐨勬柟娉�,浼氭竻绌烘寚瀹氱紦瀛�
+ * 銆屼竴鑸敤鍦ㄥ垹闄ょ殑鏂规硶涓娿��
+ * <p>
+ * cacheNames 鍛藉悕瑙勫垯 鏌ョ湅 {@link CacheNames} 娉ㄩ噴 鏀寔澶氬弬鏁�
+ */
+ @CacheEvict(cacheNames = CacheNames.DEMO_CACHE, key = "#key", condition = "#key != null")
+ @GetMapping("/test3")
+ public R<String> test3(String key, String value) {
+ return R.ok("鎿嶄綔鎴愬姛", value);
+ }
+
+ /**
+ * 娴嬭瘯璁剧疆杩囨湡鏃堕棿
+ * 鎵嬪姩璁剧疆杩囨湡鏃堕棿10绉�
+ * 11绉掑悗鑾峰彇 鍒ゆ柇鏄惁鐩哥瓑
+ */
+ @GetMapping("/test6")
+ public R<Boolean> test6(String key, String value) {
+ RedisUtils.setCacheObject(key, value);
+ boolean flag = RedisUtils.expire(key, Duration.ofSeconds(10));
+ System.out.println("***********" + flag);
+ ThreadUtil.sleep(11 * 1000);
+ Object obj = RedisUtils.getCacheObject(key);
+ return R.ok(value.equals(obj));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisLockController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisLockController.java
new file mode 100755
index 0000000..237b6ee
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisLockController.java
@@ -0,0 +1,64 @@
+package org.dromara.demo.controller;
+
+import cn.hutool.core.thread.ThreadUtil;
+import com.baomidou.lock.LockInfo;
+import com.baomidou.lock.LockTemplate;
+import com.baomidou.lock.annotation.Lock4j;
+import com.baomidou.lock.executor.RedissonLockExecutor;
+import org.dromara.common.core.domain.R;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalTime;
+
+
+/**
+ * 娴嬭瘯鍒嗗竷寮忛攣鐨勬牱渚�
+ *
+ * @author shenxinquan
+ */
+@Slf4j
+@RestController
+@RequestMapping("/demo/redisLock")
+public class RedisLockController {
+
+ @Autowired
+ private LockTemplate lockTemplate;
+
+ /**
+ * 娴嬭瘯lock4j 娉ㄨВ
+ */
+ @Lock4j(keys = {"#key"})
+ @GetMapping("/testLock4j")
+ public R<String> testLock4j(String key, String value) {
+ System.out.println("start:" + key + ",time:" + LocalTime.now());
+ ThreadUtil.sleep(10000);
+ System.out.println("end :" + key + ",time:" + LocalTime.now());
+ return R.ok("鎿嶄綔鎴愬姛", value);
+ }
+
+ /**
+ * 娴嬭瘯lock4j 宸ュ叿
+ */
+ @GetMapping("/testLock4jLockTemplate")
+ public R<String> testLock4jLockTemplate(String key, String value) {
+ final LockInfo lockInfo = lockTemplate.lock(key, 30000L, 5000L, RedissonLockExecutor.class);
+ if (null == lockInfo) {
+ throw new RuntimeException("涓氬姟澶勭悊涓�,璇风◢鍚庡啀璇�");
+ }
+ // 鑾峰彇閿佹垚鍔燂紝澶勭悊涓氬姟
+ try {
+ ThreadUtil.sleep(8000);
+ System.out.println("鎵ц绠�鍗曟柟娉�1 , 褰撳墠绾跨▼:" + Thread.currentThread().getName());
+ } finally {
+ //閲婃斁閿�
+ lockTemplate.releaseLock(lockInfo);
+ }
+ //缁撴潫
+ return R.ok("鎿嶄綔鎴愬姛", value);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisPubSubController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisPubSubController.java
new file mode 100755
index 0000000..bdbf033
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisPubSubController.java
@@ -0,0 +1,47 @@
+package org.dromara.demo.controller;
+
+import org.dromara.common.core.domain.R;
+import org.dromara.common.redis.utils.RedisUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Redis 鍙戝竷璁㈤槄 婕旂ず妗堜緥
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/redis/pubsub")
+public class RedisPubSubController {
+
+ /**
+ * 鍙戝竷娑堟伅
+ *
+ * @param key 閫氶亾Key
+ * @param value 鍙戦�佸唴瀹�
+ */
+ @GetMapping("/pub")
+ public R<Void> pub(String key, String value) {
+ RedisUtils.publish(key, value, consumer -> {
+ System.out.println("鍙戝竷閫氶亾 => " + key + ", 鍙戦�佸�� => " + value);
+ });
+ return R.ok("鎿嶄綔鎴愬姛");
+ }
+
+ /**
+ * 璁㈤槄娑堟伅
+ *
+ * @param key 閫氶亾Key
+ */
+ @GetMapping("/sub")
+ public R<Void> sub(String key) {
+ RedisUtils.subscribe(key, String.class, msg -> {
+ System.out.println("璁㈤槄閫氶亾 => " + key + ", 鎺ユ敹鍊� => " + msg);
+ });
+ return R.ok("鎿嶄綔鎴愬姛");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisRateLimiterController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisRateLimiterController.java
new file mode 100755
index 0000000..f8adf7d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisRateLimiterController.java
@@ -0,0 +1,64 @@
+package org.dromara.demo.controller;
+
+import org.dromara.common.core.domain.R;
+import org.dromara.common.ratelimiter.annotation.RateLimiter;
+import org.dromara.common.ratelimiter.enums.LimitType;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+
+/**
+ * 娴嬭瘯鍒嗗竷寮忛檺娴佹牱渚�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RestController
+@RequestMapping("/demo/rateLimiter")
+public class RedisRateLimiterController {
+
+ /**
+ * 娴嬭瘯鍏ㄥ眬闄愭祦
+ * 鍏ㄥ眬褰卞搷
+ */
+ @RateLimiter(count = 2, time = 10)
+ @GetMapping("/test")
+ public R<String> test(String value) {
+ return R.ok("鎿嶄綔鎴愬姛", value);
+ }
+
+ /**
+ * 娴嬭瘯璇锋眰IP闄愭祦
+ * 鍚屼竴IP璇锋眰鍙楀奖鍝�
+ */
+ @RateLimiter(count = 2, time = 10, limitType = LimitType.IP)
+ @GetMapping("/testip")
+ public R<String> testip(String value) {
+ return R.ok("鎿嶄綔鎴愬姛", value);
+ }
+
+ /**
+ * 娴嬭瘯闆嗙兢瀹炰緥闄愭祦
+ * 鍚姩涓や釜鍚庣鏈嶅姟浜掍笉褰卞搷
+ */
+ @RateLimiter(count = 2, time = 10, limitType = LimitType.CLUSTER)
+ @GetMapping("/testcluster")
+ public R<String> testcluster(String value) {
+ return R.ok("鎿嶄綔鎴愬姛", value);
+ }
+
+ /**
+ * 娴嬭瘯璇锋眰IP闄愭祦(key鍩轰簬鍙傛暟鑾峰彇)
+ * 鍚屼竴IP璇锋眰鍙楀奖鍝�
+ *
+ * 绠�鍗曞彉閲忚幏鍙� #鍙橀噺 澶嶆潅琛ㄨ揪寮� #{#鍙橀噺 != 1 ? 1 : 0}
+ */
+ @RateLimiter(count = 2, time = 10, limitType = LimitType.IP, key = "#value")
+ @GetMapping("/testObj")
+ public R<String> testObj(String value) {
+ return R.ok("鎿嶄綔鎴愬姛", value);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/SmsController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/SmsController.java
new file mode 100755
index 0000000..b993f60
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/SmsController.java
@@ -0,0 +1,82 @@
+package org.dromara.demo.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.sms4j.api.SmsBlend;
+import org.dromara.sms4j.api.entity.SmsResponse;
+import org.dromara.sms4j.core.factory.SmsFactory;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.LinkedHashMap;
+
+/**
+ * 鐭俊婕旂ず妗堜緥
+ * 璇峰厛闃呰鏂囨。 鍚﹀垯鏃犳硶浣跨敤
+ *
+ * @author Lion Li
+ * @version 4.2.0
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/sms")
+public class SmsController {
+ /**
+ * 鍙戦�佺煭淇liyun
+ *
+ * @param phones 鐢佃瘽鍙�
+ * @param templateId 妯℃澘ID
+ */
+ @GetMapping("/sendAliyun")
+ public R<Object> sendAliyun(String phones, String templateId) {
+ LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
+ map.put("code", "1234");
+ SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
+ SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
+ return R.ok(smsResponse);
+ }
+
+ /**
+ * 鍙戦�佺煭淇encent
+ *
+ * @param phones 鐢佃瘽鍙�
+ * @param templateId 妯℃澘ID
+ */
+ @GetMapping("/sendTencent")
+ public R<Object> sendTencent(String phones, String templateId) {
+ LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
+// map.put("2", "娴嬭瘯娴嬭瘯");
+ map.put("1", "1234");
+ SmsBlend smsBlend = SmsFactory.getSmsBlend("config2");
+ SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
+ return R.ok(smsResponse);
+ }
+
+ /**
+ * 娣诲姞榛戝悕鍗�
+ *
+ * @param phone 鎵嬫満鍙�
+ */
+ @GetMapping("/addBlacklist")
+ public R<Object> addBlacklist(String phone){
+ SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
+ smsBlend.joinInBlacklist(phone);
+ return R.ok();
+ }
+
+ /**
+ * 绉婚櫎榛戝悕鍗�
+ *
+ * @param phone 鎵嬫満鍙�
+ */
+ @GetMapping("/removeBlacklist")
+ public R<Object> removeBlacklist(String phone){
+ SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
+ smsBlend.removeFromBlacklist(phone);
+ return R.ok();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/Swagger3DemoController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/Swagger3DemoController.java
new file mode 100755
index 0000000..bb02f98
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/Swagger3DemoController.java
@@ -0,0 +1,31 @@
+package org.dromara.demo.controller;
+
+import org.dromara.common.core.domain.R;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * swagger3 鐢ㄦ硶绀轰緥
+ *
+ * @author Lion Li
+ */
+@RestController
+@RequestMapping("/swagger/demo")
+public class Swagger3DemoController {
+
+ /**
+ * 涓婁紶璇锋眰
+ * 蹇呴』浣跨敤 @RequestPart 娉ㄨВ鏍囨敞涓烘枃浠�
+ *
+ * @param file 鏂囦欢
+ */
+ @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public R<String> upload(@RequestPart("file") MultipartFile file) {
+ return R.ok("鎿嶄綔鎴愬姛", file.getOriginalFilename());
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestBatchController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestBatchController.java
new file mode 100755
index 0000000..af8c77b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestBatchController.java
@@ -0,0 +1,90 @@
+package org.dromara.demo.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.demo.domain.TestDemo;
+import org.dromara.demo.mapper.TestDemoMapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 娴嬭瘯鎵归噺鏂规硶
+ *
+ * @author Lion Li
+ * @date 2021-05-30
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/batch")
+public class TestBatchController extends BaseController {
+
+ /**
+ * 涓轰簡渚夸簬娴嬭瘯 鐩存帴寮曞叆mapper
+ */
+ private final TestDemoMapper testDemoMapper;
+
+ /**
+ * 鏂板鎵归噺鏂规硶 鍙畬缇庢浛浠� saveBatch 绉掔骇鎻掑叆涓婁竾鏁版嵁 (瀵筸ysql璐熻嵎杈冨ぇ)
+ * <p>
+ * 3.5.0 鐗堟湰 澧炲姞 rewriteBatchedStatements=true 鎵瑰鐞嗗弬鏁� 浣� MP 鍘熺敓鎵瑰鐞嗗彲浠ヨ揪鍒板悓鏍风殑閫熷害
+ */
+ @PostMapping("/add")
+// @DS("slave")
+ public R<Void> add() {
+ List<TestDemo> list = new ArrayList<>();
+ for (int i = 0; i < 1000; i++) {
+ TestDemo testDemo = new TestDemo();
+ testDemo.setOrderNum(-1);
+ testDemo.setTestKey("鎵归噺鏂板");
+ testDemo.setValue("娴嬭瘯鏂板");
+ list.add(testDemo);
+ }
+ return toAjax(testDemoMapper.insertBatch(list));
+ }
+
+ /**
+ * 鏂板鎴栨洿鏂� 鍙畬缇庢浛浠� saveOrUpdateBatch 楂樻�ц兘
+ * <p>
+ * 3.5.0 鐗堟湰 澧炲姞 rewriteBatchedStatements=true 鎵瑰鐞嗗弬鏁� 浣� MP 鍘熺敓鎵瑰鐞嗗彲浠ヨ揪鍒板悓鏍风殑閫熷害
+ */
+ @PostMapping("/addOrUpdate")
+// @DS("slave")
+ public R<Void> addOrUpdate() {
+ List<TestDemo> list = new ArrayList<>();
+ for (int i = 0; i < 1000; i++) {
+ TestDemo testDemo = new TestDemo();
+ testDemo.setOrderNum(-1);
+ testDemo.setTestKey("鎵归噺鏂板");
+ testDemo.setValue("娴嬭瘯鏂板");
+ list.add(testDemo);
+ }
+ testDemoMapper.insertBatch(list);
+ for (int i = 0; i < list.size(); i++) {
+ TestDemo testDemo = list.get(i);
+ testDemo.setTestKey("鎵归噺鏂板鎴栦慨鏀�");
+ testDemo.setValue("鎵归噺鏂板鎴栦慨鏀�");
+ if (i % 2 == 0) {
+ testDemo.setId(null);
+ }
+ }
+ return toAjax(testDemoMapper.insertOrUpdateBatch(list));
+ }
+
+ /**
+ * 鍒犻櫎鎵归噺鏂规硶
+ */
+ @DeleteMapping()
+// @DS("slave")
+ public R<Void> remove() {
+ return toAjax(testDemoMapper.delete(new LambdaQueryWrapper<TestDemo>()
+ .eq(TestDemo::getOrderNum, -1L)));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestDemoController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestDemoController.java
new file mode 100755
index 0000000..f31c540
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestDemoController.java
@@ -0,0 +1,147 @@
+package org.dromara.demo.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.core.validate.QueryGroup;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.excel.core.ExcelResult;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.demo.domain.TestDemo;
+import org.dromara.demo.domain.bo.TestDemoBo;
+import org.dromara.demo.domain.bo.TestDemoImportVo;
+import org.dromara.demo.domain.vo.TestDemoVo;
+import org.dromara.demo.service.ITestDemoService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 娴嬭瘯鍗曡〃Controller
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/demo")
+public class TestDemoController extends BaseController {
+
+ private final ITestDemoService testDemoService;
+
+ /**
+ * 鏌ヨ娴嬭瘯鍗曡〃鍒楄〃
+ */
+ @SaCheckPermission("demo:demo:list")
+ @GetMapping("/list")
+ public TableDataInfo<TestDemoVo> list(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) {
+ return testDemoService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 鑷畾涔夊垎椤垫煡璇�
+ */
+ @SaCheckPermission("demo:demo:list")
+ @GetMapping("/page")
+ public TableDataInfo<TestDemoVo> page(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) {
+ return testDemoService.customPageList(bo, pageQuery);
+ }
+
+ /**
+ * 瀵煎叆鏁版嵁
+ *
+ * @param file 瀵煎叆鏂囦欢
+ */
+ @Log(title = "娴嬭瘯鍗曡〃", businessType = BusinessType.IMPORT)
+ @SaCheckPermission("demo:demo:import")
+ @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public R<Void> importData(@RequestPart("file") MultipartFile file) throws Exception {
+ ExcelResult<TestDemoImportVo> excelResult = ExcelUtil.importExcel(file.getInputStream(), TestDemoImportVo.class, true);
+ List<TestDemo> list = MapstructUtils.convert(excelResult.getList(), TestDemo.class);
+ testDemoService.saveBatch(list);
+ return R.ok(excelResult.getAnalysis());
+ }
+
+ /**
+ * 瀵煎嚭娴嬭瘯鍗曡〃鍒楄〃
+ */
+ @SaCheckPermission("demo:demo:export")
+ @Log(title = "娴嬭瘯鍗曡〃", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(@Validated TestDemoBo bo, HttpServletResponse response) {
+ List<TestDemoVo> list = testDemoService.queryList(bo);
+ // 娴嬭瘯闆姳id瀵煎嚭
+// for (TestDemoVo vo : list) {
+// vo.setId(1234567891234567893L);
+// }
+ ExcelUtil.exportExcel(list, "娴嬭瘯鍗曡〃", TestDemoVo.class, response);
+ }
+
+ /**
+ * 鑾峰彇娴嬭瘯鍗曡〃璇︾粏淇℃伅
+ *
+ * @param id 娴嬭瘯ID
+ */
+ @SaCheckPermission("demo:demo:query")
+ @GetMapping("/{id}")
+ public R<TestDemoVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable("id") Long id) {
+ return R.ok(testDemoService.queryById(id));
+ }
+
+ /**
+ * 鏂板娴嬭瘯鍗曡〃
+ */
+ @SaCheckPermission("demo:demo:add")
+ @Log(title = "娴嬭瘯鍗曡〃", businessType = BusinessType.INSERT)
+ @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "{repeat.submit.message}")
+ @PostMapping()
+ public R<Void> add(@RequestBody TestDemoBo bo) {
+ // 浣跨敤鏍¢獙宸ュ叿瀵规爣 @Validated(AddGroup.class) 娉ㄨВ
+ // 鐢ㄤ簬鍦ㄩ潪 Controller 鐨勫湴鏂规牎楠屽璞�
+ ValidatorUtils.validate(bo, AddGroup.class);
+ return toAjax(testDemoService.insertByBo(bo));
+ }
+
+ /**
+ * 淇敼娴嬭瘯鍗曡〃
+ */
+ @SaCheckPermission("demo:demo:edit")
+ @Log(title = "娴嬭瘯鍗曡〃", businessType = BusinessType.UPDATE)
+ @RepeatSubmit
+ @PutMapping()
+ public R<Void> edit(@Validated(EditGroup.class) @RequestBody TestDemoBo bo) {
+ return toAjax(testDemoService.updateByBo(bo));
+ }
+
+ /**
+ * 鍒犻櫎娴嬭瘯鍗曡〃
+ *
+ * @param ids 娴嬭瘯ID涓�
+ */
+ @SaCheckPermission("demo:demo:remove")
+ @Log(title = "娴嬭瘯鍗曡〃", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long[] ids) {
+ return toAjax(testDemoService.deleteWithValidByIds(Arrays.asList(ids), true));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestEncryptController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestEncryptController.java
new file mode 100755
index 0000000..2b6886d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestEncryptController.java
@@ -0,0 +1,55 @@
+package org.dromara.demo.controller;
+
+import org.dromara.common.core.domain.R;
+import org.dromara.demo.domain.TestDemoEncrypt;
+import org.dromara.demo.mapper.TestDemoEncryptMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * 娴嬭瘯鏁版嵁搴撳姞瑙e瘑鍔熻兘
+ *
+ * @author Lion Li
+ */
+@Validated
+@RestController
+@RequestMapping("/demo/encrypt")
+public class TestEncryptController {
+
+ @Autowired
+ private TestDemoEncryptMapper mapper;
+ @Value("${mybatis-encryptor.enable}")
+ private Boolean encryptEnable;
+
+ /**
+ * 娴嬭瘯鏁版嵁搴撳姞瑙e瘑
+ *
+ * @param key 娴嬭瘯key
+ * @param value 娴嬭瘯value
+ */
+ @GetMapping()
+ public R<Map<String, TestDemoEncrypt>> test(String key, String value) {
+ if (!encryptEnable) {
+ throw new RuntimeException("鍔犲瘑鍔熻兘鏈紑鍚�!");
+ }
+ Map<String, TestDemoEncrypt> map = new HashMap<>(2);
+ TestDemoEncrypt demo = new TestDemoEncrypt();
+ demo.setTestKey(key);
+ demo.setValue(value);
+ mapper.insert(demo);
+ map.put("鍔犲瘑", demo);
+ TestDemoEncrypt testDemo = mapper.selectById(demo.getId());
+ map.put("瑙e瘑", testDemo);
+ return R.ok(map);
+ }
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java
new file mode 100755
index 0000000..64243b7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java
@@ -0,0 +1,172 @@
+package org.dromara.demo.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.hutool.core.collection.CollUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.excel.core.ExcelResult;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.demo.domain.vo.ExportDemoVo;
+import org.dromara.demo.listener.ExportDemoListener;
+import org.dromara.demo.service.IExportExcelService;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 娴嬭瘯Excel鍔熻兘
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/excel")
+public class TestExcelController {
+
+ private final IExportExcelService exportExcelService;
+
+ /**
+ * 鍗曞垪琛ㄥ鏁版嵁
+ */
+ @GetMapping("/exportTemplateOne")
+ public void exportTemplateOne(HttpServletResponse response) {
+ Map<String, String> map = new HashMap<>();
+ map.put("title", "鍗曞垪琛ㄥ鏁版嵁");
+ map.put("test1", "鏁版嵁娴嬭瘯1");
+ map.put("test2", "鏁版嵁娴嬭瘯2");
+ map.put("test3", "鏁版嵁娴嬭瘯3");
+ map.put("test4", "鏁版嵁娴嬭瘯4");
+ map.put("testTest", "666");
+ List<TestObj> list = new ArrayList<>();
+ list.add(new TestObj("鍗曞垪琛ㄦ祴璇�1", "鍒楄〃娴嬭瘯1", "鍒楄〃娴嬭瘯2", "鍒楄〃娴嬭瘯3", "鍒楄〃娴嬭瘯4"));
+ list.add(new TestObj("鍗曞垪琛ㄦ祴璇�2", "鍒楄〃娴嬭瘯5", "鍒楄〃娴嬭瘯6", "鍒楄〃娴嬭瘯7", "鍒楄〃娴嬭瘯8"));
+ list.add(new TestObj("鍗曞垪琛ㄦ祴璇�3", "鍒楄〃娴嬭瘯9", "鍒楄〃娴嬭瘯10", "鍒楄〃娴嬭瘯11", "鍒楄〃娴嬭瘯12"));
+ ExcelUtil.exportTemplate(CollUtil.newArrayList(map, list), "鍗曞垪琛�.xlsx", "excel/鍗曞垪琛�.xlsx", response);
+ }
+
+ /**
+ * 澶氬垪琛ㄥ鏁版嵁
+ */
+ @GetMapping("/exportTemplateMuliti")
+ public void exportTemplateMuliti(HttpServletResponse response) {
+ Map<String, String> map = new HashMap<>();
+ map.put("title1", "鏍囬1");
+ map.put("title2", "鏍囬2");
+ map.put("title3", "鏍囬3");
+ map.put("title4", "鏍囬4");
+ map.put("author", "Lion Li");
+ List<TestObj1> list1 = new ArrayList<>();
+ list1.add(new TestObj1("list1娴嬭瘯1", "list1娴嬭瘯2", "list1娴嬭瘯3"));
+ list1.add(new TestObj1("list1娴嬭瘯4", "list1娴嬭瘯5", "list1娴嬭瘯6"));
+ list1.add(new TestObj1("list1娴嬭瘯7", "list1娴嬭瘯8", "list1娴嬭瘯9"));
+ List<TestObj1> list2 = new ArrayList<>();
+ list2.add(new TestObj1("list2娴嬭瘯1", "list2娴嬭瘯2", "list2娴嬭瘯3"));
+ list2.add(new TestObj1("list2娴嬭瘯4", "list2娴嬭瘯5", "list2娴嬭瘯6"));
+ List<TestObj1> list3 = new ArrayList<>();
+ list3.add(new TestObj1("list3娴嬭瘯1", "list3娴嬭瘯2", "list3娴嬭瘯3"));
+ List<TestObj1> list4 = new ArrayList<>();
+ list4.add(new TestObj1("list4娴嬭瘯1", "list4娴嬭瘯2", "list4娴嬭瘯3"));
+ list4.add(new TestObj1("list4娴嬭瘯4", "list4娴嬭瘯5", "list4娴嬭瘯6"));
+ list4.add(new TestObj1("list4娴嬭瘯7", "list4娴嬭瘯8", "list4娴嬭瘯9"));
+ list4.add(new TestObj1("list4娴嬭瘯10", "list4娴嬭瘯11", "list4娴嬭瘯12"));
+ Map<String, Object> multiListMap = new HashMap<>();
+ multiListMap.put("map", map);
+ multiListMap.put("data1", list1);
+ multiListMap.put("data2", list2);
+ multiListMap.put("data3", list3);
+ multiListMap.put("data4", list4);
+ ExcelUtil.exportTemplateMultiList(multiListMap, "澶氬垪琛�.xlsx", "excel/澶氬垪琛�.xlsx", response);
+ }
+
+ /**
+ * 瀵煎嚭涓嬫媺妗�
+ *
+ * @param response /
+ */
+ @GetMapping("/exportWithOptions")
+ public void exportWithOptions(HttpServletResponse response) {
+ exportExcelService.exportWithOptions(response);
+ }
+
+ /**
+ * 鑷畾涔夊鍑�
+ *
+ * @param response /
+ */
+ @GetMapping("/customExport")
+ public void customExport(HttpServletResponse response) throws IOException {
+ exportExcelService.customExport(response);
+ }
+
+ /**
+ * 澶氫釜sheet瀵煎嚭
+ */
+ @GetMapping("/exportTemplateMultiSheet")
+ public void exportTemplateMultiSheet(HttpServletResponse response) {
+ List<TestObj1> list1 = new ArrayList<>();
+ list1.add(new TestObj1("list1娴嬭瘯1", "list1娴嬭瘯2", "list1娴嬭瘯3"));
+ list1.add(new TestObj1("list1娴嬭瘯4", "list1娴嬭瘯5", "list1娴嬭瘯6"));
+ List<TestObj1> list2 = new ArrayList<>();
+ list2.add(new TestObj1("list2娴嬭瘯1", "list2娴嬭瘯2", "list2娴嬭瘯3"));
+ list2.add(new TestObj1("list2娴嬭瘯4", "list2娴嬭瘯5", "list2娴嬭瘯6"));
+ List<TestObj1> list3 = new ArrayList<>();
+ list3.add(new TestObj1("list3娴嬭瘯1", "list3娴嬭瘯2", "list3娴嬭瘯3"));
+ list3.add(new TestObj1("list3娴嬭瘯4", "list3娴嬭瘯5", "list3娴嬭瘯6"));
+ List<TestObj1> list4 = new ArrayList<>();
+ list4.add(new TestObj1("list4娴嬭瘯1", "list4娴嬭瘯2", "list4娴嬭瘯3"));
+ list4.add(new TestObj1("list4娴嬭瘯4", "list4娴嬭瘯5", "list4娴嬭瘯6"));
+
+ List<Map<String, Object>> list = new ArrayList<>();
+ Map<String, Object> sheetMap1 = new HashMap<>();
+ sheetMap1.put("data1", list1);
+ Map<String, Object> sheetMap2 = new HashMap<>();
+ sheetMap2.put("data2", list2);
+ Map<String, Object> sheetMap3 = new HashMap<>();
+ sheetMap3.put("data3", list3);
+ Map<String, Object> sheetMap4 = new HashMap<>();
+ sheetMap4.put("data4", list4);
+
+ list.add(sheetMap1);
+ list.add(sheetMap2);
+ list.add(sheetMap3);
+ list.add(sheetMap4);
+ ExcelUtil.exportTemplateMultiSheet(list, "澶歴heet鍒楄〃", "excel/澶歴heet鍒楄〃.xlsx", response);
+ }
+
+ /**
+ * 瀵煎叆琛ㄦ牸
+ */
+ @PostMapping(value = "/importWithOptions", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public List<ExportDemoVo> importWithOptions(@RequestPart("file") MultipartFile file) throws Exception {
+ // 澶勭悊瑙f瀽缁撴灉
+ ExcelResult<ExportDemoVo> excelResult = ExcelUtil.importExcel(file.getInputStream(), ExportDemoVo.class, new ExportDemoListener());
+ return excelResult.getList();
+ }
+
+ @Data
+ @AllArgsConstructor
+ static class TestObj1 {
+ private String test1;
+ private String test2;
+ private String test3;
+ }
+
+ @Data
+ @AllArgsConstructor
+ static class TestObj {
+ private String name;
+ private String list1;
+ private String list2;
+ private String list3;
+ private String list4;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestI18nController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestI18nController.java
new file mode 100755
index 0000000..40393c5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestI18nController.java
@@ -0,0 +1,71 @@
+package org.dromara.demo.controller;
+
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.MessageUtils;
+import lombok.Data;
+import org.hibernate.validator.constraints.Range;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
+
+/**
+ * 娴嬭瘯鍥介檯鍖�
+ *
+ * @author Lion Li
+ */
+@Validated
+@RestController
+@RequestMapping("/demo/i18n")
+public class TestI18nController {
+
+ /**
+ * 閫氳繃code鑾峰彇鍥介檯鍖栧唴瀹�
+ * code涓� messages.properties 涓殑 key
+ * <p>
+ * 娴嬭瘯浣跨敤 user.register.success
+ *
+ * @param code 鍥介檯鍖朿ode
+ */
+ @GetMapping()
+ public R<Void> get(String code) {
+ return R.ok(MessageUtils.message(code));
+ }
+
+ /**
+ * Validator 鏍¢獙鍥介檯鍖�
+ * 涓嶄紶鍊� 鍒嗗埆鏌ョ湅寮傚父杩斿洖
+ * <p>
+ * 娴嬭瘯浣跨敤 not.null
+ */
+ @GetMapping("/test1")
+ public R<Void> test1(@NotBlank(message = "{not.null}") String str) {
+ return R.ok(str);
+ }
+
+ /**
+ * Bean 鏍¢獙鍥介檯鍖�
+ * 涓嶄紶鍊� 鍒嗗埆鏌ョ湅寮傚父杩斿洖
+ * <p>
+ * 娴嬭瘯浣跨敤 not.null
+ */
+ @GetMapping("/test2")
+ public R<TestI18nBo> test2(@Validated TestI18nBo bo) {
+ return R.ok(bo);
+ }
+
+ @Data
+ public static class TestI18nBo {
+
+ @NotBlank(message = "{not.null}")
+ private String name;
+
+ @NotNull(message = "{not.null}")
+ @Range(min = 0, max = 100, message = "{length.not.valid}")
+ private Integer age;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestSensitiveController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestSensitiveController.java
new file mode 100755
index 0000000..eba0552
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestSensitiveController.java
@@ -0,0 +1,76 @@
+package org.dromara.demo.controller;
+
+import org.dromara.common.core.domain.R;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.sensitive.annotation.Sensitive;
+import org.dromara.common.sensitive.core.SensitiveStrategy;
+import lombok.Data;
+import org.dromara.common.sensitive.core.SensitiveService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 娴嬭瘯鏁版嵁鑴辨晱鎺у埗鍣�
+ * <p>
+ * 榛樿绠$悊鍛樹笉杩囨护
+ * 闇�鑷鏍规嵁涓氬姟閲嶅啓瀹炵幇
+ *
+ * @author Lion Li
+ * @version 3.6.0
+ * @see SensitiveService
+ */
+@RestController
+@RequestMapping("/demo/sensitive")
+public class TestSensitiveController extends BaseController {
+
+ /**
+ * 娴嬭瘯鏁版嵁鑴辨晱
+ */
+ @GetMapping("/test")
+ public R<TestSensitive> test() {
+ TestSensitive testSensitive = new TestSensitive();
+ testSensitive.setIdCard("210397198608215431");
+ testSensitive.setPhone("17640125371");
+ testSensitive.setAddress("鍖椾含甯傛湞闃冲尯鏌愭煇鍥涘悎闄�1203瀹�");
+ testSensitive.setEmail("17640125371@163.com");
+ testSensitive.setBankCard("6226456952351452853");
+ return R.ok(testSensitive);
+ }
+
+ @Data
+ static class TestSensitive {
+
+ /**
+ * 韬唤璇�
+ */
+ @Sensitive(strategy = SensitiveStrategy.ID_CARD)
+ private String idCard;
+
+ /**
+ * 鐢佃瘽
+ */
+ @Sensitive(strategy = SensitiveStrategy.PHONE, roleKey = "common")
+ private String phone;
+
+ /**
+ * 鍦板潃
+ */
+ @Sensitive(strategy = SensitiveStrategy.ADDRESS, perms = "system:user:query")
+ private String address;
+
+ /**
+ * 閭
+ */
+ @Sensitive(strategy = SensitiveStrategy.EMAIL, roleKey = "common", perms = "system:user:query1")
+ private String email;
+
+ /**
+ * 閾惰鍗�
+ */
+ @Sensitive(strategy = SensitiveStrategy.BANK_CARD, roleKey = "common1", perms = "system:user:query")
+ private String bankCard;
+
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestTreeController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestTreeController.java
new file mode 100755
index 0000000..5c55205
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestTreeController.java
@@ -0,0 +1,107 @@
+package org.dromara.demo.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.core.validate.QueryGroup;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.demo.domain.bo.TestTreeBo;
+import org.dromara.demo.domain.vo.TestTreeVo;
+import org.dromara.demo.service.ITestTreeService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 娴嬭瘯鏍戣〃Controller
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/tree")
+public class TestTreeController extends BaseController {
+
+ private final ITestTreeService testTreeService;
+
+ /**
+ * 鏌ヨ娴嬭瘯鏍戣〃鍒楄〃
+ */
+ @SaCheckPermission("demo:tree:list")
+ @GetMapping("/list")
+ public R<List<TestTreeVo>> list(@Validated(QueryGroup.class) TestTreeBo bo) {
+ List<TestTreeVo> list = testTreeService.queryList(bo);
+ return R.ok(list);
+ }
+
+ /**
+ * 瀵煎嚭娴嬭瘯鏍戣〃鍒楄〃
+ */
+ @SaCheckPermission("demo:tree:export")
+ @Log(title = "娴嬭瘯鏍戣〃", businessType = BusinessType.EXPORT)
+ @GetMapping("/export")
+ public void export(@Validated TestTreeBo bo, HttpServletResponse response) {
+ List<TestTreeVo> list = testTreeService.queryList(bo);
+ ExcelUtil.exportExcel(list, "娴嬭瘯鏍戣〃", TestTreeVo.class, response);
+ }
+
+ /**
+ * 鑾峰彇娴嬭瘯鏍戣〃璇︾粏淇℃伅
+ *
+ * @param id 娴嬭瘯鏍慖D
+ */
+ @SaCheckPermission("demo:tree:query")
+ @GetMapping("/{id}")
+ public R<TestTreeVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable("id") Long id) {
+ return R.ok(testTreeService.queryById(id));
+ }
+
+ /**
+ * 鏂板娴嬭瘯鏍戣〃
+ */
+ @SaCheckPermission("demo:tree:add")
+ @Log(title = "娴嬭瘯鏍戣〃", businessType = BusinessType.INSERT)
+ @RepeatSubmit
+ @PostMapping()
+ public R<Void> add(@Validated(AddGroup.class) @RequestBody TestTreeBo bo) {
+ return toAjax(testTreeService.insertByBo(bo));
+ }
+
+ /**
+ * 淇敼娴嬭瘯鏍戣〃
+ */
+ @SaCheckPermission("demo:tree:edit")
+ @Log(title = "娴嬭瘯鏍戣〃", businessType = BusinessType.UPDATE)
+ @RepeatSubmit
+ @PutMapping()
+ public R<Void> edit(@Validated(EditGroup.class) @RequestBody TestTreeBo bo) {
+ return toAjax(testTreeService.updateByBo(bo));
+ }
+
+ /**
+ * 鍒犻櫎娴嬭瘯鏍戣〃
+ *
+ * @param ids 娴嬭瘯鏍慖D涓�
+ */
+ @SaCheckPermission("demo:tree:remove")
+ @Log(title = "娴嬭瘯鏍戣〃", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long[] ids) {
+ return toAjax(testTreeService.deleteWithValidByIds(Arrays.asList(ids), true));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/WebSocketController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/WebSocketController.java
new file mode 100755
index 0000000..65bf68e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/WebSocketController.java
@@ -0,0 +1,33 @@
+package org.dromara.demo.controller;
+
+import org.dromara.common.core.domain.R;
+import org.dromara.common.websocket.dto.WebSocketMessageDto;
+import org.dromara.common.websocket.utils.WebSocketUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * WebSocket 婕旂ず妗堜緥
+ *
+ * @author zendwang
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/websocket")
+@Slf4j
+public class WebSocketController {
+
+ /**
+ * 鍙戝竷娑堟伅
+ *
+ * @param dto 鍙戦�佸唴瀹�
+ */
+ @GetMapping("/send")
+ public R<Void> send(WebSocketMessageDto dto) throws InterruptedException {
+ WebSocketUtils.publishMessage(dto);
+ return R.ok("鎿嶄綔鎴愬姛");
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/package-info.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/package-info.java
new file mode 100755
index 0000000..16c30f8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/package-info.java
@@ -0,0 +1 @@
+package org.dromara.demo.controller;
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/BoundedQueueController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/BoundedQueueController.java
new file mode 100755
index 0000000..a3051b2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/BoundedQueueController.java
@@ -0,0 +1,92 @@
+package org.dromara.demo.controller.queue;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.redis.utils.QueueUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 鏈夌晫闃熷垪 婕旂ず妗堜緥
+ * <p>
+ * 杞婚噺绾ч槦鍒� 閲嶉噺绾ф暟鎹噺 璇蜂娇鐢� MQ
+ * <p>
+ * 闆嗙兢娴嬭瘯閫氳繃 鍚屼竴涓暟鎹彧浼氳娑堣垂涓�娆� 鍋氬ソ浜嬪姟琛ュ伩
+ * 闆嗙兢娴嬭瘯娴佺▼ 鍦ㄥ叾涓竴鍙板彂閫佹暟鎹� 涓ょ鍒嗗埆璋冪敤鑾峰彇鎺ュ彛 涓�娆¤幏鍙栦竴鏉�
+ *
+ * @author Lion Li
+ * @version 3.6.0
+ * @deprecated redisson 鏂扮増鏈凡缁忓皢闃熷垪鍔熻兘鏍囪鍒犻櫎 涓�浜涙妧鏈棶棰樻棤娉曡В鍐� 寤鸿鎼缓MQ浣跨敤
+ */
+@Deprecated
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/queue/bounded")
+public class BoundedQueueController {
+
+
+ /**
+ * 娣诲姞闃熷垪鏁版嵁
+ *
+ * @param queueName 闃熷垪鍚�
+ * @param capacity 瀹归噺
+ */
+ @GetMapping("/add")
+ public R<Void> add(String queueName, int capacity) {
+ // 鐢ㄥ畬浜嗕竴瀹氳閿�姣� 鍚﹀垯浼氫竴鐩村瓨鍦�
+ boolean b = QueueUtils.destroyBoundedQueue(queueName);
+ log.info("閫氶亾: {} , 鍒犻櫎: {}", queueName, b);
+ // 鍒濆鍖栬缃竴娆″嵆鍙�
+ if (QueueUtils.trySetBoundedQueueCapacity(queueName, capacity)) {
+ log.info("閫氶亾: {} , 璁剧疆瀹归噺: {}", queueName, capacity);
+ } else {
+ log.info("閫氶亾: {} , 璁剧疆瀹归噺澶辫触", queueName);
+ return R.fail("鎿嶄綔澶辫触");
+ }
+ for (int i = 0; i < 11; i++) {
+ String data = "data-" + i;
+ boolean flag = QueueUtils.addBoundedQueueObject(queueName, data);
+ if (flag == false) {
+ log.info("閫氶亾: {} , 鍙戦�佹暟鎹�: {} 澶辫触, 閫氶亾宸叉弧", queueName, data);
+ } else {
+ log.info("閫氶亾: {} , 鍙戦�佹暟鎹�: {}", queueName, data);
+ }
+ }
+ return R.ok("鎿嶄綔鎴愬姛");
+ }
+
+ /**
+ * 鍒犻櫎闃熷垪鏁版嵁
+ *
+ * @param queueName 闃熷垪鍚�
+ */
+ @GetMapping("/remove")
+ public R<Void> remove(String queueName) {
+ String data = "data-" + 5;
+ if (QueueUtils.removeBoundedQueueObject(queueName, data)) {
+ log.info("閫氶亾: {} , 鍒犻櫎鏁版嵁: {}", queueName, data);
+ } else {
+ return R.fail("鎿嶄綔澶辫触");
+ }
+ return R.ok("鎿嶄綔鎴愬姛");
+ }
+
+ /**
+ * 鑾峰彇闃熷垪鏁版嵁
+ *
+ * @param queueName 闃熷垪鍚�
+ */
+ @GetMapping("/get")
+ public R<Void> get(String queueName) {
+ String data;
+ do {
+ data = QueueUtils.getBoundedQueueObject(queueName);
+ log.info("閫氶亾: {} , 鑾峰彇鏁版嵁: {}", queueName, data);
+ } while (data != null);
+ return R.ok("鎿嶄綔鎴愬姛");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/DelayedQueueController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/DelayedQueueController.java
new file mode 100755
index 0000000..7c494e2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/DelayedQueueController.java
@@ -0,0 +1,97 @@
+package org.dromara.demo.controller.queue;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.redis.utils.QueueUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 寤惰繜闃熷垪 婕旂ず妗堜緥
+ * <p>
+ * 杞婚噺绾ч槦鍒� 閲嶉噺绾ф暟鎹噺 璇蜂娇鐢� MQ
+ * 渚嬪: 鍒涘缓璁㈠崟30鍒嗛挓鍚庤繃鏈熷鐞�
+ * <p>
+ * 闆嗙兢娴嬭瘯閫氳繃 鍚屼竴涓暟鎹彧浼氳娑堣垂涓�娆� 鍋氬ソ浜嬪姟琛ュ伩
+ * 闆嗙兢娴嬭瘯娴佺▼ 涓ゅ彴闆嗙兢鍒嗗埆寮�鍚闃� 鍦ㄥ叾涓竴鍙板彂閫佹暟鎹� 瑙傚療鎺ユ敹娑堟伅鐨勮寰�
+ *
+ * @author Lion Li
+ * @version 3.6.0
+ * @deprecated redisson 鏂扮増鏈凡缁忓皢闃熷垪鍔熻兘鏍囪鍒犻櫎 涓�浜涙妧鏈棶棰樻棤娉曡В鍐� 寤鸿鎼缓MQ浣跨敤
+ */
+@Deprecated
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/queue/delayed")
+public class DelayedQueueController {
+
+ /**
+ * 璁㈤槄闃熷垪
+ *
+ * @param queueName 闃熷垪鍚�
+ */
+ @GetMapping("/subscribe")
+ public R<Void> subscribe(String queueName) {
+ log.info("閫氶亾: {} 鐩戝惉涓�......", queueName);
+ // 椤圭洰鍒濆鍖栬缃竴娆″嵆鍙�
+ QueueUtils.subscribeBlockingQueue(queueName, (String orderNum) -> {
+ // 瑙傚療鎺ユ敹鏃堕棿
+ log.info("閫氶亾: {}, 鏀跺埌鏁版嵁: {}", queueName, orderNum);
+ return CompletableFuture.runAsync(() -> {
+ // 寮傛澶勭悊鏁版嵁閫昏緫 涓嶈鍦ㄤ笂鏂瑰鐞嗕笟鍔¢�昏緫
+ log.info("鏁版嵁澶勭悊: {}", orderNum);
+ });
+ }, true);
+ return R.ok("鎿嶄綔鎴愬姛");
+ }
+
+ /**
+ * 娣诲姞闃熷垪鏁版嵁
+ *
+ * @param queueName 闃熷垪鍚�
+ * @param orderNum 璁㈠崟鍙�
+ * @param time 寤惰繜鏃堕棿(绉�)
+ */
+ @GetMapping("/add")
+ public R<Void> add(String queueName, String orderNum, Long time) {
+ QueueUtils.addDelayedQueueObject(queueName, orderNum, time, TimeUnit.SECONDS);
+ // 瑙傚療鍙戦�佹椂闂�
+ log.info("閫氶亾: {} , 鍙戦�佹暟鎹�: {}", queueName, orderNum);
+ return R.ok("鎿嶄綔鎴愬姛");
+ }
+
+ /**
+ * 鍒犻櫎闃熷垪鏁版嵁
+ *
+ * @param queueName 闃熷垪鍚�
+ * @param orderNum 璁㈠崟鍙�
+ */
+ @GetMapping("/remove")
+ public R<Void> remove(String queueName, String orderNum) {
+ if (QueueUtils.removeDelayedQueueObject(queueName, orderNum)) {
+ log.info("閫氶亾: {} , 鍒犻櫎鏁版嵁: {}", queueName, orderNum);
+ } else {
+ return R.fail("鎿嶄綔澶辫触");
+ }
+ return R.ok("鎿嶄綔鎴愬姛");
+ }
+
+ /**
+ * 閿�姣侀槦鍒�
+ *
+ * @param queueName 闃熷垪鍚�
+ */
+ @GetMapping("/destroy")
+ public R<Void> destroy(String queueName) {
+ // 鐢ㄥ畬浜嗕竴瀹氳閿�姣� 鍚﹀垯浼氫竴鐩村瓨鍦�
+ QueueUtils.destroyDelayedQueue(queueName);
+ return R.ok("鎿嶄綔鎴愬姛");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/PriorityDemo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/PriorityDemo.java
new file mode 100755
index 0000000..43862a6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/PriorityDemo.java
@@ -0,0 +1,24 @@
+package org.dromara.demo.controller.queue;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 瀹炰綋绫� 娉ㄦ剰涓嶅厑璁镐娇鐢ㄥ唴閮ㄧ被 鍚﹀垯浼氭壘涓嶅埌绫�
+ *
+ * @author Lion Li
+ * @version 3.6.0
+ * @deprecated redisson 鏂扮増鏈凡缁忓皢闃熷垪鍔熻兘鏍囪鍒犻櫎 涓�浜涙妧鏈棶棰樻棤娉曡В鍐� 寤鸿鎼缓MQ浣跨敤
+ */
+@Deprecated
+@Data
+@NoArgsConstructor
+public class PriorityDemo implements Comparable<PriorityDemo> {
+ private String name;
+ private Integer orderNum;
+
+ @Override
+ public int compareTo(PriorityDemo other) {
+ return Integer.compare(getOrderNum(), other.getOrderNum());
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/PriorityQueueController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/PriorityQueueController.java
new file mode 100755
index 0000000..2f2b737
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/PriorityQueueController.java
@@ -0,0 +1,91 @@
+package org.dromara.demo.controller.queue;
+
+import cn.hutool.core.util.RandomUtil;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.redis.utils.QueueUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 浼樺厛闃熷垪 婕旂ず妗堜緥
+ * <p>
+ * 杞婚噺绾ч槦鍒� 閲嶉噺绾ф暟鎹噺 璇蜂娇鐢� MQ
+ * <p>
+ * 闆嗙兢娴嬭瘯閫氳繃 鍚屼竴涓秷鎭彧浼氳娑堣垂涓�娆� 鍋氬ソ浜嬪姟琛ュ伩
+ * 闆嗙兢娴嬭瘯娴佺▼ 鍦ㄥ叾涓竴鍙板彂閫佹暟鎹� 涓ょ鍒嗗埆璋冪敤鑾峰彇鎺ュ彛 涓�娆¤幏鍙栦竴鏉�
+ *
+ * @author Lion Li
+ * @version 3.6.0
+ * @deprecated redisson 鏂扮増鏈凡缁忓皢闃熷垪鍔熻兘鏍囪鍒犻櫎 涓�浜涙妧鏈棶棰樻棤娉曡В鍐� 寤鸿鎼缓MQ浣跨敤
+ */
+@Deprecated
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/queue/priority")
+public class PriorityQueueController {
+
+ /**
+ * 娣诲姞闃熷垪鏁版嵁
+ *
+ * @param queueName 闃熷垪鍚�
+ */
+ @GetMapping("/add")
+ public R<Void> add(String queueName) {
+ // 鐢ㄥ畬浜嗕竴瀹氳閿�姣� 鍚﹀垯浼氫竴鐩村瓨鍦�
+ boolean b = QueueUtils.destroyPriorityQueue(queueName);
+ log.info("閫氶亾: {} , 鍒犻櫎: {}", queueName, b);
+
+ for (int i = 0; i < 10; i++) {
+ int randomNum = RandomUtil.randomInt(10);
+ PriorityDemo data = new PriorityDemo();
+ data.setName("data-" + i);
+ data.setOrderNum(randomNum);
+ if (QueueUtils.addPriorityQueueObject(queueName, data)) {
+ log.info("閫氶亾: {} , 鍙戦�佹暟鎹�: {}", queueName, data);
+ } else {
+ log.info("閫氶亾: {} , 鍙戦�佹暟鎹�: {}, 鍙戦�佸け璐�", queueName, data);
+ }
+ }
+ return R.ok("鎿嶄綔鎴愬姛");
+ }
+
+ /**
+ * 鍒犻櫎闃熷垪鏁版嵁
+ *
+ * @param queueName 闃熷垪鍚�
+ * @param name 瀵硅薄鍚�
+ * @param orderNum 鎺掑簭鍙�
+ */
+ @GetMapping("/remove")
+ public R<Void> remove(String queueName, String name, Integer orderNum) {
+ PriorityDemo data = new PriorityDemo();
+ data.setName(name);
+ data.setOrderNum(orderNum);
+ if (QueueUtils.removePriorityQueueObject(queueName, data)) {
+ log.info("閫氶亾: {} , 鍒犻櫎鏁版嵁: {}", queueName, data);
+ } else {
+ return R.fail("鎿嶄綔澶辫触");
+ }
+ return R.ok("鎿嶄綔鎴愬姛");
+ }
+
+ /**
+ * 鑾峰彇闃熷垪鏁版嵁
+ *
+ * @param queueName 闃熷垪鍚�
+ */
+ @GetMapping("/get")
+ public R<Void> get(String queueName) {
+ PriorityDemo data;
+ do {
+ data = QueueUtils.getPriorityQueueObject(queueName);
+ log.info("閫氶亾: {} , 鑾峰彇鏁版嵁: {}", queueName, data);
+ } while (data != null);
+ return R.ok("鎿嶄綔鎴愬姛");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/TestDemo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/TestDemo.java
new file mode 100755
index 0000000..d3af0c9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/TestDemo.java
@@ -0,0 +1,68 @@
+package org.dromara.demo.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import org.dromara.common.tenant.core.TenantEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 娴嬭瘯鍗曡〃瀵硅薄 test_demo
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("test_demo")
+public class TestDemo extends TenantEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭
+ */
+ @TableId(value = "id")
+ private Long id;
+
+ /**
+ * 閮ㄩ棬id
+ */
+ private Long deptId;
+
+ /**
+ * 鐢ㄦ埛id
+ */
+ private Long userId;
+
+ /**
+ * 鎺掑簭鍙�
+ */
+ @OrderBy(asc = false, sort = 1)
+ private Integer orderNum;
+
+ /**
+ * key閿�
+ */
+ private String testKey;
+
+ /**
+ * 鍊�
+ */
+ private String value;
+
+ /**
+ * 鐗堟湰
+ */
+ @Version
+ private Long version;
+
+ /**
+ * 鍒犻櫎鏍囧織
+ */
+ @TableLogic
+ private Long delFlag;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/TestDemoEncrypt.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/TestDemoEncrypt.java
new file mode 100755
index 0000000..bdcd596
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/TestDemoEncrypt.java
@@ -0,0 +1,29 @@
+package org.dromara.demo.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.dromara.common.encrypt.annotation.EncryptField;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("test_demo")
+public class TestDemoEncrypt extends TestDemo {
+
+ /**
+ * key閿�
+ */
+ // @EncryptField(algorithm=AlgorithmType.SM2, privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgZSlOvw8FBiH+aFJWLYZP/VRjg9wjfRarTkGBZd/T3N+gCgYIKoEcz1UBgi2hRANCAAR5DGuQwJqkxnbCsP+iPSDoHWIF4RwcR5EsSvT8QPxO1wRkR2IhCkzvRb32x2CUgJFdvoqVqfApFDPZzShqzBwX", publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEeQxrkMCapMZ2wrD/oj0g6B1iBeEcHEeRLEr0/ED8TtcEZEdiIQpM70W99sdglICRXb6KlanwKRQz2c0oaswcFw==")
+ @EncryptField(algorithm = AlgorithmType.RSA, privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANBBEeueWlXlkkj2+WY5l+IWe42d8b5K28g+G/CFKC/yYAEHtqGlCsBOrb+YBkG9mPzmuYA/n9k0NFIc8E8yY5vZQaroyFBrTTWEzG9RY2f7Y3svVyybs6jpXSUs4xff8abo7wL1Y/wUaeatTViamxYnyTvdTmLm3d+JjRij68rxAgMBAAECgYAB0TnhXraSopwIVRfmboea1b0upl+BUdTJcmci412UjrKr5aE695ZLPkXbFXijVu7HJlyyv94NVUdaMACV7Ku/S2RuNB70M7YJm8rAjHFC3/i2ZeIM60h1Ziy4QKv0XM3pRATlDCDNhC1WUrtQCQSgU8kcp6eUUppruOqDzcY04QJBAPm9+sBP9CwDRgy3e5+V8aZtJkwDstb0lVVV/KY890cydVxiCwvX3fqVnxKMlb+x0YtH0sb9v+71xvK2lGobaRECQQDVePU6r/cCEfpc+nkWF6osAH1f8Mux3rYv2DoBGvaPzV2BGfsLed4neRfCwWNCKvGPCdW+L0xMJg8+RwaoBUPhAkAT5kViqXxFPYWJYd1h2+rDXhMdH3ZSlm6HvDBDdrwlWinr0Iwcx3iSjPV93uHXwm118aUj4fg3LDJMCKxOwBxhAkByrQXfvwOMYygBprRBf/j0plazoWFrbd6lGR0f1uI5IfNnFRPdeFw1DEINZ2Hw+6zEUF44SqRMC+4IYJNc02dBAkBCgy7RvfyV/A7N6kKXxTHauY0v6XwSSvpeKtRJkbIcRWOdIYvaHO9L7cklj3vIEdwjSUp9K4VTBYYlmAz1xh03", publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQQRHrnlpV5ZJI9vlmOZfiFnuNnfG+StvIPhvwhSgv8mABB7ahpQrATq2/mAZBvZj85rmAP5/ZNDRSHPBPMmOb2UGq6MhQa001hMxvUWNn+2N7L1csm7Oo6V0lLOMX3/Gm6O8C9WP8FGnmrU1YmpsWJ8k73U5i5t3fiY0Yo+vK8QIDAQAB")
+ private String testKey;
+
+ /**
+ * 鍊�
+ */
+ // @EncryptField // 浠�涔堜篃涓嶅啓璧伴粯璁ml閰嶇疆
+ // @EncryptField(algorithm = AlgorithmType.SM4, password = "10rfylhtccpuyke5")
+ @EncryptField(algorithm = AlgorithmType.AES, password = "10rfylhtccpuyke5")
+ private String value;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/TestTree.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/TestTree.java
new file mode 100755
index 0000000..fd68253
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/TestTree.java
@@ -0,0 +1,65 @@
+package org.dromara.demo.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.Version;
+import org.dromara.common.tenant.core.TenantEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 娴嬭瘯鏍戣〃瀵硅薄 test_tree
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("test_tree")
+public class TestTree extends TenantEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭
+ */
+ @TableId(value = "id")
+ private Long id;
+
+ /**
+ * 鐖禝D
+ */
+ private Long parentId;
+
+ /**
+ * 閮ㄩ棬id
+ */
+ private Long deptId;
+
+ /**
+ * 鐢ㄦ埛id
+ */
+ private Long userId;
+
+ /**
+ * 鏍戣妭鐐瑰悕
+ */
+ private String treeName;
+
+ /**
+ * 鐗堟湰
+ */
+ @Version
+ private Long version;
+
+ /**
+ * 鍒犻櫎鏍囧織
+ */
+ @TableLogic
+ private Long delFlag;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestDemoBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestDemoBo.java
new file mode 100755
index 0000000..ef21980
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestDemoBo.java
@@ -0,0 +1,67 @@
+package org.dromara.demo.domain.bo;
+
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.demo.domain.TestDemo;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
+/**
+ * 娴嬭瘯鍗曡〃涓氬姟瀵硅薄 test_demo
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = TestDemo.class, reverseConvertGenerate = false)
+public class TestDemoBo extends BaseEntity {
+
+ /**
+ * 涓婚敭
+ */
+ @NotNull(message = "涓婚敭涓嶈兘涓虹┖", groups = {EditGroup.class})
+ private Long id;
+
+ /**
+ * 閮ㄩ棬id
+ */
+ @NotNull(message = "閮ㄩ棬id涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+ private Long deptId;
+
+ /**
+ * 鐢ㄦ埛id
+ */
+ @NotNull(message = "鐢ㄦ埛id涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+ private Long userId;
+
+ /**
+ * 鎺掑簭鍙�
+ */
+ @NotNull(message = "鎺掑簭鍙蜂笉鑳戒负绌�", groups = {AddGroup.class, EditGroup.class})
+ private Integer orderNum;
+
+ /**
+ * key閿�
+ */
+ @NotBlank(message = "key閿笉鑳戒负绌�", groups = {AddGroup.class, EditGroup.class})
+ private String testKey;
+
+ /**
+ * 鍊�
+ */
+ @NotBlank(message = "鍊间笉鑳戒负绌�", groups = {AddGroup.class, EditGroup.class})
+ private String value;
+
+ /**
+ * 鐗堟湰
+ */
+ private Long version;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestDemoImportVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestDemoImportVo.java
new file mode 100755
index 0000000..dc8b35f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestDemoImportVo.java
@@ -0,0 +1,53 @@
+package org.dromara.demo.domain.bo;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
+/**
+ * 娴嬭瘯鍗曡〃涓氬姟瀵硅薄 test_demo
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+@Data
+public class TestDemoImportVo {
+
+ /**
+ * 閮ㄩ棬id
+ */
+ @NotNull(message = "閮ㄩ棬id涓嶈兘涓虹┖")
+ @ExcelProperty(value = "閮ㄩ棬id")
+ private Long deptId;
+
+ /**
+ * 鐢ㄦ埛id
+ */
+ @NotNull(message = "鐢ㄦ埛id涓嶈兘涓虹┖")
+ @ExcelProperty(value = "鐢ㄦ埛id")
+ private Long userId;
+
+ /**
+ * 鎺掑簭鍙�
+ */
+ @NotNull(message = "鎺掑簭鍙蜂笉鑳戒负绌�")
+ @ExcelProperty(value = "鎺掑簭鍙�")
+ private Long orderNum;
+
+ /**
+ * key閿�
+ */
+ @NotBlank(message = "key閿笉鑳戒负绌�")
+ @ExcelProperty(value = "key閿�")
+ private String testKey;
+
+ /**
+ * 鍊�
+ */
+ @NotBlank(message = "鍊间笉鑳戒负绌�")
+ @ExcelProperty(value = "鍊�")
+ private String value;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestTreeBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestTreeBo.java
new file mode 100755
index 0000000..1bbac0e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/bo/TestTreeBo.java
@@ -0,0 +1,54 @@
+package org.dromara.demo.domain.bo;
+
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.demo.domain.TestTree;
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 娴嬭瘯鏍戣〃涓氬姟瀵硅薄 test_tree
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = TestTree.class, reverseConvertGenerate = false)
+public class TestTreeBo extends BaseEntity {
+
+ /**
+ * 涓婚敭
+ */
+ @NotNull(message = "涓婚敭涓嶈兘涓虹┖", groups = {EditGroup.class})
+ private Long id;
+
+ /**
+ * 鐖禝D
+ */
+ private Long parentId;
+
+ /**
+ * 閮ㄩ棬id
+ */
+ @NotNull(message = "閮ㄩ棬id涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+ private Long deptId;
+
+ /**
+ * 鐢ㄦ埛id
+ */
+ @NotNull(message = "鐢ㄦ埛id涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+ private Long userId;
+
+ /**
+ * 鏍戣妭鐐瑰悕
+ */
+ @NotBlank(message = "鏍戣妭鐐瑰悕涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+ private String treeName;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/package-info.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/package-info.java
new file mode 100755
index 0000000..cb7d83f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/package-info.java
@@ -0,0 +1 @@
+package org.dromara.demo.domain;
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/ExportDemoVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/ExportDemoVo.java
new file mode 100755
index 0000000..b42ce76
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/ExportDemoVo.java
@@ -0,0 +1,122 @@
+package org.dromara.demo.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.enums.UserStatus;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.annotation.ExcelEnumFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.common.excel.convert.ExcelEnumConvert;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 甯︽湁涓嬫媺閫夌殑Excel瀵煎嚭
+ *
+ * @author Emil.Zhang
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AllArgsConstructor
+@NoArgsConstructor
+public class ExportDemoVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鐢ㄦ埛鏄电О
+ */
+ @ExcelProperty(value = "鐢ㄦ埛鍚�", index = 0)
+ @NotEmpty(message = "鐢ㄦ埛鍚嶄笉鑳戒负绌�", groups = AddGroup.class)
+ private String nickName;
+
+ /**
+ * 鐢ㄦ埛绫诲瀷
+ * </p>
+ * 浣跨敤ExcelEnumFormat娉ㄨВ闇�瑕佽繘琛屼笅鎷夐�夌殑閮ㄥ垎
+ */
+ @ExcelProperty(value = "鐢ㄦ埛绫诲瀷", index = 1, converter = ExcelEnumConvert.class)
+ @ExcelEnumFormat(enumClass = UserStatus.class, textField = "info")
+ @NotEmpty(message = "鐢ㄦ埛绫诲瀷涓嶈兘涓虹┖", groups = AddGroup.class)
+ private String userStatus;
+
+ /**
+ * 鎬у埆
+ * <p>
+ * 浣跨敤ExcelDictFormat娉ㄨВ闇�瑕佽繘琛屼笅鎷夐�夌殑閮ㄥ垎
+ */
+ @ExcelProperty(value = "鎬у埆", index = 2, converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_user_sex")
+ @NotEmpty(message = "鎬у埆涓嶈兘涓虹┖", groups = AddGroup.class)
+ private String gender;
+
+ /**
+ * 鎵嬫満鍙�
+ */
+ @ExcelProperty(value = "鎵嬫満鍙�", index = 3)
+ @NotEmpty(message = "鎵嬫満鍙蜂笉鑳戒负绌�", groups = AddGroup.class)
+ private String phoneNumber;
+
+ /**
+ * Email
+ */
+ @ExcelProperty(value = "Email", index = 4)
+ @NotEmpty(message = "Email涓嶈兘涓虹┖", groups = AddGroup.class)
+ private String email;
+
+ /**
+ * 鐪�
+ * <p>
+ * 绾ц仈涓嬫媺锛屼粎鍒ゆ柇鏄惁閫変簡
+ */
+ @ExcelProperty(value = "鐪�", index = 5)
+ @NotNull(message = "鐪佷笉鑳戒负绌�", groups = AddGroup.class)
+ private String province;
+
+ /**
+ * 鏁版嵁搴撲腑鐨勭渷ID
+ * </p>
+ * 澶勭悊瀹屾瘯鍚庡啀鍒ゆ柇鏄惁甯傛纭殑鍊�
+ */
+ @NotNull(message = "璇峰嬁鎵嬪姩杈撳叆", groups = EditGroup.class)
+ private Integer provinceId;
+
+ /**
+ * 甯�
+ * <p>
+ * 绾ц仈涓嬫媺
+ */
+ @ExcelProperty(value = "甯�", index = 6)
+ @NotNull(message = "甯備笉鑳戒负绌�", groups = AddGroup.class)
+ private String city;
+
+ /**
+ * 鏁版嵁搴撲腑鐨勫競ID
+ */
+ @NotNull(message = "璇峰嬁鎵嬪姩杈撳叆", groups = EditGroup.class)
+ private Integer cityId;
+
+ /**
+ * 鍘�
+ * <p>
+ * 绾ц仈涓嬫媺
+ */
+ @ExcelProperty(value = "鍘�", index = 7)
+ @NotNull(message = "鍘夸笉鑳戒负绌�", groups = AddGroup.class)
+ private String area;
+
+ /**
+ * 鏁版嵁搴撲腑鐨勫幙ID
+ */
+ @NotNull(message = "璇峰嬁鎵嬪姩杈撳叆", groups = EditGroup.class)
+ private Integer areaId;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestDemoVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestDemoVo.java
new file mode 100755
index 0000000..642e0f7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestDemoVo.java
@@ -0,0 +1,119 @@
+package org.dromara.demo.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.format.DateTimeFormat;
+import org.dromara.common.excel.annotation.ExcelNotation;
+import org.dromara.common.excel.annotation.ExcelRequired;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.demo.domain.TestDemo;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 娴嬭瘯鍗曡〃瑙嗗浘瀵硅薄 test_demo
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = TestDemo.class)
+public class TestDemoVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭
+ */
+ @ExcelProperty(value = "涓婚敭")
+ private Long id;
+
+ /**
+ * 閮ㄩ棬id
+ */
+ @ExcelRequired
+ @ExcelProperty(value = "閮ㄩ棬id")
+ private Long deptId;
+
+ /**
+ * 鐢ㄦ埛id
+ */
+ @ExcelRequired
+ @ExcelProperty(value = "鐢ㄦ埛id", index = 5)
+ private Long userId;
+
+ /**
+ * 鎺掑簭鍙�
+ */
+ @ExcelRequired
+ @ExcelProperty(value = "鎺掑簭鍙�")
+ private Integer orderNum;
+
+ /**
+ * key閿�
+ */
+ @ExcelNotation(value = "娴嬭瘯key")
+ @ExcelProperty(value = "key閿�")
+ private String testKey;
+
+ /**
+ * 鍊�
+ */
+ @ExcelNotation(value = "娴嬭瘯value")
+ @ExcelProperty(value = "鍊�")
+ private String value;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ @ExcelRequired
+ @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
+ @ExcelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+
+ /**
+ * 鍒涘缓浜�
+ */
+ @ExcelProperty(value = "鍒涘缓浜�")
+ private Long createBy;
+
+ /**
+ * 鍒涘缓浜鸿处鍙�
+ */
+ @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy")
+ @ExcelProperty(value = "鍒涘缓浜鸿处鍙�")
+ private String createByName;
+
+ /**
+ * 鏇存柊鏃堕棿
+ */
+ @ExcelProperty(value = "鏇存柊鏃堕棿")
+ private Date updateTime;
+
+ /**
+ * 鏇存柊浜�
+ */
+ @ExcelProperty(value = "鏇存柊浜�")
+ private Long updateBy;
+
+ /**
+ * 鏇存柊浜鸿处鍙�
+ */
+ @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "updateBy")
+ @ExcelProperty(value = "鏇存柊浜鸿处鍙�")
+ private String updateByName;
+
+ /**
+ * 鐗堟湰
+ */
+ private Long version;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestTreeVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestTreeVo.java
new file mode 100755
index 0000000..ee2336a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/vo/TestTreeVo.java
@@ -0,0 +1,64 @@
+package org.dromara.demo.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.demo.domain.TestTree;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 娴嬭瘯鏍戣〃瑙嗗浘瀵硅薄 test_tree
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = TestTree.class)
+public class TestTreeVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭
+ */
+ private Long id;
+
+ /**
+ * 鐖秈d
+ */
+ @ExcelProperty(value = "鐖秈d")
+ private Long parentId;
+
+ /**
+ * 閮ㄩ棬id
+ */
+ @ExcelProperty(value = "閮ㄩ棬id")
+ private Long deptId;
+
+ /**
+ * 鐢ㄦ埛id
+ */
+ @ExcelProperty(value = "鐢ㄦ埛id")
+ private Long userId;
+
+ /**
+ * 鏍戣妭鐐瑰悕
+ */
+ @ExcelProperty(value = "鏍戣妭鐐瑰悕")
+ private String treeName;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ @ExcelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/listener/ExportDemoListener.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/listener/ExportDemoListener.java
new file mode 100755
index 0000000..de92760
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/listener/ExportDemoListener.java
@@ -0,0 +1,68 @@
+package org.dromara.demo.listener;
+
+import cn.hutool.core.util.NumberUtil;
+import cn.idev.excel.context.AnalysisContext;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.core.DefaultExcelListener;
+import org.dromara.common.excel.core.DropDownOptions;
+import org.dromara.demo.domain.vo.ExportDemoVo;
+
+import java.util.List;
+
+/**
+ * Excel甯︿笅鎷夋鐨勮В鏋愬鐞嗗櫒
+ *
+ * @author Emil.Zhang
+ */
+public class ExportDemoListener extends DefaultExcelListener<ExportDemoVo> {
+
+ public ExportDemoListener() {
+ // 鏄剧ず浣跨敤鏋勯�犲嚱鏁帮紝鍚﹀垯灏嗗鑷寸┖鎸囬拡
+ super(true);
+ }
+
+ @Override
+ public void invoke(ExportDemoVo data, AnalysisContext context) {
+ // 鍏堟牎楠屽繀濉�
+ ValidatorUtils.validate(data, AddGroup.class);
+
+ // 澶勭悊绾ц仈涓嬫媺鐨勯儴鍒�
+ String province = data.getProvince();
+ String city = data.getCity();
+ String area = data.getArea();
+ // 鏈鐢ㄦ埛閫夋嫨鐨勭渷
+ List<String> thisRowSelectedProvinceOption = DropDownOptions.analyzeOptionValue(province);
+ if (thisRowSelectedProvinceOption.size() == 2) {
+ String provinceIdStr = thisRowSelectedProvinceOption.get(1);
+ if (NumberUtil.isNumber(provinceIdStr)) {
+ // 涓ユ牸瑕佹眰鏁版嵁鐨勮瘽鍙互鍦ㄨ繖閲屽仛涓庢暟鎹簱鐩稿叧鐨勫垽鏂�
+ // 渚嬪鍒ゆ柇鐪佷俊鎭槸鍚﹀湪鏁版嵁搴撲腑瀛樺湪绛夛紝寤鸿缁撳悎RedisCache鍋氱紦瀛�10s锛屽噺灏戞暟鎹簱璋冪敤
+ data.setProvinceId(Integer.parseInt(provinceIdStr));
+ }
+ }
+ // 鏈鐢ㄦ埛閫夋嫨鐨勫競
+ List<String> thisRowSelectedCityOption = DropDownOptions.analyzeOptionValue(city);
+ if (thisRowSelectedCityOption.size() == 2) {
+ String cityIdStr = thisRowSelectedCityOption.get(1);
+ if (NumberUtil.isNumber(cityIdStr)) {
+ data.setCityId(Integer.parseInt(cityIdStr));
+ }
+ }
+ // 鏈鐢ㄦ埛閫夋嫨鐨勫幙
+ List<String> thisRowSelectedAreaOption = DropDownOptions.analyzeOptionValue(area);
+ if (thisRowSelectedAreaOption.size() == 2) {
+ String areaIdStr = thisRowSelectedAreaOption.get(1);
+ if (NumberUtil.isNumber(areaIdStr)) {
+ data.setAreaId(Integer.parseInt(areaIdStr));
+ }
+ }
+
+ // 澶勭悊瀹屾瘯浠ュ悗鍒ゆ柇鏄惁绗﹀悎瑙勫垯
+ ValidatorUtils.validate(data, EditGroup.class);
+
+ // 娣诲姞鍒板鐞嗙粨鏋滀腑
+ getExcelResult().getList().add(data);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoEncryptMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoEncryptMapper.java
new file mode 100755
index 0000000..601f97a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoEncryptMapper.java
@@ -0,0 +1,13 @@
+package org.dromara.demo.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.demo.domain.TestDemoEncrypt;
+
+/**
+ * 娴嬭瘯鍔犲瘑鍔熻兘
+ *
+ * @author Lion Li
+ */
+public interface TestDemoEncryptMapper extends BaseMapperPlus<TestDemoEncrypt, TestDemoEncrypt> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoMapper.java
new file mode 100755
index 0000000..19b2d52
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoMapper.java
@@ -0,0 +1,64 @@
+package org.dromara.demo.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+import org.dromara.common.mybatis.annotation.DataColumn;
+import org.dromara.common.mybatis.annotation.DataPermission;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.demo.domain.TestDemo;
+import org.dromara.demo.domain.vo.TestDemoVo;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 娴嬭瘯鍗曡〃Mapper鎺ュ彛
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+public interface TestDemoMapper extends BaseMapperPlus<TestDemo, TestDemoVo> {
+
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "user_id")
+ })
+ Page<TestDemoVo> customPageList(@Param("page") Page<TestDemo> page, @Param("ew") Wrapper<TestDemo> wrapper);
+
+ @Override
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "user_id")
+ })
+ default <P extends IPage<TestDemoVo>> P selectVoPage(IPage<TestDemo> page, Wrapper<TestDemo> wrapper) {
+ return selectVoPage(page, wrapper, this.currentVoClass());
+ }
+
+ @Override
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "user_id")
+ })
+ default List<TestDemoVo> selectVoList(Wrapper<TestDemo> wrapper) {
+ return selectVoList(wrapper, this.currentVoClass());
+ }
+
+ @Override
+ @DataPermission(value = {
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "user_id")
+ }, joinStr = "AND")
+ List<TestDemo> selectByIds(@Param(Constants.COLL) Collection<? extends Serializable> idList);
+
+ @Override
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "user_id")
+ })
+ int updateById(@Param(Constants.ENTITY) TestDemo entity);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestTreeMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestTreeMapper.java
new file mode 100755
index 0000000..e5f4c44
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestTreeMapper.java
@@ -0,0 +1,21 @@
+package org.dromara.demo.mapper;
+
+import org.dromara.common.mybatis.annotation.DataColumn;
+import org.dromara.common.mybatis.annotation.DataPermission;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.demo.domain.TestTree;
+import org.dromara.demo.domain.vo.TestTreeVo;
+
+/**
+ * 娴嬭瘯鏍戣〃Mapper鎺ュ彛
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+@DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "user_id")
+})
+public interface TestTreeMapper extends BaseMapperPlus<TestTree, TestTreeVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/package-info.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/package-info.java
new file mode 100755
index 0000000..ff1c4df
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/package-info.java
@@ -0,0 +1 @@
+package org.dromara.demo.mapper;
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java
new file mode 100755
index 0000000..ad2392b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java
@@ -0,0 +1,27 @@
+package org.dromara.demo.service;
+
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+
+/**
+ * 瀵煎嚭涓嬫媺妗咵xcel绀轰緥
+ *
+ * @author Emil.Zhang
+ */
+public interface IExportExcelService {
+
+ /**
+ * 瀵煎嚭涓嬫媺妗�
+ *
+ * @param response /
+ */
+ void exportWithOptions(HttpServletResponse response);
+
+ /**
+ * 鑷畾涔夊鍑�
+ *
+ * @param response /
+ */
+ void customExport(HttpServletResponse response) throws IOException;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/ITestDemoService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/ITestDemoService.java
new file mode 100755
index 0000000..bca4192
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/ITestDemoService.java
@@ -0,0 +1,71 @@
+package org.dromara.demo.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.demo.domain.TestDemo;
+import org.dromara.demo.domain.bo.TestDemoBo;
+import org.dromara.demo.domain.vo.TestDemoVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 娴嬭瘯鍗曡〃Service鎺ュ彛
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+public interface ITestDemoService {
+
+ /**
+ * 鏌ヨ鍗曚釜
+ *
+ * @return
+ */
+ TestDemoVo queryById(Long id);
+
+ /**
+ * 鏌ヨ鍒楄〃
+ */
+ TableDataInfo<TestDemoVo> queryPageList(TestDemoBo bo, PageQuery pageQuery);
+
+ /**
+ * 鑷畾涔夊垎椤垫煡璇�
+ */
+ TableDataInfo<TestDemoVo> customPageList(TestDemoBo bo, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ鍒楄〃
+ */
+ List<TestDemoVo> queryList(TestDemoBo bo);
+
+ /**
+ * 鏍规嵁鏂板涓氬姟瀵硅薄鎻掑叆娴嬭瘯鍗曡〃
+ *
+ * @param bo 娴嬭瘯鍗曡〃鏂板涓氬姟瀵硅薄
+ * @return
+ */
+ Boolean insertByBo(TestDemoBo bo);
+
+ /**
+ * 鏍规嵁缂栬緫涓氬姟瀵硅薄淇敼娴嬭瘯鍗曡〃
+ *
+ * @param bo 娴嬭瘯鍗曡〃缂栬緫涓氬姟瀵硅薄
+ * @return
+ */
+ Boolean updateByBo(TestDemoBo bo);
+
+ /**
+ * 鏍¢獙骞跺垹闄ゆ暟鎹�
+ *
+ * @param ids 涓婚敭闆嗗悎
+ * @param isValid 鏄惁鏍¢獙,true-鍒犻櫎鍓嶆牎楠�,false-涓嶆牎楠�
+ * @return
+ */
+ Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+ /**
+ * 鎵归噺淇濆瓨
+ */
+ Boolean saveBatch(List<TestDemo> list);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/ITestTreeService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/ITestTreeService.java
new file mode 100755
index 0000000..9155201
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/ITestTreeService.java
@@ -0,0 +1,52 @@
+package org.dromara.demo.service;
+
+import org.dromara.demo.domain.bo.TestTreeBo;
+import org.dromara.demo.domain.vo.TestTreeVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 娴嬭瘯鏍戣〃Service鎺ュ彛
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+public interface ITestTreeService {
+ /**
+ * 鏌ヨ鍗曚釜
+ *
+ * @return
+ */
+ TestTreeVo queryById(Long id);
+
+ /**
+ * 鏌ヨ鍒楄〃
+ */
+ List<TestTreeVo> queryList(TestTreeBo bo);
+
+ /**
+ * 鏍规嵁鏂板涓氬姟瀵硅薄鎻掑叆娴嬭瘯鏍戣〃
+ *
+ * @param bo 娴嬭瘯鏍戣〃鏂板涓氬姟瀵硅薄
+ * @return
+ */
+ Boolean insertByBo(TestTreeBo bo);
+
+ /**
+ * 鏍规嵁缂栬緫涓氬姟瀵硅薄淇敼娴嬭瘯鏍戣〃
+ *
+ * @param bo 娴嬭瘯鏍戣〃缂栬緫涓氬姟瀵硅薄
+ * @return
+ */
+ Boolean updateByBo(TestTreeBo bo);
+
+ /**
+ * 鏍¢獙骞跺垹闄ゆ暟鎹�
+ *
+ * @param ids 涓婚敭闆嗗悎
+ * @param isValid 鏄惁鏍¢獙,true-鍒犻櫎鍓嶆牎楠�,false-涓嶆牎楠�
+ * @return
+ */
+ Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java
new file mode 100755
index 0000000..2813cc2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java
@@ -0,0 +1,297 @@
+package org.dromara.demo.service.impl;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.idev.excel.write.metadata.WriteSheet;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.file.FileUtils;
+import org.dromara.common.excel.core.DropDownOptions;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.excel.utils.ExcelWriterWrapper;
+import org.dromara.demo.domain.vo.ExportDemoVo;
+import org.dromara.demo.service.IExportExcelService;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 瀵煎嚭涓嬫媺妗咵xcel绀轰緥
+ *
+ * @author Emil.Zhang
+ */
+@Service
+@RequiredArgsConstructor
+public class ExportExcelServiceImpl implements IExportExcelService {
+
+ @Override
+ public void exportWithOptions(HttpServletResponse response) {
+ // 鍒涘缓琛ㄦ牸鏁版嵁锛屼笟鍔′腑涓�鑸�氳繃鏁版嵁搴撴煡璇�
+ List<ExportDemoVo> excelDataList = new ArrayList<>();
+ for (int i = 0; i < 3; i++) {
+ // 妯℃嫙鏁版嵁搴撲腑鐨勪竴鏉℃暟鎹�
+ ExportDemoVo everyRowData = new ExportDemoVo();
+ everyRowData.setNickName("鐢ㄦ埛-" + i);
+ everyRowData.setUserStatus(SystemConstants.NORMAL);
+ everyRowData.setGender("1");
+ everyRowData.setPhoneNumber(String.format("175%08d", i));
+ everyRowData.setEmail(String.format("175%08d", i) + "@163.com");
+ everyRowData.setProvinceId(i);
+ everyRowData.setCityId(i);
+ everyRowData.setAreaId(i);
+ excelDataList.add(everyRowData);
+ }
+
+ // 閫氳繃@ExcelIgnoreUnannotated閰嶅悎@ExcelProperty鍚堢悊鏄剧ず闇�瑕佺殑鍒�
+ // 骞堕�氳繃@DropDown娉ㄨВ鎸囧畾涓嬫媺鍊硷紝鎴栬�呴�氳繃鍒涘缓ExcelOptions鏉ユ寚瀹氫笅鎷夋
+ // 浣跨敤ExcelOptions鏃跺缓璁寚瀹氬垪index锛岄槻姝㈠嚭鐜颁笅鎷夊垪瑙f瀽涓嶅榻�
+
+ // 棣栧厛浠庢暟鎹簱涓煡璇笅鎷夋鍐呯殑鍙�夐」
+ // 杩欓噷妯℃嫙鏌ヨ缁撴灉
+ List<DemoCityData> provinceList = getProvinceList(),
+ cityList = getCityList(provinceList),
+ areaList = getAreaList(cityList);
+ int provinceIndex = 5, cityIndex = 6, areaIndex = 7;
+
+ DropDownOptions provinceToCity = DropDownOptions.buildLinkedOptions(
+ provinceList,
+ provinceIndex,
+ cityList,
+ cityIndex,
+ DemoCityData::getId,
+ DemoCityData::getPid,
+ everyOptions -> DropDownOptions.createOptionValue(
+ everyOptions.getName(),
+ everyOptions.getId()
+ )
+ );
+
+ DropDownOptions cityToArea = DropDownOptions.buildLinkedOptions(
+ cityList,
+ cityIndex,
+ areaList,
+ areaIndex,
+ DemoCityData::getId,
+ DemoCityData::getPid,
+ everyOptions -> DropDownOptions.createOptionValue(
+ everyOptions.getName(),
+ everyOptions.getId()
+ )
+ );
+
+ // 鎶婃墍鏈夌殑涓嬫媺妗嗗瓨鍌�
+ List<DropDownOptions> options = new ArrayList<>();
+ options.add(provinceToCity);
+ options.add(cityToArea);
+
+ // 鍒版涓烘鎵�鏈夌殑涓嬫媺妗嗗彲閫夐」宸插叏閮ㄩ厤缃畬姣�
+
+ // 鎺ヤ笅鏉ラ渶瑕佸皢Excel涓殑灞曠ず鏁版嵁杞崲涓哄搴旂殑涓嬫媺閫�
+ List<ExportDemoVo> outList = StreamUtils.toList(excelDataList, everyRowData -> {
+ // 鍙渶瑕佸鐞嗘病鏈変娇鐢ˊExcelDictFormat娉ㄨВ鐨勪笅鎷夋
+ // 涓�鑸潵璇达紝鍙互鐩存帴鍦ㄦ暟鎹簱鏌ヨ鍗虫煡璇㈠嚭鐪佸競鍘夸俊鎭紝杩欓噷閫氳繃妯℃嫙鎿嶄綔璧嬪��
+ everyRowData.setProvince(buildOptions(provinceList, everyRowData.getProvinceId()));
+ everyRowData.setCity(buildOptions(cityList, everyRowData.getCityId()));
+ everyRowData.setArea(buildOptions(areaList, everyRowData.getAreaId()));
+ return everyRowData;
+ });
+
+ ExcelUtil.exportExcel(outList, "涓嬫媺妗嗙ず渚�", ExportDemoVo.class, response, options);
+ }
+
+ private String buildOptions(List<DemoCityData> cityDataList, Integer id) {
+ Map<Integer, List<DemoCityData>> groupByIdMap =
+ cityDataList.stream().collect(Collectors.groupingBy(DemoCityData::getId));
+ if (groupByIdMap.containsKey(id)) {
+ DemoCityData demoCityData = groupByIdMap.get(id).get(0);
+ return DropDownOptions.createOptionValue(demoCityData.getName(), demoCityData.getId());
+ } else {
+ return StrUtil.EMPTY;
+ }
+ }
+
+ /**
+ * 妯℃嫙鏌ヨ鏁版嵁搴撴搷浣�
+ *
+ * @return /
+ */
+ private List<DemoCityData> getProvinceList() {
+ List<DemoCityData> provinceList = new ArrayList<>();
+
+ // 瀹為檯涓氬姟涓竴鑸噰鐢ㄦ暟鎹簱璇诲彇鐨勫舰寮忥紝杩欓噷鐩存帴鎷兼帴鍒涘缓
+ provinceList.add(new DemoCityData(0, null, "P100000"));
+ provinceList.add(new DemoCityData(1, null, "P200000"));
+ provinceList.add(new DemoCityData(2, null, "P300000"));
+
+ return provinceList;
+ }
+
+ /**
+ * 妯℃嫙鏌ユ壘鏁版嵁搴撴搷浣滐紝闇�瑕佽繛甯︽煡璇㈠嚭鐪佺殑鏁版嵁
+ *
+ * @param provinceList 妯℃嫙鐨勭埗鐪佹暟鎹�
+ * @return /
+ */
+ private List<DemoCityData> getCityList(List<DemoCityData> provinceList) {
+ List<DemoCityData> cityList = new ArrayList<>();
+
+ // 瀹為檯涓氬姟涓竴鑸噰鐢ㄦ暟鎹簱璇诲彇鐨勫舰寮忥紝杩欓噷鐩存帴鎷兼帴鍒涘缓
+ cityList.add(new DemoCityData(0, 0, "C110000"));
+ cityList.add(new DemoCityData(1, 0, "C120000"));
+ cityList.add(new DemoCityData(2, 1, "C210000"));
+ cityList.add(new DemoCityData(3, 1, "C220000"));
+ cityList.add(new DemoCityData(4, 1, "C230000"));
+
+ selectParentData(provinceList, cityList);
+
+ return cityList;
+ }
+
+ /**
+ * 妯℃嫙鏌ユ壘鏁版嵁搴撴搷浣滐紝闇�瑕佽繛甯︽煡璇㈠嚭甯傜殑鏁版嵁
+ *
+ * @param cityList 妯℃嫙鐨勭埗甯傛暟鎹�
+ * @return /
+ */
+ private List<DemoCityData> getAreaList(List<DemoCityData> cityList) {
+ List<DemoCityData> areaList = new ArrayList<>();
+
+ int minCount = 500;
+ int maxCount = 10000;
+
+ // 瀹為檯涓氬姟涓竴鑸噰鐢ㄦ暟鎹簱璇诲彇鐨勫舰寮忥紝杩欓噷鐩存帴鎷兼帴鍒涘缓
+ for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
+ areaList.add(new DemoCityData(areaList.size(), 0, String.format("A11%04d", i)));
+ }
+
+ for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
+ areaList.add(new DemoCityData(areaList.size(), 1, String.format("A12%04d", i)));
+ }
+
+ for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
+ areaList.add(new DemoCityData(areaList.size(), 2, String.format("A21%04d", i)));
+ }
+
+ for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
+ areaList.add(new DemoCityData(areaList.size(), 3, String.format("A22%04d", i)));
+ }
+
+ for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
+ areaList.add(new DemoCityData(areaList.size(), 4, String.format("A23%04d", i)));
+ }
+
+ selectParentData(cityList, areaList);
+
+ return areaList;
+ }
+
+ /**
+ * 妯℃嫙鏁版嵁搴撶殑鏌ヨ鐖舵暟鎹搷浣�
+ *
+ * @param parentList /
+ * @param sonList /
+ */
+ private void selectParentData(List<DemoCityData> parentList, List<DemoCityData> sonList) {
+ Map<Integer, List<DemoCityData>> parentGroupByIdMap =
+ parentList.stream().collect(Collectors.groupingBy(DemoCityData::getId));
+
+ sonList.forEach(everySon -> {
+ if (parentGroupByIdMap.containsKey(everySon.getPid())) {
+ everySon.setPData(parentGroupByIdMap.get(everySon.getPid()).get(0));
+ }
+ });
+ }
+
+ /**
+ * 妯℃嫙鐨勬暟鎹簱鐪佸競鍘�
+ */
+ @Data
+ private static class DemoCityData {
+ /**
+ * 鏁版嵁搴搃d瀛楁
+ */
+ private Integer id;
+ /**
+ * 鏁版嵁搴損id瀛楁
+ */
+ private Integer pid;
+ /**
+ * 鏁版嵁搴搉ame瀛楁
+ */
+ private String name;
+ /**
+ * MyBatisPlus杩炲甫鏌ヨ鐖舵暟鎹�
+ */
+ private DemoCityData pData;
+
+ public DemoCityData(Integer id, Integer pid, String name) {
+ this.id = id;
+ this.pid = pid;
+ this.name = name;
+ }
+ }
+
+
+ @Override
+ public void customExport(HttpServletResponse response) throws IOException {
+ String filename = ExcelUtil.encodingFilename("鑷畾涔夊鍑�");
+ FileUtils.setAttachmentResponseHeader(response, filename);
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
+
+ ExcelUtil.exportExcel(ExportDemoVo.class, response.getOutputStream(), wrapper -> {
+ // 鍒涘缓琛ㄦ牸鏁版嵁锛屼笟鍔′腑涓�鑸�氳繃鏁版嵁搴撴煡璇�
+ List<ExportDemoVo> excelDataList = new ArrayList<>();
+ for (int i = 0; i < 30; i++) {
+ // 妯℃嫙鏁版嵁搴撲腑鐨勪竴鏉℃暟鎹�
+ ExportDemoVo everyRowData = new ExportDemoVo();
+ everyRowData.setNickName("鐢ㄦ埛-" + i);
+ everyRowData.setUserStatus(SystemConstants.NORMAL);
+ everyRowData.setGender("1");
+ everyRowData.setPhoneNumber(String.format("175%08d", i));
+ everyRowData.setEmail(String.format("175%08d", i) + "@163.com");
+ everyRowData.setProvinceId(i);
+ everyRowData.setCityId(i);
+ everyRowData.setAreaId(i);
+ excelDataList.add(everyRowData);
+ }
+
+ // 鍒涘缓琛ㄦ牸
+ WriteSheet sheet = ExcelWriterWrapper.sheetBuilder("鑷畾涔夊鍑篸emo")
+ // 鍚堝苟鍗曞厓鏍�
+ // .registerWriteHandler(new CellMergeStrategy(excelDataList, true))
+ .build();
+
+
+ wrapper.write(excelDataList, sheet);
+
+ List<ExportDemoVo> excelDataList2 = new ArrayList<>();
+ for (int i = 0; i < 20; i++) {
+ int index = 1000 + i;
+ // 妯℃嫙鏁版嵁搴撲腑鐨勪竴鏉℃暟鎹�
+ ExportDemoVo everyRowData = new ExportDemoVo();
+ everyRowData.setNickName("鐢ㄦ埛-" + index);
+ everyRowData.setUserStatus(SystemConstants.NORMAL);
+ everyRowData.setGender("1");
+ everyRowData.setPhoneNumber(String.format("175%08d", index));
+ everyRowData.setEmail(String.format("175%08d", index) + "@163.com");
+ everyRowData.setProvinceId(index);
+ everyRowData.setCityId(index);
+ everyRowData.setAreaId(index);
+ excelDataList2.add(everyRowData);
+ }
+
+ wrapper.write(excelDataList2, sheet);
+
+ // 鎴栬�呭湪鍚屼竴涓猠xcel涓垱寤哄涓〃鏍�
+ // WriteSheet sheet2 = ExcelWriterWrapper.sheetBuilder("鑷畾涔夊鍑篸emo2").build();
+ // wrapper.write(excelDataList2, sheet2);
+ });
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestDemoServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestDemoServiceImpl.java
new file mode 100755
index 0000000..415e440
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestDemoServiceImpl.java
@@ -0,0 +1,118 @@
+package org.dromara.demo.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.demo.domain.TestDemo;
+import org.dromara.demo.domain.bo.TestDemoBo;
+import org.dromara.demo.domain.vo.TestDemoVo;
+import org.dromara.demo.mapper.TestDemoMapper;
+import org.dromara.demo.service.ITestDemoService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 娴嬭瘯鍗曡〃Service涓氬姟灞傚鐞�
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+@RequiredArgsConstructor
+@Service
+public class TestDemoServiceImpl implements ITestDemoService {
+
+ private final TestDemoMapper baseMapper;
+
+ @Override
+ public TestDemoVo queryById(Long id) {
+ return baseMapper.selectVoById(id);
+ }
+
+ @Override
+ public TableDataInfo<TestDemoVo> queryPageList(TestDemoBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper<TestDemo> lqw = buildQueryWrapper(bo);
+ Page<TestDemoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 鑷畾涔夊垎椤垫煡璇�
+ */
+ @Override
+ public TableDataInfo<TestDemoVo> customPageList(TestDemoBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper<TestDemo> lqw = buildQueryWrapper(bo);
+ Page<TestDemoVo> result = baseMapper.customPageList(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ @Override
+ public List<TestDemoVo> queryList(TestDemoBo bo) {
+ return baseMapper.selectVoList(buildQueryWrapper(bo));
+ }
+
+ private LambdaQueryWrapper<TestDemo> buildQueryWrapper(TestDemoBo bo) {
+ Map<String, Object> params = bo.getParams();
+ LambdaQueryWrapper<TestDemo> lqw = Wrappers.lambdaQuery();
+ lqw.eq(bo.getDeptId() != null, TestDemo::getDeptId, bo.getDeptId());
+ lqw.eq(bo.getUserId() != null, TestDemo::getUserId, bo.getUserId());
+ lqw.like(StringUtils.isNotBlank(bo.getTestKey()), TestDemo::getTestKey, bo.getTestKey());
+ lqw.eq(StringUtils.isNotBlank(bo.getValue()), TestDemo::getValue, bo.getValue());
+ lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
+ TestDemo::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
+ lqw.orderByAsc(TestDemo::getId);
+ return lqw;
+ }
+
+ @Override
+ public Boolean insertByBo(TestDemoBo bo) {
+ TestDemo add = MapstructUtils.convert(bo, TestDemo.class);
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setId(add.getId());
+ }
+ return flag;
+ }
+
+ @Override
+ public Boolean updateByBo(TestDemoBo bo) {
+ TestDemo update = MapstructUtils.convert(bo, TestDemo.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+ *
+ * @param entity 瀹炰綋绫绘暟鎹�
+ */
+ private void validEntityBeforeSave(TestDemo entity) {
+ //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+ }
+
+ @Override
+ public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+ if (isValid) {
+ // 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+ List<TestDemo> list = baseMapper.selectByIds(ids);
+ if (list.size() != ids.size()) {
+ throw new ServiceException("鎮ㄦ病鏈夊垹闄ゆ潈闄�!");
+ }
+ }
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+
+ @Override
+ public Boolean saveBatch(List<TestDemo> list) {
+ return baseMapper.insertBatch(list);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestTreeServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestTreeServiceImpl.java
new file mode 100755
index 0000000..836a090
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestTreeServiceImpl.java
@@ -0,0 +1,90 @@
+package org.dromara.demo.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.demo.domain.TestTree;
+import org.dromara.demo.domain.bo.TestTreeBo;
+import org.dromara.demo.domain.vo.TestTreeVo;
+import org.dromara.demo.mapper.TestTreeMapper;
+import org.dromara.demo.service.ITestTreeService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 娴嬭瘯鏍戣〃Service涓氬姟灞傚鐞�
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+// @DS("slave") // 鍒囨崲浠庡簱鏌ヨ
+@RequiredArgsConstructor
+@Service
+public class TestTreeServiceImpl implements ITestTreeService {
+
+ private final TestTreeMapper baseMapper;
+
+ @Override
+ public TestTreeVo queryById(Long id) {
+ return baseMapper.selectVoById(id);
+ }
+
+ // @DS("slave") // 鍒囨崲浠庡簱鏌ヨ
+ @Override
+ public List<TestTreeVo> queryList(TestTreeBo bo) {
+ LambdaQueryWrapper<TestTree> lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper<TestTree> buildQueryWrapper(TestTreeBo bo) {
+ Map<String, Object> params = bo.getParams();
+ LambdaQueryWrapper<TestTree> lqw = Wrappers.lambdaQuery();
+ lqw.eq(bo.getDeptId() != null, TestTree::getDeptId, bo.getDeptId());
+ lqw.eq(bo.getUserId() != null, TestTree::getUserId, bo.getUserId());
+ lqw.like(StringUtils.isNotBlank(bo.getTreeName()), TestTree::getTreeName, bo.getTreeName());
+ lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
+ TestTree::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
+ lqw.orderByAsc(TestTree::getId);
+ return lqw;
+ }
+
+ @Override
+ public Boolean insertByBo(TestTreeBo bo) {
+ TestTree add = MapstructUtils.convert(bo, TestTree.class);
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setId(add.getId());
+ }
+ return flag;
+ }
+
+ @Override
+ public Boolean updateByBo(TestTreeBo bo) {
+ TestTree update = MapstructUtils.convert(bo, TestTree.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+ *
+ * @param entity 瀹炰綋绫绘暟鎹�
+ */
+ private void validEntityBeforeSave(TestTree entity) {
+ //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+ }
+
+ @Override
+ public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+ if (isValid) {
+ //TODO 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+ }
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/package-info.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/package-info.java
new file mode 100755
index 0000000..7011984
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/package-info.java
@@ -0,0 +1 @@
+package org.dromara.demo.service.impl;
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/package-info.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/package-info.java
new file mode 100755
index 0000000..16727ff
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/package-info.java
@@ -0,0 +1 @@
+package org.dromara.demo.service;
diff --git "a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/excel/\345\215\225\345\210\227\350\241\250.xlsx" "b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/excel/\345\215\225\345\210\227\350\241\250.xlsx"
new file mode 100755
index 0000000..0f7347d
--- /dev/null
+++ "b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/excel/\345\215\225\345\210\227\350\241\250.xlsx"
Binary files differ
diff --git "a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/excel/\345\244\232sheet\345\210\227\350\241\250.xlsx" "b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/excel/\345\244\232sheet\345\210\227\350\241\250.xlsx"
new file mode 100755
index 0000000..5277f2e
--- /dev/null
+++ "b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/excel/\345\244\232sheet\345\210\227\350\241\250.xlsx"
Binary files differ
diff --git "a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/excel/\345\244\232\345\210\227\350\241\250.xlsx" "b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/excel/\345\244\232\345\210\227\350\241\250.xlsx"
new file mode 100755
index 0000000..c7d11dc
--- /dev/null
+++ "b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/excel/\345\244\232\345\210\227\350\241\250.xlsx"
Binary files differ
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestDemoMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestDemoMapper.xml
new file mode 100755
index 0000000..dbf89a3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestDemoMapper.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.demo.mapper.TestDemoMapper">
+
+ <select id="customPageList" resultType="org.dromara.demo.domain.vo.TestDemoVo">
+ SELECT * FROM test_demo ${ew.customSqlSegment}
+ </select>
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestTreeMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestTreeMapper.xml
new file mode 100755
index 0000000..d7975ec
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestTreeMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.demo.mapper.TestTreeMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/package-info.md b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/package-info.md
new file mode 100755
index 0000000..c938b1e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/package-info.md
@@ -0,0 +1,3 @@
+java鍖呬娇鐢� `.` 鍒嗗壊 resource 鐩綍浣跨敤 `/` 鍒嗗壊
+<br>
+姝ゆ枃浠剁洰鐨� 闃叉鏂囦欢澶圭矘杩炴壘涓嶅埌 `xml` 鏂囦欢
\ No newline at end of file
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/.flattened-pom.xml
new file mode 100644
index 0000000..c70c98d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/.flattened-pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-modules</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-generator</artifactId>
+ <version>5.5.3</version>
+ <description>generator 浠g爜鐢熸垚</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-log</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-idempotent</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.anyline</groupId>
+ <artifactId>anyline-environment-spring-data-jdbc</artifactId>
+ <version>${anyline.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.anyline</groupId>
+ <artifactId>anyline-data-jdbc-mysql</artifactId>
+ <version>${anyline.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.anyline</groupId>
+ <artifactId>anyline-data-jdbc-postgresql</artifactId>
+ <version>${anyline.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/pom.xml
new file mode 100755
index 0000000..fa5dc25
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/pom.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-modules</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-generator</artifactId>
+
+ <description>
+ generator 浠g爜鐢熸垚
+ </description>
+
+ <dependencies>
+ <!-- 閫氱敤宸ュ叿-->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-web</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-log</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-idempotent</artifactId>
+ </dependency>
+
+ <!--velocity浠g爜鐢熸垚浣跨敤妯℃澘 -->
+ <dependency>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.anyline</groupId>
+ <artifactId>anyline-environment-spring-data-jdbc</artifactId>
+ <version>${anyline.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.anyline</groupId>
+ <artifactId>anyline-data-jdbc-mysql</artifactId>
+ <version>${anyline.version}</version>
+ </dependency>
+
+ <!-- anyline鏀寔100+绉嶇被鍨嬫暟鎹簱 娣诲姞瀵瑰簲鐨刯dbc渚濊禆涓巃nyline瀵瑰簲鏁版嵁搴撲緷璧栧寘鍗冲彲 -->
+<!-- <dependency>-->
+<!-- <groupId>org.anyline</groupId>-->
+<!-- <artifactId>anyline-data-jdbc-oracle</artifactId>-->
+<!-- <version>${anyline.version}</version>-->
+<!-- </dependency>-->
+
+ <dependency>
+ <groupId>org.anyline</groupId>
+ <artifactId>anyline-data-jdbc-postgresql</artifactId>
+ <version>${anyline.version}</version>
+ </dependency>
+
+<!-- <dependency>-->
+<!-- <groupId>org.anyline</groupId>-->
+<!-- <artifactId>anyline-data-jdbc-mssql</artifactId>-->
+<!-- <version>${anyline.version}</version>-->
+<!-- </dependency>-->
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/config/GenConfig.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/config/GenConfig.java
new file mode 100755
index 0000000..b29f8c9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/config/GenConfig.java
@@ -0,0 +1,73 @@
+package org.dromara.generator.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 璇诲彇浠g爜鐢熸垚鐩稿叧閰嶇疆
+ *
+ * @author ruoyi
+ */
+@Component
+@ConfigurationProperties(prefix = "gen")
+@PropertySource(value = {"classpath:generator.yml"}, encoding = "UTF-8")
+public class GenConfig {
+
+ /**
+ * 浣滆��
+ */
+ public static String author;
+
+ /**
+ * 鐢熸垚鍖呰矾寰�
+ */
+ public static String packageName;
+
+ /**
+ * 鑷姩鍘婚櫎琛ㄥ墠缂�锛岄粯璁ゆ槸false
+ */
+ public static boolean autoRemovePre;
+
+ /**
+ * 琛ㄥ墠缂�(绫诲悕涓嶄細鍖呭惈琛ㄥ墠缂�)
+ */
+ public static String tablePrefix;
+
+ public static String getAuthor() {
+ return author;
+ }
+
+ @Value("${author}")
+ public void setAuthor(String author) {
+ GenConfig.author = author;
+ }
+
+ public static String getPackageName() {
+ return packageName;
+ }
+
+ @Value("${packageName}")
+ public void setPackageName(String packageName) {
+ GenConfig.packageName = packageName;
+ }
+
+ public static boolean getAutoRemovePre() {
+ return autoRemovePre;
+ }
+
+ @Value("${autoRemovePre}")
+ public void setAutoRemovePre(boolean autoRemovePre) {
+ GenConfig.autoRemovePre = autoRemovePre;
+ }
+
+ public static String getTablePrefix() {
+ return tablePrefix;
+ }
+
+ @Value("${tablePrefix}")
+ public void setTablePrefix(String tablePrefix) {
+ GenConfig.tablePrefix = tablePrefix;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/config/MyBatisDataSourceMonitor.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/config/MyBatisDataSourceMonitor.java
new file mode 100755
index 0000000..8c0f352
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/config/MyBatisDataSourceMonitor.java
@@ -0,0 +1,105 @@
+package org.dromara.generator.config;
+
+import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
+import lombok.extern.slf4j.Slf4j;
+import org.anyline.data.datasource.DataSourceMonitor;
+import org.anyline.data.runtime.DataRuntime;
+import org.anyline.util.ConfigTable;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.stereotype.Component;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * anyline 閫傞厤 鍔ㄦ�佹暟鎹簮鏀归��
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Component
+public class MyBatisDataSourceMonitor implements DataSourceMonitor {
+
+ public MyBatisDataSourceMonitor() {
+ // 璋冩暣鎵ц妯″紡涓鸿嚜瀹氫箟
+ ConfigTable.KEEP_ADAPTER = 2;
+ // 绂佺敤缂撳瓨
+ ConfigTable.METADATA_CACHE_SCOPE = 0;
+ }
+
+ private final Map<String, String> features = new HashMap<>();
+
+ /**
+ * 鏁版嵁婧愮壒寰� 鐢ㄦ潵瀹氬噯 adapter 鍖呭惈鏁版嵁搴撴垨JDBC鍗忚鍏抽敭瀛�<br/>
+ * 涓�鑸細閫氳繃 浜у搧鍚峗url 鍚堟垚 濡傛灉杩斿洖null 涓婂眰鏂规硶浼氶�氳繃driver_浜у搧鍚峗url鍚堟垚
+ *
+ * @param datasource 鏁版嵁婧�
+ * @return String 杩斿洖null鐢变笂灞傝嚜鍔ㄦ彁鍙�
+ */
+ @Override
+ public String feature(DataRuntime runtime, Object datasource) {
+ String feature = null;
+ if (datasource instanceof JdbcTemplate jdbc) {
+ DataSource ds = jdbc.getDataSource();
+ if (ds instanceof DynamicRoutingDataSource) {
+ String key = DynamicDataSourceContextHolder.peek();
+ feature = features.get(key);
+ if (null == feature) {
+ Connection con = null;
+ try {
+ con = DataSourceUtils.getConnection(ds);
+ DatabaseMetaData meta = con.getMetaData();
+ String url = meta.getURL();
+ feature = meta.getDatabaseProductName().toLowerCase().replace(" ", "") + "_" + url;
+ features.put(key, feature);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ } finally {
+ if (null != con && !DataSourceUtils.isConnectionTransactional(con, ds)) {
+ DataSourceUtils.releaseConnection(con, ds);
+ }
+ }
+ }
+ }
+ }
+ return feature;
+ }
+
+ /**
+ * 鏁版嵁婧愬敮涓�鏍囪瘑 濡傛灉涓嶅疄鐜板垯榛樿feature
+ * @param datasource 鏁版嵁婧�
+ * @return String 杩斿洖null鐢变笂灞傝嚜鍔ㄦ彁鍙�
+ */
+ @Override
+ public String key(DataRuntime runtime, Object datasource) {
+ if(datasource instanceof JdbcTemplate jdbc){
+ DataSource ds = jdbc.getDataSource();
+ if(ds instanceof DynamicRoutingDataSource){
+ return DynamicDataSourceContextHolder.peek();
+ }
+ }
+ return runtime.getKey();
+ }
+
+ /**
+ * ConfigTable.KEEP_ADAPTER=2 : 鏍规嵁褰撳墠鎺ュ彛鍒ゆ柇鏄惁淇濇寔鍚屼竴涓暟鎹簮缁戝畾鍚屼竴涓猘dapter<br/>
+ * DynamicRoutingDataSource绫诲瀷鐨勮繑鍥瀎alse,鍥犱负鍚屼竴涓狣ynamicRoutingDataSource鍙兘瀵瑰簲澶氱被鏁版嵁搴�, 濡傛灉椤圭洰涓彧鏈変竴绉嶆暟鎹簱 搴旇鐩存帴杩斿洖true
+ *
+ * @param datasource 鏁版嵁婧�
+ * @return boolean
+ */
+ @Override
+ public boolean keepAdapter(DataRuntime runtime, Object datasource) {
+ if (datasource instanceof JdbcTemplate jdbc) {
+ DataSource ds = jdbc.getDataSource();
+ return !(ds instanceof DynamicRoutingDataSource);
+ }
+ return true;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/constant/GenConstants.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/constant/GenConstants.java
new file mode 100755
index 0000000..b9888fb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/constant/GenConstants.java
@@ -0,0 +1,186 @@
+package org.dromara.generator.constant;
+
+/**
+ * 浠g爜鐢熸垚閫氱敤甯搁噺
+ *
+ * @author ruoyi
+ */
+public interface GenConstants {
+ /**
+ * 鍗曡〃锛堝鍒犳敼鏌ワ級
+ */
+ String TPL_CRUD = "crud";
+
+ /**
+ * 鏍戣〃锛堝鍒犳敼鏌ワ級
+ */
+ String TPL_TREE = "tree";
+
+ /**
+ * 鏍戠紪鐮佸瓧娈�
+ */
+ String TREE_CODE = "treeCode";
+
+ /**
+ * 鏍戠埗缂栫爜瀛楁
+ */
+ String TREE_PARENT_CODE = "treeParentCode";
+
+ /**
+ * 鏍戝悕绉板瓧娈�
+ */
+ String TREE_NAME = "treeName";
+
+ /**
+ * 涓婄骇鑿滃崟ID瀛楁
+ */
+ String PARENT_MENU_ID = "parentMenuId";
+
+ /**
+ * 涓婄骇鑿滃崟鍚嶇О瀛楁
+ */
+ String PARENT_MENU_NAME = "parentMenuName";
+
+ /**
+ * 鏁版嵁搴撳瓧绗︿覆绫诲瀷
+ */
+ String[] COLUMNTYPE_STR = {"char", "varchar", "enum", "set", "nchar", "nvarchar", "varchar2", "nvarchar2"};
+
+ /**
+ * 鏁版嵁搴撴枃鏈被鍨�
+ */
+ String[] COLUMNTYPE_TEXT = {"tinytext", "text", "mediumtext", "longtext", "binary", "varbinary", "blob",
+ "ntext", "image", "bytea"};
+
+ /**
+ * 鏁版嵁搴撴椂闂寸被鍨�
+ */
+ String[] COLUMNTYPE_TIME = {"datetime", "time", "date", "timestamp", "year", "interval",
+ "smalldatetime", "datetime2", "datetimeoffset", "timestamptz"};
+
+ /**
+ * 鏁版嵁搴撴暟瀛楃被鍨�
+ */
+ String[] COLUMNTYPE_NUMBER = {"tinyint", "smallint", "mediumint", "int", "int2", "int4", "int8", "number", "integer",
+ "bit", "bigint", "float", "float4", "float8", "double", "decimal", "numeric", "real", "double precision",
+ "smallserial", "serial", "bigserial", "money", "smallmoney"};
+
+ /**
+ * BO瀵硅薄 涓嶉渶瑕佹坊鍔犲瓧娈�
+ */
+ String[] COLUMNNAME_NOT_ADD = {"create_dept", "create_by", "create_time", "del_flag", "update_by",
+ "update_time", "version", "tenant_id"};
+
+ /**
+ * BO瀵硅薄 涓嶉渶瑕佺紪杈戝瓧娈�
+ */
+ String[] COLUMNNAME_NOT_EDIT = {"create_dept", "create_by", "create_time", "del_flag", "update_by",
+ "update_time", "version", "tenant_id"};
+
+ /**
+ * VO瀵硅薄 涓嶉渶瑕佽繑鍥炲瓧娈�
+ */
+ String[] COLUMNNAME_NOT_LIST = {"create_dept", "create_by", "create_time", "del_flag", "update_by",
+ "update_time", "version", "tenant_id"};
+
+ /**
+ * BO瀵硅薄 涓嶉渶瑕佹煡璇㈠瓧娈�
+ */
+ String[] COLUMNNAME_NOT_QUERY = {"id", "create_dept", "create_by", "create_time", "del_flag", "update_by",
+ "update_time", "remark", "version", "tenant_id"};
+
+ /**
+ * Entity鍩虹被瀛楁
+ */
+ String[] BASE_ENTITY = {"createDept", "createBy", "createTime", "updateBy", "updateTime", "tenantId"};
+
+ /**
+ * 鏂囨湰妗�
+ */
+ String HTML_INPUT = "input";
+
+ /**
+ * 鏂囨湰鍩�
+ */
+ String HTML_TEXTAREA = "textarea";
+
+ /**
+ * 涓嬫媺妗�
+ */
+ String HTML_SELECT = "select";
+
+ /**
+ * 鍗曢�夋
+ */
+ String HTML_RADIO = "radio";
+
+ /**
+ * 澶嶉�夋
+ */
+ String HTML_CHECKBOX = "checkbox";
+
+ /**
+ * 鏃ユ湡鎺т欢
+ */
+ String HTML_DATETIME = "datetime";
+
+ /**
+ * 鍥剧墖涓婁紶鎺т欢
+ */
+ String HTML_IMAGE_UPLOAD = "imageUpload";
+
+ /**
+ * 鏂囦欢涓婁紶鎺т欢
+ */
+ String HTML_FILE_UPLOAD = "fileUpload";
+
+ /**
+ * 瀵屾枃鏈帶浠�
+ */
+ String HTML_EDITOR = "editor";
+
+ /**
+ * 瀛楃涓茬被鍨�
+ */
+ String TYPE_STRING = "String";
+
+ /**
+ * 鏁村瀷
+ */
+ String TYPE_INTEGER = "Integer";
+
+ /**
+ * 闀挎暣鍨�
+ */
+ String TYPE_LONG = "Long";
+
+ /**
+ * 娴偣鍨�
+ */
+ String TYPE_DOUBLE = "Double";
+
+ /**
+ * 楂樼簿搴﹁绠楃被鍨�
+ */
+ String TYPE_BIGDECIMAL = "BigDecimal";
+
+ /**
+ * 鏃堕棿绫诲瀷
+ */
+ String TYPE_DATE = "Date";
+
+ /**
+ * 妯$硦鏌ヨ
+ */
+ String QUERY_LIKE = "LIKE";
+
+ /**
+ * 鐩哥瓑鏌ヨ
+ */
+ String QUERY_EQ = "EQ";
+
+ /**
+ * 闇�瑕�
+ */
+ String REQUIRE = "1";
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/controller/GenController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/controller/GenController.java
new file mode 100755
index 0000000..802c43c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/controller/GenController.java
@@ -0,0 +1,222 @@
+package org.dromara.generator.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.io.IoUtil;
+import com.baomidou.lock.annotation.Lock4j;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.helper.DataBaseHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.generator.domain.GenTable;
+import org.dromara.generator.domain.GenTableColumn;
+import org.dromara.generator.service.IGenTableService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 浠g爜鐢熸垚 鎿嶄綔澶勭悊
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/tool/gen")
+public class GenController extends BaseController {
+
+ private final IGenTableService genTableService;
+
+ /**
+ * 鏌ヨ浠g爜鐢熸垚鍒楄〃
+ */
+ @SaCheckPermission("tool:gen:list")
+ @GetMapping("/list")
+ public TableDataInfo<GenTable> genList(GenTable genTable, PageQuery pageQuery) {
+ return genTableService.selectPageGenTableList(genTable, pageQuery);
+ }
+
+ /**
+ * 淇敼浠g爜鐢熸垚涓氬姟
+ *
+ * @param tableId 琛↖D
+ */
+ @RepeatSubmit()
+ @SaCheckPermission("tool:gen:query")
+ @GetMapping(value = "/{tableId}")
+ public R<Map<String, Object>> getInfo(@PathVariable Long tableId) {
+ GenTable table = genTableService.selectGenTableById(tableId);
+ List<GenTable> tables = genTableService.selectGenTableAll();
+ List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
+ Map<String, Object> map = new HashMap<>(3);
+ map.put("info", table);
+ map.put("rows", list);
+ map.put("tables", tables);
+ return R.ok(map);
+ }
+
+ /**
+ * 鏌ヨ鏁版嵁搴撳垪琛�
+ */
+ @SaCheckPermission("tool:gen:list")
+ @GetMapping("/db/list")
+ public TableDataInfo<GenTable> dataList(GenTable genTable, PageQuery pageQuery) {
+ return genTableService.selectPageDbTableList(genTable, pageQuery);
+ }
+
+ /**
+ * 鏌ヨ鏁版嵁琛ㄥ瓧娈靛垪琛�
+ *
+ * @param tableId 琛↖D
+ */
+ @SaCheckPermission("tool:gen:list")
+ @GetMapping(value = "/column/{tableId}")
+ public TableDataInfo<GenTableColumn> columnList(@PathVariable("tableId") Long tableId) {
+ List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
+ return TableDataInfo.build(list);
+ }
+
+ /**
+ * 瀵煎叆琛ㄧ粨鏋勶紙淇濆瓨锛�
+ *
+ * @param tables 琛ㄥ悕涓�
+ * @param dataName 鏁版嵁婧愬悕绉�
+ */
+ @SaCheckPermission("tool:gen:import")
+ @Log(title = "浠g爜鐢熸垚", businessType = BusinessType.IMPORT)
+ @Lock4j(keys = {"#dataName"}, acquireTimeout = 10000)
+ @RepeatSubmit()
+ @PostMapping("/importTable")
+ public R<Void> importTableSave(String tables, String dataName) {
+ String[] tableNames = Convert.toStrArray(tables);
+ // 鏌ヨ琛ㄤ俊鎭�
+ List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames, dataName);
+ genTableService.importGenTable(tableList, dataName);
+ return R.ok();
+ }
+
+ /**
+ * 淇敼淇濆瓨浠g爜鐢熸垚涓氬姟
+ */
+ @SaCheckPermission("tool:gen:edit")
+ @Log(title = "浠g爜鐢熸垚", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping
+ public R<Void> editSave(@Validated @RequestBody GenTable genTable) {
+ genTableService.validateEdit(genTable);
+ genTableService.updateGenTable(genTable);
+ return R.ok();
+ }
+
+ /**
+ * 鍒犻櫎浠g爜鐢熸垚
+ *
+ * @param tableIds 琛↖D涓�
+ */
+ @SaCheckPermission("tool:gen:remove")
+ @Log(title = "浠g爜鐢熸垚", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{tableIds}")
+ public R<Void> remove(@PathVariable Long[] tableIds) {
+ genTableService.deleteGenTableByIds(tableIds);
+ return R.ok();
+ }
+
+ /**
+ * 棰勮浠g爜
+ *
+ * @param tableId 琛↖D
+ */
+ @SaCheckPermission("tool:gen:preview")
+ @GetMapping("/preview/{tableId}")
+ public R<Map<String, String>> preview(@PathVariable("tableId") Long tableId) throws IOException {
+ Map<String, String> dataMap = genTableService.previewCode(tableId);
+ return R.ok(dataMap);
+ }
+
+ /**
+ * 鐢熸垚浠g爜锛堜笅杞芥柟寮忥級
+ *
+ * @param tableId 琛↖D
+ */
+ @SaCheckPermission("tool:gen:code")
+ @Log(title = "浠g爜鐢熸垚", businessType = BusinessType.GENCODE)
+ @GetMapping("/download/{tableId}")
+ public void download(HttpServletResponse response, @PathVariable("tableId") Long tableId) throws IOException {
+ byte[] data = genTableService.downloadCode(tableId);
+ genCode(response, data);
+ }
+
+ /**
+ * 鐢熸垚浠g爜锛堣嚜瀹氫箟璺緞锛�
+ *
+ * @param tableId 琛↖D
+ */
+ @SaCheckPermission("tool:gen:code")
+ @Log(title = "浠g爜鐢熸垚", businessType = BusinessType.GENCODE)
+ @GetMapping("/genCode/{tableId}")
+ public R<Void> genCode(@PathVariable("tableId") Long tableId) {
+ genTableService.generatorCode(tableId);
+ return R.ok();
+ }
+
+ /**
+ * 鍚屾鏁版嵁搴�
+ *
+ * @param tableId 琛↖D
+ */
+ @SaCheckPermission("tool:gen:edit")
+ @Log(title = "浠g爜鐢熸垚", businessType = BusinessType.UPDATE)
+ @Lock4j(keys = {"#tableId"}, acquireTimeout = 5000)
+ @GetMapping("/synchDb/{tableId}")
+ public R<Void> synchDb(@PathVariable("tableId") Long tableId) {
+ genTableService.synchDb(tableId);
+ return R.ok();
+ }
+
+ /**
+ * 鎵归噺鐢熸垚浠g爜
+ *
+ * @param tableIdStr 琛↖D涓�
+ */
+ @SaCheckPermission("tool:gen:code")
+ @Log(title = "浠g爜鐢熸垚", businessType = BusinessType.GENCODE)
+ @GetMapping("/batchGenCode")
+ public void batchGenCode(HttpServletResponse response, String tableIdStr) throws IOException {
+ String[] tableIds = Convert.toStrArray(tableIdStr);
+ byte[] data = genTableService.downloadCode(tableIds);
+ genCode(response, data);
+ }
+
+ /**
+ * 鐢熸垚zip鏂囦欢
+ */
+ private void genCode(HttpServletResponse response, byte[] data) throws IOException {
+ response.reset();
+ response.addHeader("Access-Control-Allow-Origin", "*");
+ response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
+ response.setHeader("Content-Disposition", "attachment; filename=\"ruoyi.zip\"");
+ response.addHeader("Content-Length", "" + data.length);
+ response.setContentType("application/octet-stream; charset=UTF-8");
+ IoUtil.write(response.getOutputStream(), false, data);
+ }
+
+ /**
+ * 鏌ヨ鏁版嵁婧愬悕绉板垪琛�
+ */
+ @SaCheckPermission("tool:gen:list")
+ @GetMapping(value = "/getDataNames")
+ public R<Object> getCurrentDataSourceNameList() {
+ return R.ok(DataBaseHelper.getDataSourceNameList());
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTable.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTable.java
new file mode 100755
index 0000000..f2d7257
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTable.java
@@ -0,0 +1,196 @@
+package org.dromara.generator.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.generator.constant.GenConstants;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * 涓氬姟琛� gen_table
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("gen_table")
+public class GenTable extends BaseEntity {
+
+ /**
+ * 缂栧彿
+ */
+ @TableId(value = "table_id")
+ private Long tableId;
+
+ /**
+ * 鏁版嵁婧愬悕绉�
+ */
+ @NotBlank(message = "鏁版嵁婧愬悕绉颁笉鑳戒负绌�")
+ private String dataName;
+
+ /**
+ * 琛ㄥ悕绉�
+ */
+ @NotBlank(message = "琛ㄥ悕绉颁笉鑳戒负绌�")
+ private String tableName;
+
+ /**
+ * 琛ㄦ弿杩�
+ */
+ @NotBlank(message = "琛ㄦ弿杩颁笉鑳戒负绌�")
+ private String tableComment;
+
+ /**
+ * 鍏宠仈鐖惰〃鐨勮〃鍚�
+ */
+ private String subTableName;
+
+ /**
+ * 鏈〃鍏宠仈鐖惰〃鐨勫閿悕
+ */
+ private String subTableFkName;
+
+ /**
+ * 瀹炰綋绫诲悕绉�(棣栧瓧姣嶅ぇ鍐�)
+ */
+ @NotBlank(message = "瀹炰綋绫诲悕绉颁笉鑳戒负绌�")
+ private String className;
+
+ /**
+ * 浣跨敤鐨勬ā鏉匡紙crud鍗曡〃鎿嶄綔 tree鏍戣〃鎿嶄綔 sub涓诲瓙琛ㄦ搷浣滐級
+ */
+ private String tplCategory;
+
+ /**
+ * 鐢熸垚鍖呰矾寰�
+ */
+ @NotBlank(message = "鐢熸垚鍖呰矾寰勪笉鑳戒负绌�")
+ private String packageName;
+
+ /**
+ * 鐢熸垚妯″潡鍚�
+ */
+ @NotBlank(message = "鐢熸垚妯″潡鍚嶄笉鑳戒负绌�")
+ private String moduleName;
+
+ /**
+ * 鐢熸垚涓氬姟鍚�
+ */
+ @NotBlank(message = "鐢熸垚涓氬姟鍚嶄笉鑳戒负绌�")
+ private String businessName;
+
+ /**
+ * 鐢熸垚鍔熻兘鍚�
+ */
+ @NotBlank(message = "鐢熸垚鍔熻兘鍚嶄笉鑳戒负绌�")
+ private String functionName;
+
+ /**
+ * 鐢熸垚浣滆��
+ */
+ @NotBlank(message = "浣滆�呬笉鑳戒负绌�")
+ private String functionAuthor;
+
+ /**
+ * 鐢熸垚浠g爜鏂瑰紡锛�0zip鍘嬬缉鍖� 1鑷畾涔夎矾寰勶級
+ */
+ private String genType;
+
+ /**
+ * 鐢熸垚璺緞锛堜笉濉粯璁ら」鐩矾寰勶級
+ */
+ @TableField(updateStrategy = FieldStrategy.NOT_EMPTY)
+ private String genPath;
+
+ /**
+ * 涓婚敭淇℃伅
+ */
+ @TableField(exist = false)
+ private GenTableColumn pkColumn;
+
+ /**
+ * 琛ㄥ垪淇℃伅
+ */
+ @Valid
+ @TableField(exist = false)
+ private List<GenTableColumn> columns;
+
+ /**
+ * 鍏跺畠鐢熸垚閫夐」
+ */
+ private String options;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鏍戠紪鐮佸瓧娈�
+ */
+ @TableField(exist = false)
+ private String treeCode;
+
+ /**
+ * 鏍戠埗缂栫爜瀛楁
+ */
+ @TableField(exist = false)
+ private String treeParentCode;
+
+ /**
+ * 鏍戝悕绉板瓧娈�
+ */
+ @TableField(exist = false)
+ private String treeName;
+
+ /*
+ * 鑿滃崟id鍒楄〃
+ */
+ @TableField(exist = false)
+ private List<Long> menuIds;
+
+ /**
+ * 涓婄骇鑿滃崟ID瀛楁
+ */
+ @TableField(exist = false)
+ private Long parentMenuId;
+
+ /**
+ * 涓婄骇鑿滃崟鍚嶇О瀛楁
+ */
+ @TableField(exist = false)
+ private String parentMenuName;
+
+ public boolean isTree() {
+ return isTree(this.tplCategory);
+ }
+
+ public static boolean isTree(String tplCategory) {
+ return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory);
+ }
+
+ public boolean isCrud() {
+ return isCrud(this.tplCategory);
+ }
+
+ public static boolean isCrud(String tplCategory) {
+ return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory);
+ }
+
+ public boolean isSuperColumn(String javaField) {
+ return isSuperColumn(this.tplCategory, javaField);
+ }
+
+ public static boolean isSuperColumn(String tplCategory, String javaField) {
+ return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTableColumn.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTableColumn.java
new file mode 100755
index 0000000..63fd1f5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTableColumn.java
@@ -0,0 +1,222 @@
+package org.dromara.generator.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.ibatis.type.JdbcType;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+/**
+ * 浠g爜鐢熸垚涓氬姟瀛楁琛� gen_table_column
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("gen_table_column")
+public class GenTableColumn extends BaseEntity {
+
+ /**
+ * 缂栧彿
+ */
+ @TableId(value = "column_id")
+ private Long columnId;
+
+ /**
+ * 褰掑睘琛ㄧ紪鍙�
+ */
+ private Long tableId;
+
+ /**
+ * 鍒楀悕绉�
+ */
+ private String columnName;
+
+ /**
+ * 鍒楁弿杩�
+ */
+ @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
+ private String columnComment;
+
+ /**
+ * 鍒楃被鍨�
+ */
+ private String columnType;
+
+ /**
+ * JAVA绫诲瀷
+ */
+ private String javaType;
+
+ /**
+ * JAVA瀛楁鍚�
+ */
+ @NotBlank(message = "Java灞炴�т笉鑳戒负绌�")
+ private String javaField;
+
+ /**
+ * 鏄惁涓婚敭锛�1鏄級
+ */
+ @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
+ private String isPk;
+
+ /**
+ * 鏄惁鑷锛�1鏄級
+ */
+ @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
+ private String isIncrement;
+
+ /**
+ * 鏄惁蹇呭~锛�1鏄級
+ */
+ @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
+ private String isRequired;
+
+ /**
+ * 鏄惁涓烘彃鍏ュ瓧娈碉紙1鏄級
+ */
+ @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
+ private String isInsert;
+
+ /**
+ * 鏄惁缂栬緫瀛楁锛�1鏄級
+ */
+ @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
+ private String isEdit;
+
+ /**
+ * 鏄惁鍒楄〃瀛楁锛�1鏄級
+ */
+ @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
+ private String isList;
+
+ /**
+ * 鏄惁鏌ヨ瀛楁锛�1鏄級
+ */
+ @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
+ private String isQuery;
+
+ /**
+ * 鏌ヨ鏂瑰紡锛圗Q绛変簬銆丯E涓嶇瓑浜庛�丟T澶т簬銆丩T灏忎簬銆丩IKE妯$硦銆丅ETWEEN鑼冨洿锛�
+ */
+ private String queryType;
+
+ /**
+ * 鏄剧ず绫诲瀷锛坕nput鏂囨湰妗嗐�乼extarea鏂囨湰鍩熴�乻elect涓嬫媺妗嗐�乧heckbox澶嶉�夋銆乺adio鍗曢�夋銆乨atetime鏃ユ湡鎺т欢銆乮mage鍥剧墖涓婁紶鎺т欢銆乽pload鏂囦欢涓婁紶鎺т欢銆乪ditor瀵屾枃鏈帶浠讹級
+ */
+ private String htmlType;
+
+ /**
+ * 瀛楀吀绫诲瀷
+ */
+ @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
+ private String dictType;
+
+ /**
+ * 鎺掑簭
+ */
+ private Integer sort;
+
+ public String getCapJavaField() {
+ return StringUtils.capitalize(javaField);
+ }
+
+ public boolean isPk() {
+ return isPk(this.isPk);
+ }
+
+ public boolean isPk(String isPk) {
+ return isPk != null && StringUtils.equals("1", isPk);
+ }
+
+ public boolean isIncrement() {
+ return isIncrement(this.isIncrement);
+ }
+
+ public boolean isIncrement(String isIncrement) {
+ return isIncrement != null && StringUtils.equals("1", isIncrement);
+ }
+
+ public boolean isRequired() {
+ return isRequired(this.isRequired);
+ }
+
+ public boolean isRequired(String isRequired) {
+ return isRequired != null && StringUtils.equals("1", isRequired);
+ }
+
+ public boolean isInsert() {
+ return isInsert(this.isInsert);
+ }
+
+ public boolean isInsert(String isInsert) {
+ return isInsert != null && StringUtils.equals("1", isInsert);
+ }
+
+ public boolean isEdit() {
+ return isEdit(this.isEdit);
+ }
+
+ public boolean isEdit(String isEdit) {
+ return isEdit != null && StringUtils.equals("1", isEdit);
+ }
+
+ public boolean isList() {
+ return isList(this.isList);
+ }
+
+ public boolean isList(String isList) {
+ return isList != null && StringUtils.equals("1", isList);
+ }
+
+ public boolean isQuery() {
+ return isQuery(this.isQuery);
+ }
+
+ public boolean isQuery(String isQuery) {
+ return isQuery != null && StringUtils.equals("1", isQuery);
+ }
+
+ public boolean isSuperColumn() {
+ return isSuperColumn(this.javaField);
+ }
+
+ public static boolean isSuperColumn(String javaField) {
+ return StringUtils.equalsAnyIgnoreCase(javaField,
+ // BaseEntity
+ "createBy", "createTime", "updateBy", "updateTime",
+ // TreeEntity
+ "parentName", "parentId");
+ }
+
+ public boolean isUsableColumn() {
+ return isUsableColumn(javaField);
+ }
+
+ public static boolean isUsableColumn(String javaField) {
+ // isSuperColumn()涓殑鍚嶅崟鐢ㄤ簬閬垮厤鐢熸垚澶氫綑Domain灞炴�э紝鑻ユ煇浜涘睘鎬у湪鐢熸垚椤甸潰鏃堕渶瑕佺敤鍒颁笉鑳藉拷鐣ワ紝鍒欐斁鍦ㄦ澶勭櫧鍚嶅崟
+ return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark");
+ }
+
+ public String readConverterExp() {
+ String remarks = StringUtils.substringBetween(this.columnComment, "锛�", "锛�");
+ StringBuffer sb = new StringBuffer();
+ if (StringUtils.isNotEmpty(remarks)) {
+ for (String value : remarks.split(" ")) {
+ if (StringUtils.isNotEmpty(value)) {
+ Object startStr = value.subSequence(0, 1);
+ String endStr = value.substring(1);
+ sb.append(StringUtils.EMPTY).append(startStr).append("=").append(endStr).append(StringUtils.SEPARATOR);
+ }
+ }
+ return sb.deleteCharAt(sb.length() - 1).toString();
+ } else {
+ return this.columnComment;
+ }
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableColumnMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableColumnMapper.java
new file mode 100755
index 0000000..ed8ed20
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableColumnMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.generator.mapper;
+
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.generator.domain.GenTableColumn;
+
+/**
+ * 涓氬姟瀛楁 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+@InterceptorIgnore(dataPermission = "true", tenantLine = "true")
+public interface GenTableColumnMapper extends BaseMapperPlus<GenTableColumn, GenTableColumn> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableMapper.java
new file mode 100755
index 0000000..1798b4b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/mapper/GenTableMapper.java
@@ -0,0 +1,51 @@
+package org.dromara.generator.mapper;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.generator.domain.GenTable;
+
+import java.util.List;
+
+/**
+ * 涓氬姟 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+@InterceptorIgnore(dataPermission = "true", tenantLine = "true")
+public interface GenTableMapper extends BaseMapperPlus<GenTable, GenTable> {
+
+ /**
+ * 鏌ヨ鎵�鏈夎〃淇℃伅
+ *
+ * @return 琛ㄤ俊鎭泦鍚�
+ */
+ List<GenTable> selectGenTableAll();
+
+ /**
+ * 鏌ヨ琛↖D涓氬姟淇℃伅
+ *
+ * @param id 涓氬姟ID
+ * @return 涓氬姟淇℃伅
+ */
+ GenTable selectGenTableById(Long id);
+
+ /**
+ * 鏌ヨ琛ㄥ悕绉颁笟鍔′俊鎭�
+ *
+ * @param tableName 琛ㄥ悕绉�
+ * @return 涓氬姟淇℃伅
+ */
+ GenTable selectGenTableByName(String tableName);
+
+ /**
+ * 鏌ヨ鎸囧畾鏁版嵁婧愪笅鐨勬墍鏈夎〃鍚嶅垪琛�
+ *
+ * @param dataName 鏁版嵁婧愬悕绉帮紝鐢ㄤ簬閫夋嫨涓嶅悓鐨勬暟鎹簮
+ * @return 褰撳墠鏁版嵁搴撲腑鐨勮〃鍚嶅垪琛�
+ *
+ * @DS("") 浣跨敤榛樿鏁版嵁婧愭墽琛屾煡璇㈡搷浣�
+ */
+ @DS("")
+ List<String> selectTableNameList(String dataName);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java
new file mode 100755
index 0000000..3e7bdf7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java
@@ -0,0 +1,575 @@
+package org.dromara.generator.service;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.dynamic.datasource.annotation.DSTransactional;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.anyline.metadata.Column;
+import org.anyline.metadata.Table;
+import org.anyline.proxy.ServiceProxy;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.file.FileUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.utils.IdGeneratorUtil;
+import org.dromara.generator.constant.GenConstants;
+import org.dromara.generator.domain.GenTable;
+import org.dromara.generator.domain.GenTableColumn;
+import org.dromara.generator.mapper.GenTableColumnMapper;
+import org.dromara.generator.mapper.GenTableMapper;
+import org.dromara.generator.util.GenUtils;
+import org.dromara.generator.util.VelocityInitializer;
+import org.dromara.generator.util.VelocityUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * 涓氬姟 鏈嶅姟灞傚疄鐜�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class GenTableServiceImpl implements IGenTableService {
+
+ private final GenTableMapper baseMapper;
+ private final GenTableColumnMapper genTableColumnMapper;
+
+ private static final String[] TABLE_IGNORE = new String[]{"sj_", "flow_", "gen_"};
+
+ /**
+ * 鏌ヨ涓氬姟瀛楁鍒楄〃
+ *
+ * @param tableId 涓氬姟瀛楁缂栧彿
+ * @return 涓氬姟瀛楁闆嗗悎
+ */
+ @Override
+ public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId) {
+ return genTableColumnMapper.selectList(new LambdaQueryWrapper<GenTableColumn>()
+ .eq(GenTableColumn::getTableId, tableId)
+ .orderByAsc(GenTableColumn::getSort));
+ }
+
+ /**
+ * 鏌ヨ涓氬姟淇℃伅
+ *
+ * @param id 涓氬姟ID
+ * @return 涓氬姟淇℃伅
+ */
+ @Override
+ public GenTable selectGenTableById(Long id) {
+ GenTable genTable = baseMapper.selectGenTableById(id);
+ setTableFromOptions(genTable);
+ return genTable;
+ }
+
+ @Override
+ public TableDataInfo<GenTable> selectPageGenTableList(GenTable genTable, PageQuery pageQuery) {
+ Page<GenTable> page = baseMapper.selectPage(pageQuery.build(), this.buildGenTableQueryWrapper(genTable));
+ return TableDataInfo.build(page);
+ }
+
+ private QueryWrapper<GenTable> buildGenTableQueryWrapper(GenTable genTable) {
+ Map<String, Object> params = genTable.getParams();
+ QueryWrapper<GenTable> wrapper = Wrappers.query();
+ wrapper
+ .eq(StringUtils.isNotEmpty(genTable.getDataName()), "data_name", genTable.getDataName())
+ .like(StringUtils.isNotBlank(genTable.getTableName()), "lower(table_name)", StringUtils.lowerCase(genTable.getTableName()))
+ .like(StringUtils.isNotBlank(genTable.getTableComment()), "lower(table_comment)", StringUtils.lowerCase(genTable.getTableComment()))
+ .between(params.get("beginTime") != null && params.get("endTime") != null,
+ "create_time", params.get("beginTime"), params.get("endTime"))
+ .orderByDesc("update_time");
+ return wrapper;
+ }
+
+ /**
+ * 鏌ヨ鏁版嵁搴撳垪琛�
+ *
+ * @param genTable 鍖呭惈鏌ヨ鏉′欢鐨凣enTable瀵硅薄
+ * @param pageQuery 鍖呭惈鍒嗛〉淇℃伅鐨凱ageQuery瀵硅薄
+ * @return 鍖呭惈鍒嗛〉缁撴灉鐨凾ableDataInfo瀵硅薄
+ */
+ @DS("#genTable.dataName")
+ @Override
+ public TableDataInfo<GenTable> selectPageDbTableList(GenTable genTable, PageQuery pageQuery) {
+ // 鑾峰彇鏌ヨ鏉′欢
+ String tableName = genTable.getTableName();
+ String tableComment = genTable.getTableComment();
+
+ LinkedHashMap<String, Table<?>> tablesMap = ServiceProxy.metadata().tables();
+ if (CollUtil.isEmpty(tablesMap)) {
+ return TableDataInfo.build();
+ }
+ List<String> tableNames = baseMapper.selectTableNameList(genTable.getDataName());
+ String[] tableArrays;
+ if (CollUtil.isNotEmpty(tableNames)) {
+ tableArrays = tableNames.toArray(new String[0]);
+ } else {
+ tableArrays = new String[0];
+ }
+ // 杩囨护骞惰浆鎹㈣〃鏍兼暟鎹�
+ List<GenTable> tables = tablesMap.values().stream()
+ .filter(x -> !StringUtils.startWithAnyIgnoreCase(x.getName(), TABLE_IGNORE))
+ .filter(x -> {
+ if (CollUtil.isEmpty(tableNames)) {
+ return true;
+ }
+ return !StringUtils.equalsAnyIgnoreCase(x.getName(), tableArrays);
+ })
+ .filter(x -> {
+ boolean nameMatches = true;
+ boolean commentMatches = true;
+ // 杩涜琛ㄥ悕绉扮殑妯$硦鏌ヨ
+ if (StringUtils.isNotBlank(tableName)) {
+ nameMatches = StringUtils.containsIgnoreCase(x.getName(), tableName);
+ }
+ // 杩涜琛ㄦ弿杩扮殑妯$硦鏌ヨ
+ if (StringUtils.isNotBlank(tableComment)) {
+ commentMatches = StringUtils.containsIgnoreCase(x.getComment(), tableComment);
+ }
+ // 鍚屾椂鍖归厤鍚嶇О鍜屾弿杩�
+ return nameMatches && commentMatches;
+ })
+ .map(x -> {
+ GenTable gen = new GenTable();
+ gen.setTableName(x.getName());
+ gen.setTableComment(x.getComment());
+ // postgresql鐨勮〃鍏冩暟鎹病鏈夊垱寤烘椂闂磋繖涓笢瑗�(濂藉钁�) 鍙兘new Date浠f浛
+ gen.setCreateTime(ObjectUtil.defaultIfNull(x.getCreateTime(), new Date()));
+ gen.setUpdateTime(x.getUpdateTime());
+ return gen;
+ }).sorted(Comparator.comparing(GenTable::getCreateTime).reversed())
+ .toList();
+ return TableDataInfo.build(tables, pageQuery.build());
+ }
+
+ /**
+ * 鏌ヨ鎹簱鍒楄〃
+ *
+ * @param tableNames 琛ㄥ悕绉扮粍
+ * @param dataName 鏁版嵁婧愬悕绉�
+ * @return 鏁版嵁搴撹〃闆嗗悎
+ */
+ @DS("#dataName")
+ @Override
+ public List<GenTable> selectDbTableListByNames(String[] tableNames, String dataName) {
+ Set<String> tableNameSet = new HashSet<>(List.of(tableNames));
+ LinkedHashMap<String, Table<?>> tablesMap = ServiceProxy.metadata().tables();
+
+ if (CollUtil.isEmpty(tablesMap)) {
+ return new ArrayList<>();
+ }
+
+ List<Table<?>> tableList = tablesMap.values().stream()
+ .filter(x -> !StringUtils.startWithAnyIgnoreCase(x.getName(), TABLE_IGNORE))
+ .filter(x -> tableNameSet.contains(x.getName())).toList();
+
+ if (CollUtil.isEmpty(tableList)) {
+ return new ArrayList<>();
+ }
+ return tableList.stream().map(x -> {
+ GenTable gen = new GenTable();
+ gen.setDataName(dataName);
+ gen.setTableName(x.getName());
+ gen.setTableComment(x.getComment());
+ gen.setCreateTime(x.getCreateTime());
+ gen.setUpdateTime(x.getUpdateTime());
+ return gen;
+ }).toList();
+ }
+
+ /**
+ * 鏌ヨ鎵�鏈夎〃淇℃伅
+ *
+ * @return 琛ㄤ俊鎭泦鍚�
+ */
+ @Override
+ public List<GenTable> selectGenTableAll() {
+ return baseMapper.selectGenTableAll();
+ }
+
+ /**
+ * 淇敼涓氬姟
+ *
+ * @param genTable 涓氬姟淇℃伅
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void updateGenTable(GenTable genTable) {
+ String options = JsonUtils.toJsonString(genTable.getParams());
+ genTable.setOptions(options);
+ int row = baseMapper.updateById(genTable);
+ if (row > 0) {
+ for (GenTableColumn cenTableColumn : genTable.getColumns()) {
+ genTableColumnMapper.updateById(cenTableColumn);
+ }
+ }
+ }
+
+ /**
+ * 鍒犻櫎涓氬姟瀵硅薄
+ *
+ * @param tableIds 闇�瑕佸垹闄ょ殑鏁版嵁ID
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void deleteGenTableByIds(Long[] tableIds) {
+ List<Long> ids = Arrays.asList(tableIds);
+ baseMapper.deleteByIds(ids);
+ genTableColumnMapper.delete(new LambdaQueryWrapper<GenTableColumn>().in(GenTableColumn::getTableId, ids));
+ }
+
+ /**
+ * 瀵煎叆琛ㄧ粨鏋�
+ *
+ * @param tableList 瀵煎叆琛ㄥ垪琛�
+ * @param dataName 鏁版嵁婧愬悕绉�
+ */
+ @DSTransactional
+ @Override
+ public void importGenTable(List<GenTable> tableList, String dataName) {
+ try {
+ for (GenTable table : tableList) {
+ String tableName = table.getTableName();
+ GenUtils.initTable(table);
+ table.setDataName(dataName);
+ int row = baseMapper.insert(table);
+ if (row > 0) {
+ // 淇濆瓨鍒椾俊鎭�
+ List<GenTableColumn> genTableColumns = SpringUtils.getAopProxy(this).selectDbTableColumnsByName(tableName, dataName);
+ List<GenTableColumn> saveColumns = new ArrayList<>();
+ for (GenTableColumn column : genTableColumns) {
+ GenUtils.initColumnField(column, table);
+ saveColumns.add(column);
+ }
+ if (CollUtil.isNotEmpty(saveColumns)) {
+ genTableColumnMapper.insertBatch(saveColumns);
+ }
+ }
+ }
+ } catch (Exception e) {
+ throw new ServiceException("瀵煎叆澶辫触锛�" + e.getMessage());
+ }
+ }
+
+ /**
+ * 鏍规嵁琛ㄥ悕绉版煡璇㈠垪淇℃伅
+ *
+ * @param tableName 琛ㄥ悕绉�
+ * @param dataName 鏁版嵁婧愬悕绉�
+ * @return 鍒椾俊鎭�
+ */
+ @DS("#dataName")
+ @Override
+ public List<GenTableColumn> selectDbTableColumnsByName(String tableName, String dataName) {
+ Table<?> table = ServiceProxy.metadata().table(tableName);
+ if (ObjectUtil.isNull(table)) {
+ return new ArrayList<>();
+ }
+ LinkedHashMap<String, Column> columns = table.getColumns();
+ List<GenTableColumn> tableColumns = new ArrayList<>();
+ columns.forEach((columnName, column) -> {
+ GenTableColumn tableColumn = new GenTableColumn();
+ tableColumn.setIsPk(column.isPrimaryKey() ? "1" : "0");
+ tableColumn.setColumnName(column.getName());
+ tableColumn.setColumnComment(column.getComment());
+ tableColumn.setColumnType(column.getOriginType().toLowerCase());
+ tableColumn.setSort(column.getPosition());
+ tableColumn.setIsRequired(column.isNullable() ? "0" : "1");
+ tableColumn.setIsIncrement(column.isAutoIncrement() ? "1" : "0");
+ tableColumns.add(tableColumn);
+ });
+ return tableColumns;
+ }
+
+ /**
+ * 棰勮浠g爜
+ *
+ * @param tableId 琛ㄧ紪鍙�
+ * @return 棰勮鏁版嵁鍒楄〃
+ */
+ @Override
+ public Map<String, String> previewCode(Long tableId) {
+ Map<String, String> dataMap = new LinkedHashMap<>();
+ // 鏌ヨ琛ㄤ俊鎭�
+ GenTable table = baseMapper.selectGenTableById(tableId);
+ List<Long> menuIds = new ArrayList<>();
+ for (int i = 0; i < 6; i++) {
+ menuIds.add(IdGeneratorUtil.nextLongId());
+ }
+ table.setMenuIds(menuIds);
+ // 璁剧疆涓婚敭鍒椾俊鎭�
+ setPkColumn(table);
+ VelocityInitializer.initVelocity();
+
+ VelocityContext context = VelocityUtils.prepareContext(table);
+
+ // 鑾峰彇妯℃澘鍒楄〃
+ List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
+ for (String template : templates) {
+ // 娓叉煋妯℃澘
+ StringWriter sw = new StringWriter();
+ Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+ tpl.merge(context, sw);
+ dataMap.put(template, sw.toString());
+ }
+ return dataMap;
+ }
+
+ /**
+ * 鐢熸垚浠g爜锛堜笅杞芥柟寮忥級
+ *
+ * @param tableId 琛ㄥ悕绉�
+ * @return 鏁版嵁
+ */
+ @Override
+ public byte[] downloadCode(Long tableId) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ZipOutputStream zip = new ZipOutputStream(outputStream);
+ generatorCode(tableId, zip);
+ IoUtil.close(zip);
+ return outputStream.toByteArray();
+ }
+
+ /**
+ * 鐢熸垚浠g爜锛堣嚜瀹氫箟璺緞锛�
+ *
+ * @param tableId 琛ㄥ悕绉�
+ */
+ @Override
+ public void generatorCode(Long tableId) {
+ // 鏌ヨ琛ㄤ俊鎭�
+ GenTable table = baseMapper.selectGenTableById(tableId);
+ // 璁剧疆涓婚敭鍒椾俊鎭�
+ setPkColumn(table);
+
+ VelocityInitializer.initVelocity();
+
+ VelocityContext context = VelocityUtils.prepareContext(table);
+
+ // 鑾峰彇妯℃澘鍒楄〃
+ List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
+ for (String template : templates) {
+ if (!StringUtils.containsAny(template, "sql.vm", "api.ts.vm", "types.ts.vm", "index.vue.vm", "index-tree.vue.vm")) {
+ // 娓叉煋妯℃澘
+ StringWriter sw = new StringWriter();
+ Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+ tpl.merge(context, sw);
+ try {
+ String path = getGenPath(table, template);
+ FileUtils.writeUtf8String(sw.toString(), path);
+ } catch (Exception e) {
+ throw new ServiceException("娓叉煋妯℃澘澶辫触锛岃〃鍚嶏細" + table.getTableName());
+ }
+ }
+ }
+ }
+
+ /**
+ * 鍚屾鏁版嵁搴�
+ *
+ * @param tableId 琛ㄥ悕绉�
+ */
+ @DSTransactional
+ @Override
+ public void synchDb(Long tableId) {
+ GenTable table = baseMapper.selectGenTableById(tableId);
+ List<GenTableColumn> tableColumns = table.getColumns();
+ Map<String, GenTableColumn> tableColumnMap = StreamUtils.toIdentityMap(tableColumns, GenTableColumn::getColumnName);
+
+ List<GenTableColumn> dbTableColumns = SpringUtils.getAopProxy(this).selectDbTableColumnsByName(table.getTableName(), table.getDataName());
+ if (CollUtil.isEmpty(dbTableColumns)) {
+ throw new ServiceException("鍚屾鏁版嵁澶辫触锛屽師琛ㄧ粨鏋勪笉瀛樺湪");
+ }
+ List<String> dbTableColumnNames = StreamUtils.toList(dbTableColumns, GenTableColumn::getColumnName);
+
+ List<GenTableColumn> saveColumns = new ArrayList<>();
+ dbTableColumns.forEach(column -> {
+ GenUtils.initColumnField(column, table);
+ if (tableColumnMap.containsKey(column.getColumnName())) {
+ GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName());
+ column.setColumnId(prevColumn.getColumnId());
+ if (column.isList()) {
+ // 濡傛灉鏄垪琛紝缁х画淇濈暀鏌ヨ鏂瑰紡/瀛楀吀绫诲瀷閫夐」
+ column.setDictType(prevColumn.getDictType());
+ column.setQueryType(prevColumn.getQueryType());
+ }
+ if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk()
+ && (column.isInsert() || column.isEdit())
+ && ((column.isUsableColumn()) || (!column.isSuperColumn()))) {
+ // 濡傛灉鏄�(鏂板/淇敼&闈炰富閿�/闈炲拷鐣ュ強鐖跺睘鎬�)锛岀户缁繚鐣欏繀濉�/鏄剧ず绫诲瀷閫夐」
+ column.setIsRequired(prevColumn.getIsRequired());
+ column.setHtmlType(prevColumn.getHtmlType());
+ }
+ }
+ saveColumns.add(column);
+ });
+ if (CollUtil.isNotEmpty(saveColumns)) {
+ genTableColumnMapper.insertOrUpdateBatch(saveColumns);
+ }
+ List<GenTableColumn> delColumns = StreamUtils.filter(tableColumns, column -> !dbTableColumnNames.contains(column.getColumnName()));
+ if (CollUtil.isNotEmpty(delColumns)) {
+ List<Long> ids = StreamUtils.toList(delColumns, GenTableColumn::getColumnId);
+ if (CollUtil.isNotEmpty(ids)) {
+ genTableColumnMapper.deleteByIds(ids);
+ }
+ }
+ }
+
+ /**
+ * 鎵归噺鐢熸垚浠g爜锛堜笅杞芥柟寮忥級
+ *
+ * @param tableIds 琛↖D鏁扮粍
+ * @return 鏁版嵁
+ */
+ @Override
+ public byte[] downloadCode(String[] tableIds) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ZipOutputStream zip = new ZipOutputStream(outputStream);
+ for (String tableId : tableIds) {
+ generatorCode(Long.parseLong(tableId), zip);
+ }
+ IoUtil.close(zip);
+ return outputStream.toByteArray();
+ }
+
+ /**
+ * 鏌ヨ琛ㄤ俊鎭苟鐢熸垚浠g爜
+ */
+ private void generatorCode(Long tableId, ZipOutputStream zip) {
+ // 鏌ヨ琛ㄤ俊鎭�
+ GenTable table = baseMapper.selectGenTableById(tableId);
+ List<Long> menuIds = new ArrayList<>();
+ for (int i = 0; i < 6; i++) {
+ menuIds.add(IdGeneratorUtil.nextLongId());
+ }
+ table.setMenuIds(menuIds);
+ // 璁剧疆涓婚敭鍒椾俊鎭�
+ setPkColumn(table);
+
+ VelocityInitializer.initVelocity();
+
+ VelocityContext context = VelocityUtils.prepareContext(table);
+
+ // 鑾峰彇妯℃澘鍒楄〃
+ List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
+ for (String template : templates) {
+ // 娓叉煋妯℃澘
+ StringWriter sw = new StringWriter();
+ Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+ tpl.merge(context, sw);
+ try {
+ // 娣诲姞鍒皕ip
+ zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
+ IoUtil.write(zip, StandardCharsets.UTF_8, false, sw.toString());
+ IoUtil.close(sw);
+ zip.flush();
+ zip.closeEntry();
+ } catch (IOException e) {
+ log.error("娓叉煋妯℃澘澶辫触锛岃〃鍚嶏細" + table.getTableName(), e);
+ }
+ }
+ }
+
+ /**
+ * 淇敼淇濆瓨鍙傛暟鏍¢獙
+ *
+ * @param genTable 涓氬姟淇℃伅
+ */
+ @Override
+ public void validateEdit(GenTable genTable) {
+ if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) {
+ String options = JsonUtils.toJsonString(genTable.getParams());
+ Dict paramsObj = JsonUtils.parseMap(options);
+ if (StringUtils.isEmpty(paramsObj.getStr(GenConstants.TREE_CODE))) {
+ throw new ServiceException("鏍戠紪鐮佸瓧娈典笉鑳戒负绌�");
+ } else if (StringUtils.isEmpty(paramsObj.getStr(GenConstants.TREE_PARENT_CODE))) {
+ throw new ServiceException("鏍戠埗缂栫爜瀛楁涓嶈兘涓虹┖");
+ } else if (StringUtils.isEmpty(paramsObj.getStr(GenConstants.TREE_NAME))) {
+ throw new ServiceException("鏍戝悕绉板瓧娈典笉鑳戒负绌�");
+ }
+ }
+ }
+
+ /**
+ * 璁剧疆涓婚敭鍒椾俊鎭�
+ *
+ * @param table 涓氬姟琛ㄤ俊鎭�
+ */
+ public void setPkColumn(GenTable table) {
+ for (GenTableColumn column : table.getColumns()) {
+ if (column.isPk()) {
+ table.setPkColumn(column);
+ break;
+ }
+ }
+ if (ObjectUtil.isNull(table.getPkColumn())) {
+ table.setPkColumn(table.getColumns().get(0));
+ }
+
+ }
+
+ /**
+ * 璁剧疆浠g爜鐢熸垚鍏朵粬閫夐」鍊�
+ *
+ * @param genTable 璁剧疆鍚庣殑鐢熸垚瀵硅薄
+ */
+ public void setTableFromOptions(GenTable genTable) {
+ Dict paramsObj = JsonUtils.parseMap(genTable.getOptions());
+ if (ObjectUtil.isNotNull(paramsObj)) {
+ String treeCode = paramsObj.getStr(GenConstants.TREE_CODE);
+ String treeParentCode = paramsObj.getStr(GenConstants.TREE_PARENT_CODE);
+ String treeName = paramsObj.getStr(GenConstants.TREE_NAME);
+ Long parentMenuId = paramsObj.getLong(GenConstants.PARENT_MENU_ID);
+ String parentMenuName = paramsObj.getStr(GenConstants.PARENT_MENU_NAME);
+
+ genTable.setTreeCode(treeCode);
+ genTable.setTreeParentCode(treeParentCode);
+ genTable.setTreeName(treeName);
+ genTable.setParentMenuId(parentMenuId);
+ genTable.setParentMenuName(parentMenuName);
+ }
+ }
+
+ /**
+ * 鑾峰彇浠g爜鐢熸垚鍦板潃
+ *
+ * @param table 涓氬姟琛ㄤ俊鎭�
+ * @param template 妯℃澘鏂囦欢璺緞
+ * @return 鐢熸垚鍦板潃
+ */
+ public static String getGenPath(GenTable table, String template) {
+ String genPath = table.getGenPath();
+ if (StringUtils.equals(genPath, "/")) {
+ return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table);
+ }
+ return genPath + File.separator + VelocityUtils.getFileName(template, table);
+ }
+}
+
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/IGenTableService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/IGenTableService.java
new file mode 100755
index 0000000..b2c20c5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/IGenTableService.java
@@ -0,0 +1,141 @@
+package org.dromara.generator.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.generator.domain.GenTable;
+import org.dromara.generator.domain.GenTableColumn;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 涓氬姟 鏈嶅姟灞�
+ *
+ * @author Lion Li
+ */
+public interface IGenTableService {
+
+ /**
+ * 鏌ヨ涓氬姟瀛楁鍒楄〃
+ *
+ * @param tableId 涓氬姟瀛楁缂栧彿
+ * @return 涓氬姟瀛楁闆嗗悎
+ */
+ List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId);
+
+ /**
+ * 鏌ヨ涓氬姟鍒楄〃
+ *
+ * @param genTable 涓氬姟淇℃伅
+ * @return 涓氬姟闆嗗悎
+ */
+ TableDataInfo<GenTable> selectPageGenTableList(GenTable genTable, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ鎹簱鍒楄〃
+ *
+ * @param genTable 涓氬姟淇℃伅
+ * @return 鏁版嵁搴撹〃闆嗗悎
+ */
+ TableDataInfo<GenTable> selectPageDbTableList(GenTable genTable, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ鎹簱鍒楄〃
+ *
+ * @param tableNames 琛ㄥ悕绉扮粍
+ * @param dataName 鏁版嵁婧愬悕绉�
+ * @return 鏁版嵁搴撹〃闆嗗悎
+ */
+ List<GenTable> selectDbTableListByNames(String[] tableNames, String dataName);
+
+ /**
+ * 鏌ヨ鎵�鏈夎〃淇℃伅
+ *
+ * @return 琛ㄤ俊鎭泦鍚�
+ */
+ List<GenTable> selectGenTableAll();
+
+ /**
+ * 鏌ヨ涓氬姟淇℃伅
+ *
+ * @param id 涓氬姟ID
+ * @return 涓氬姟淇℃伅
+ */
+ GenTable selectGenTableById(Long id);
+
+ /**
+ * 淇敼涓氬姟
+ *
+ * @param genTable 涓氬姟淇℃伅
+ */
+ void updateGenTable(GenTable genTable);
+
+ /**
+ * 鍒犻櫎涓氬姟淇℃伅
+ *
+ * @param tableIds 闇�瑕佸垹闄ょ殑琛ㄦ暟鎹甀D
+ */
+ void deleteGenTableByIds(Long[] tableIds);
+
+ /**
+ * 瀵煎叆琛ㄧ粨鏋�
+ *
+ * @param tableList 瀵煎叆琛ㄥ垪琛�
+ * @param dataName 鏁版嵁婧愬悕绉�
+ */
+ void importGenTable(List<GenTable> tableList, String dataName);
+
+ /**
+ * 鏍规嵁琛ㄥ悕绉版煡璇㈠垪淇℃伅
+ *
+ * @param tableName 琛ㄥ悕绉�
+ * @param dataName 鏁版嵁婧愬悕绉�
+ * @return 鍒椾俊鎭�
+ */
+ List<GenTableColumn> selectDbTableColumnsByName(String tableName, String dataName);
+
+ /**
+ * 棰勮浠g爜
+ *
+ * @param tableId 琛ㄧ紪鍙�
+ * @return 棰勮鏁版嵁鍒楄〃
+ */
+ Map<String, String> previewCode(Long tableId);
+
+ /**
+ * 鐢熸垚浠g爜锛堜笅杞芥柟寮忥級
+ *
+ * @param tableId 琛ㄥ悕绉�
+ * @return 鏁版嵁
+ */
+ byte[] downloadCode(Long tableId);
+
+ /**
+ * 鐢熸垚浠g爜锛堣嚜瀹氫箟璺緞锛�
+ *
+ * @param tableId 琛ㄥ悕绉�
+ */
+ void generatorCode(Long tableId);
+
+ /**
+ * 鍚屾鏁版嵁搴�
+ *
+ * @param tableId 琛ㄥ悕绉�
+ */
+ void synchDb(Long tableId);
+
+ /**
+ * 鎵归噺鐢熸垚浠g爜锛堜笅杞芥柟寮忥級
+ *
+ * @param tableIds 琛↖D鏁扮粍
+ * @return 鏁版嵁
+ */
+ byte[] downloadCode(String[] tableIds);
+
+ /**
+ * 淇敼淇濆瓨鍙傛暟鏍¢獙
+ *
+ * @param genTable 涓氬姟淇℃伅
+ */
+ void validateEdit(GenTable genTable);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/GenUtils.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/GenUtils.java
new file mode 100755
index 0000000..996cf9b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/GenUtils.java
@@ -0,0 +1,219 @@
+package org.dromara.generator.util;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.RegExUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.generator.config.GenConfig;
+import org.dromara.generator.constant.GenConstants;
+import org.dromara.generator.domain.GenTable;
+import org.dromara.generator.domain.GenTableColumn;
+
+import java.util.Arrays;
+
+/**
+ * 浠g爜鐢熸垚鍣� 宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class GenUtils {
+
+ /**
+ * 鍒濆鍖栬〃淇℃伅
+ */
+ public static void initTable(GenTable genTable) {
+ genTable.setClassName(convertClassName(genTable.getTableName()));
+ genTable.setPackageName(GenConfig.getPackageName());
+ genTable.setModuleName(getModuleName(GenConfig.getPackageName()));
+ genTable.setBusinessName(getBusinessName(genTable.getTableName()));
+ genTable.setFunctionName(replaceText(genTable.getTableComment()));
+ genTable.setFunctionAuthor(GenConfig.getAuthor());
+ genTable.setCreateTime(null);
+ genTable.setUpdateTime(null);
+ }
+
+ /**
+ * 鍒濆鍖栧垪灞炴�у瓧娈�
+ */
+ public static void initColumnField(GenTableColumn column, GenTable table) {
+ String dataType = getDbType(column.getColumnType());
+ // 缁熶竴杞皬鍐� 閬垮厤鏈変簺鏁版嵁搴撻粯璁ゅぇ鍐欓棶棰� 濡傛灉闇�瑕佺壒鍒功鍐欐柟寮� 璇峰湪瀹炰綋绫诲鍔犳敞瑙f爣娉ㄥ埆鍚�
+ String columnName = column.getColumnName().toLowerCase();
+ column.setTableId(table.getTableId());
+ column.setCreateTime(null);
+ column.setUpdateTime(null);
+ // 璁剧疆java瀛楁鍚�
+ column.setJavaField(StringUtils.toCamelCase(columnName));
+ // 璁剧疆榛樿绫诲瀷
+ column.setJavaType(GenConstants.TYPE_STRING);
+ column.setQueryType(GenConstants.QUERY_EQ);
+
+ if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) {
+ // 瀛楃涓查暱搴﹁秴杩�500璁剧疆涓烘枃鏈煙
+ Integer columnLength = getColumnLength(column.getColumnType());
+ String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT;
+ column.setHtmlType(htmlType);
+ } else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) {
+ column.setJavaType(GenConstants.TYPE_DATE);
+ column.setHtmlType(GenConstants.HTML_DATETIME);
+ } else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) {
+ column.setHtmlType(GenConstants.HTML_INPUT);
+ // 鏁版嵁搴撶殑鏁板瓧瀛楁涓巎ava涓嶅尮閰� 涓斿緢澶氭暟鎹簱鐨勬暟瀛楀瓧娈靛緢妯$硦 渚嬪oracle鍙湁number娌℃湁缁嗗垎
+ // 鎵�浠ラ粯璁ゆ暟瀛楃被鍨嬪叏涓篖ong鍙湪鐣岄潰涓婅嚜琛岀紪杈戞兂瑕佺殑绫诲瀷 鏈変粈涔堢壒娈婇渶姹備篃鍙互鍦ㄨ繖閲岀壒娈婂鐞�
+ column.setJavaType(GenConstants.TYPE_LONG);
+ }
+
+ // BO瀵硅薄 榛樿鎻掑叆鍕鹃��
+ if (!arraysContains(GenConstants.COLUMNNAME_NOT_ADD, columnName) && !column.isPk()) {
+ column.setIsInsert(GenConstants.REQUIRE);
+ }
+ // BO瀵硅薄 榛樿缂栬緫鍕鹃��
+ if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName)) {
+ column.setIsEdit(GenConstants.REQUIRE);
+ }
+ // VO瀵硅薄 榛樿杩斿洖鍕鹃��
+ if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName)) {
+ column.setIsList(GenConstants.REQUIRE);
+ }
+ // BO瀵硅薄 榛樿鏌ヨ鍕鹃��
+ if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) {
+ column.setIsQuery(GenConstants.REQUIRE);
+ }
+
+ // 鏌ヨ瀛楁绫诲瀷
+ if (StringUtils.endsWithIgnoreCase(columnName, "name")) {
+ column.setQueryType(GenConstants.QUERY_LIKE);
+ }
+ // 鐘舵�佸瓧娈佃缃崟閫夋
+ if (StringUtils.endsWithIgnoreCase(columnName, "status")) {
+ column.setHtmlType(GenConstants.HTML_RADIO);
+ }
+ // 绫诲瀷&鎬у埆瀛楁璁剧疆涓嬫媺妗�
+ else if (StringUtils.endsWithIgnoreCase(columnName, "type")
+ || StringUtils.endsWithIgnoreCase(columnName, "sex")) {
+ column.setHtmlType(GenConstants.HTML_SELECT);
+ }
+ // 鍥剧墖瀛楁璁剧疆鍥剧墖涓婁紶鎺т欢
+ else if (StringUtils.endsWithIgnoreCase(columnName, "image")) {
+ column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD);
+ }
+ // 鏂囦欢瀛楁璁剧疆鏂囦欢涓婁紶鎺т欢
+ else if (StringUtils.endsWithIgnoreCase(columnName, "file")) {
+ column.setHtmlType(GenConstants.HTML_FILE_UPLOAD);
+ }
+ // 鍐呭瀛楁璁剧疆瀵屾枃鏈帶浠�
+ else if (StringUtils.endsWithIgnoreCase(columnName, "content")) {
+ column.setHtmlType(GenConstants.HTML_EDITOR);
+ }
+ }
+
+ /**
+ * 鏍¢獙鏁扮粍鏄惁鍖呭惈鎸囧畾鍊�
+ *
+ * @param arr 鏁扮粍
+ * @param targetValue 鍊�
+ * @return 鏄惁鍖呭惈
+ */
+ public static boolean arraysContains(String[] arr, String targetValue) {
+ return Arrays.asList(arr).contains(targetValue);
+ }
+
+ /**
+ * 鑾峰彇妯″潡鍚�
+ *
+ * @param packageName 鍖呭悕
+ * @return 妯″潡鍚�
+ */
+ public static String getModuleName(String packageName) {
+ int lastIndex = packageName.lastIndexOf(".");
+ int nameLength = packageName.length();
+ return StringUtils.substring(packageName, lastIndex + 1, nameLength);
+ }
+
+ /**
+ * 鑾峰彇涓氬姟鍚�
+ *
+ * @param tableName 琛ㄥ悕
+ * @return 涓氬姟鍚�
+ */
+ public static String getBusinessName(String tableName) {
+ int firstIndex = tableName.indexOf("_");
+ int nameLength = tableName.length();
+ String businessName = StringUtils.substring(tableName, firstIndex + 1, nameLength);
+ businessName = StringUtils.toCamelCase(businessName);
+ return businessName;
+ }
+
+ /**
+ * 琛ㄥ悕杞崲鎴怞ava绫诲悕
+ *
+ * @param tableName 琛ㄥ悕绉�
+ * @return 绫诲悕
+ */
+ public static String convertClassName(String tableName) {
+ boolean autoRemovePre = GenConfig.getAutoRemovePre();
+ String tablePrefix = GenConfig.getTablePrefix();
+ if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) {
+ String[] searchList = StringUtils.split(tablePrefix, StringUtils.SEPARATOR);
+ tableName = replaceFirst(tableName, searchList);
+ }
+ return StringUtils.convertToCamelCase(tableName);
+ }
+
+ /**
+ * 鎵归噺鏇挎崲鍓嶇紑
+ *
+ * @param replacementm 鏇挎崲鍊�
+ * @param searchList 鏇挎崲鍒楄〃
+ */
+ public static String replaceFirst(String replacementm, String[] searchList) {
+ String text = replacementm;
+ for (String searchString : searchList) {
+ if (replacementm.startsWith(searchString)) {
+ text = replacementm.replaceFirst(searchString, StringUtils.EMPTY);
+ break;
+ }
+ }
+ return text;
+ }
+
+ /**
+ * 鍏抽敭瀛楁浛鎹�
+ *
+ * @param text 闇�瑕佽鏇挎崲鐨勫悕瀛�
+ * @return 鏇挎崲鍚庣殑鍚嶅瓧
+ */
+ public static String replaceText(String text) {
+ return RegExUtils.replaceAll(text, "(?:琛▅鑻ヤ緷)", "");
+ }
+
+ /**
+ * 鑾峰彇鏁版嵁搴撶被鍨嬪瓧娈�
+ *
+ * @param columnType 鍒楃被鍨�
+ * @return 鎴彇鍚庣殑鍒楃被鍨�
+ */
+ public static String getDbType(String columnType) {
+ if (StringUtils.indexOf(columnType, "(") > 0) {
+ return StringUtils.substringBefore(columnType, "(");
+ } else {
+ return columnType;
+ }
+ }
+
+ /**
+ * 鑾峰彇瀛楁闀垮害
+ *
+ * @param columnType 鍒楃被鍨�
+ * @return 鎴彇鍚庣殑鍒楃被鍨�
+ */
+ public static Integer getColumnLength(String columnType) {
+ if (StringUtils.indexOf(columnType, "(") > 0) {
+ String length = StringUtils.substringBetween(columnType, "(", ")");
+ return Integer.valueOf(length);
+ } else {
+ return 0;
+ }
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityInitializer.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityInitializer.java
new file mode 100755
index 0000000..09e0121
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityInitializer.java
@@ -0,0 +1,35 @@
+package org.dromara.generator.util;
+
+import org.dromara.common.core.constant.Constants;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.velocity.app.Velocity;
+
+import java.util.Properties;
+
+/**
+ * VelocityEngine宸ュ巶
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class VelocityInitializer {
+
+ /**
+ * 鍒濆鍖杤m鏂规硶
+ */
+ public static void initVelocity() {
+ Properties p = new Properties();
+ try {
+ // 鍔犺浇classpath鐩綍涓嬬殑vm鏂囦欢
+ p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
+ // 瀹氫箟瀛楃闆�
+ p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8);
+ // 鍒濆鍖朧elocity寮曟搸锛屾寚瀹氶厤缃甈roperties
+ Velocity.init(p);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils.java
new file mode 100755
index 0000000..b9a50ce
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils.java
@@ -0,0 +1,389 @@
+package org.dromara.generator.util;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.StrUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.velocity.VelocityContext;
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.mybatis.enums.DataBaseType;
+import org.dromara.common.mybatis.helper.DataBaseHelper;
+import org.dromara.generator.constant.GenConstants;
+import org.dromara.generator.domain.GenTable;
+import org.dromara.generator.domain.GenTableColumn;
+
+import java.util.*;
+
+/**
+ * 妯℃澘澶勭悊宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class VelocityUtils {
+
+ /**
+ * 椤圭洰绌洪棿璺緞
+ */
+ private static final String PROJECT_PATH = "main/java";
+
+ /**
+ * mybatis绌洪棿璺緞
+ */
+ private static final String MYBATIS_PATH = "main/resources/mapper";
+
+ /**
+ * 榛樿涓婄骇鑿滃崟锛岀郴缁熷伐鍏�
+ */
+ private static final String DEFAULT_PARENT_MENU_ID = "3";
+
+ /**
+ * 璁剧疆妯℃澘鍙橀噺淇℃伅
+ *
+ * @return 妯℃澘鍒楄〃
+ */
+ public static VelocityContext prepareContext(GenTable genTable) {
+ String moduleName = genTable.getModuleName();
+ String businessName = genTable.getBusinessName();
+ String packageName = genTable.getPackageName();
+ String tplCategory = genTable.getTplCategory();
+ String functionName = genTable.getFunctionName();
+
+ VelocityContext velocityContext = new VelocityContext();
+ velocityContext.put("tplCategory", genTable.getTplCategory());
+ velocityContext.put("tableName", genTable.getTableName());
+ velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "銆愯濉啓鍔熻兘鍚嶇О銆�");
+ velocityContext.put("ClassName", genTable.getClassName());
+ velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
+ velocityContext.put("moduleName", StrUtil.toSymbolCase(genTable.getModuleName(), '-'));
+ velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
+ velocityContext.put("businessName", genTable.getBusinessName());
+ velocityContext.put("business_name", StrUtil.toUnderlineCase(genTable.getBusinessName()));
+ velocityContext.put("business__name", StrUtil.toSymbolCase(genTable.getBusinessName(), '-'));
+ velocityContext.put("businessname", StrUtil.toSymbolCase(genTable.getBusinessName(), ' '));
+ velocityContext.put("basePackage", getPackagePrefix(packageName));
+ velocityContext.put("packageName", packageName);
+ velocityContext.put("author", genTable.getFunctionAuthor());
+ velocityContext.put("datetime", DateUtils.getDate());
+ velocityContext.put("pkColumn", genTable.getPkColumn());
+ velocityContext.put("importList", getImportList(genTable));
+ velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
+ velocityContext.put("dicts", getDicts(genTable));
+ velocityContext.put("dictList", getDictList(genTable));
+ velocityContext.put("pkColumn", genTable.getPkColumn());
+ velocityContext.put("columns", genTable.getColumns());
+ velocityContext.put("table", genTable);
+ velocityContext.put("StrUtil", new StrUtil());
+ setMenuVelocityContext(velocityContext, genTable);
+ if (GenConstants.TPL_TREE.equals(tplCategory)) {
+ setTreeVelocityContext(velocityContext, genTable);
+ }
+ return velocityContext;
+ }
+
+ public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) {
+ String options = genTable.getOptions();
+ Dict paramsObj = JsonUtils.parseMap(options);
+ String parentMenuId = getParentMenuId(paramsObj);
+ context.put("parentMenuId", parentMenuId);
+ }
+
+ public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) {
+ String options = genTable.getOptions();
+ Dict paramsObj = JsonUtils.parseMap(options);
+ String treeCode = getTreecode(paramsObj);
+ String treeParentCode = getTreeParentCode(paramsObj);
+ String treeName = getTreeName(paramsObj);
+
+ context.put("treeCode", treeCode);
+ context.put("treeParentCode", treeParentCode);
+ context.put("treeName", treeName);
+ context.put("expandColumn", getExpandColumn(genTable));
+ if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
+ context.put("tree_parent_code", paramsObj.get(GenConstants.TREE_PARENT_CODE));
+ }
+ if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
+ context.put("tree_name", paramsObj.get(GenConstants.TREE_NAME));
+ }
+ }
+
+ /**
+ * 鑾峰彇妯℃澘淇℃伅
+ *
+ * @return 妯℃澘鍒楄〃
+ */
+ public static List<String> getTemplateList(String tplCategory) {
+ List<String> templates = new ArrayList<>();
+ templates.add("vm/java/domain.java.vm");
+ templates.add("vm/java/vo.java.vm");
+ templates.add("vm/java/bo.java.vm");
+ templates.add("vm/java/mapper.java.vm");
+ templates.add("vm/java/service.java.vm");
+ templates.add("vm/java/serviceImpl.java.vm");
+ templates.add("vm/java/controller.java.vm");
+ templates.add("vm/xml/mapper.xml.vm");
+ DataBaseType dataBaseType = DataBaseHelper.getDataBaseType();
+ if (dataBaseType.isOracle()) {
+ templates.add("vm/sql/oracle/sql.vm");
+ } else if (dataBaseType.isPostgreSql()) {
+ templates.add("vm/sql/postgres/sql.vm");
+ } else if (dataBaseType.isSqlServer()) {
+ templates.add("vm/sql/sqlserver/sql.vm");
+ } else {
+ templates.add("vm/sql/sql.vm");
+ }
+ templates.add("vm/soy/typings/api.d.ts.vm");
+ templates.add("vm/soy/api/api.ts.vm");
+ templates.add("vm/soy/modules/search.vue.vm");
+ templates.add("vm/soy/modules/operate-drawer.vue.vm");
+ if (GenConstants.TPL_CRUD.equals(tplCategory)) {
+ templates.add("vm/soy/index.vue.vm");
+ } else if (GenConstants.TPL_TREE.equals(tplCategory)) {
+ templates.add("vm/soy/index-tree.vue.vm");
+ }
+ return templates;
+ }
+
+ /**
+ * 鑾峰彇鏂囦欢鍚�
+ */
+ public static String getFileName(String template, GenTable genTable) {
+ // 鏂囦欢鍚嶇О
+ String fileName = "";
+ // 鍖呰矾寰�
+ String packageName = genTable.getPackageName();
+ // 妯″潡鍚�
+ String moduleName = genTable.getModuleName();
+ // 澶у啓绫诲悕
+ String className = genTable.getClassName();
+ // 涓氬姟鍚嶇О
+ String businessName = genTable.getBusinessName();
+
+ String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
+ String mybatisPath = MYBATIS_PATH + "/" + moduleName;
+ String soybeanPath = "soy";
+ String soybeanModuleName = StrUtil.toSymbolCase(moduleName, '-');
+ if (template.contains("domain.java.vm")) {
+ fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
+ }
+ if (template.contains("vo.java.vm")) {
+ fileName = StringUtils.format("{}/domain/vo/{}Vo.java", javaPath, className);
+ }
+ if (template.contains("bo.java.vm")) {
+ fileName = StringUtils.format("{}/domain/bo/{}Bo.java", javaPath, className);
+ }
+ if (template.contains("mapper.java.vm")) {
+ fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className);
+ } else if (template.contains("service.java.vm")) {
+ fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className);
+ } else if (template.contains("serviceImpl.java.vm")) {
+ fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className);
+ } else if (template.contains("controller.java.vm")) {
+ fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className);
+ } else if (template.contains("mapper.xml.vm")) {
+ fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className);
+ } else if (template.contains("sql.vm")) {
+ fileName = businessName + "Menu.sql";
+ } else if (template.contains("index.vue.vm")) {
+ fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
+ } else if (template.contains("index-tree.vue.vm")) {
+ fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
+ } else if (template.contains("api.d.ts.vm")) {
+ fileName = StringUtils.format("{}/typings/api/{}.{}.api.d.ts", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
+ } else if (template.contains("api.ts.vm")) {
+ fileName = StringUtils.format("{}/service/api/{}/{}.ts", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
+ } else if (template.contains("search.vue.vm")) {
+ fileName = StringUtils.format("{}/views/{}/{}/modules/{}-search.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
+ } else if (template.contains("operate-drawer.vue.vm")) {
+ fileName = StringUtils.format("{}/views/{}/{}/modules/{}-operate-drawer.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
+ }
+ return fileName;
+ }
+
+ /**
+ * 鑾峰彇鍖呭墠缂�
+ *
+ * @param packageName 鍖呭悕绉�
+ * @return 鍖呭墠缂�鍚嶇О
+ */
+ public static String getPackagePrefix(String packageName) {
+ int lastIndex = packageName.lastIndexOf(".");
+ return StringUtils.substring(packageName, 0, lastIndex);
+ }
+
+ /**
+ * 鏍规嵁鍒楃被鍨嬭幏鍙栧鍏ュ寘
+ *
+ * @param genTable 涓氬姟琛ㄥ璞�
+ * @return 杩斿洖闇�瑕佸鍏ョ殑鍖呭垪琛�
+ */
+ public static HashSet<String> getImportList(GenTable genTable) {
+ List<GenTableColumn> columns = genTable.getColumns();
+ HashSet<String> importList = new HashSet<>();
+ for (GenTableColumn column : columns) {
+ if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) {
+ importList.add("java.util.Date");
+ importList.add("com.fasterxml.jackson.annotation.JsonFormat");
+ } else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
+ importList.add("java.math.BigDecimal");
+ } else if (!column.isSuperColumn() && "imageUpload".equals(column.getHtmlType())) {
+ importList.add("org.dromara.common.translation.annotation.Translation");
+ importList.add("org.dromara.common.translation.constant.TransConstant");
+ }
+ }
+ return importList;
+ }
+
+ /**
+ * 鏍规嵁鍒楃被鍨嬭幏鍙栧瓧鍏哥粍
+ *
+ * @param genTable 涓氬姟琛ㄥ璞�
+ * @return 杩斿洖瀛楀吀缁�
+ */
+ public static String getDicts(GenTable genTable) {
+ List<GenTableColumn> columns = genTable.getColumns();
+ Set<String> dicts = new HashSet<>();
+ addDicts(dicts, columns);
+ return StringUtils.join(dicts, ", ");
+ }
+
+ /**
+ * 娣诲姞瀛楀吀鍒楄〃
+ *
+ * @param dicts 瀛楀吀鍒楄〃
+ * @param columns 鍒楅泦鍚�
+ */
+ public static void addDicts(Set<String> dicts, List<GenTableColumn> columns) {
+ for (GenTableColumn column : columns) {
+ if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
+ column.getHtmlType(),
+ GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX)) {
+ dicts.add("'" + column.getDictType() + "'");
+ }
+ }
+ }
+
+ /**
+ * 鏍规嵁鍒楃被鍨嬭幏鍙栧瓧鍏哥粍
+ *
+ * @param genTable 涓氬姟琛ㄥ璞�
+ * @return 杩斿洖瀛楀吀缁�
+ */
+ public static Set<Map<String, Object>> getDictList(GenTable genTable) {
+ List<GenTableColumn> columns = genTable.getColumns();
+ Set<Map<String, Object>> dicts = new HashSet<>();
+ addDictList(dicts, columns);
+ return dicts;
+ }
+
+ /**
+ * 娣诲姞瀛楀吀鍒楄〃
+ *
+ * @param dicts 瀛楀吀鍒楄〃
+ * @param columns 鍒楅泦鍚�
+ */
+ public static void addDictList(Set<Map<String, Object>> dicts, List<GenTableColumn> columns) {
+ for (GenTableColumn column : columns) {
+ if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
+ column.getHtmlType(),
+ GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX)) {
+ Map<String, Object> dict = new HashMap<>();
+ dict.put("type", column.getDictType());
+ dict.put("name", StringUtils.toCamelCase(column.getDictType()));
+ dict.put("immediate", column.isList());
+ dicts.add(dict);
+ }
+ }
+ }
+
+ /**
+ * 鑾峰彇鏉冮檺鍓嶇紑
+ *
+ * @param moduleName 妯″潡鍚嶇О
+ * @param businessName 涓氬姟鍚嶇О
+ * @return 杩斿洖鏉冮檺鍓嶇紑
+ */
+ public static String getPermissionPrefix(String moduleName, String businessName) {
+ return StringUtils.format("{}:{}", moduleName, businessName);
+ }
+
+ /**
+ * 鑾峰彇涓婄骇鑿滃崟ID瀛楁
+ *
+ * @param paramsObj 鐢熸垚鍏朵粬閫夐」
+ * @return 涓婄骇鑿滃崟ID瀛楁
+ */
+ public static String getParentMenuId(Dict paramsObj) {
+ if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
+ && StringUtils.isNotEmpty(paramsObj.getStr(GenConstants.PARENT_MENU_ID))) {
+ return paramsObj.getStr(GenConstants.PARENT_MENU_ID);
+ }
+ return DEFAULT_PARENT_MENU_ID;
+ }
+
+ /**
+ * 鑾峰彇鏍戠紪鐮�
+ *
+ * @param paramsObj 鐢熸垚鍏朵粬閫夐」
+ * @return 鏍戠紪鐮�
+ */
+ public static String getTreecode(Map<String, Object> paramsObj) {
+ if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_CODE)) {
+ return StringUtils.toCamelCase(Convert.toStr(paramsObj.get(GenConstants.TREE_CODE)));
+ }
+ return StringUtils.EMPTY;
+ }
+
+ /**
+ * 鑾峰彇鏍戠埗缂栫爜
+ *
+ * @param paramsObj 鐢熸垚鍏朵粬閫夐」
+ * @return 鏍戠埗缂栫爜
+ */
+ public static String getTreeParentCode(Dict paramsObj) {
+ if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
+ return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_PARENT_CODE));
+ }
+ return StringUtils.EMPTY;
+ }
+
+ /**
+ * 鑾峰彇鏍戝悕绉�
+ *
+ * @param paramsObj 鐢熸垚鍏朵粬閫夐」
+ * @return 鏍戝悕绉�
+ */
+ public static String getTreeName(Dict paramsObj) {
+ if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_NAME)) {
+ return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_NAME));
+ }
+ return StringUtils.EMPTY;
+ }
+
+ /**
+ * 鑾峰彇闇�瑕佸湪鍝竴鍒椾笂闈㈡樉绀哄睍寮�鎸夐挳
+ *
+ * @param genTable 涓氬姟琛ㄥ璞�
+ * @return 灞曞紑鎸夐挳鍒楀簭鍙�
+ */
+ public static int getExpandColumn(GenTable genTable) {
+ String options = genTable.getOptions();
+ Dict paramsObj = JsonUtils.parseMap(options);
+ String treeName = paramsObj.getStr(GenConstants.TREE_NAME);
+ int num = 0;
+ for (GenTableColumn column : genTable.getColumns()) {
+ if (column.isList()) {
+ num++;
+ String columnName = column.getColumnName();
+ if (columnName.equals(treeName)) {
+ break;
+ }
+ }
+ }
+ return num;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils2.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils2.java
new file mode 100755
index 0000000..9a12068
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils2.java
@@ -0,0 +1,343 @@
+package org.dromara.generator.util;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.velocity.VelocityContext;
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.mybatis.enums.DataBaseType;
+import org.dromara.common.mybatis.helper.DataBaseHelper;
+import org.dromara.generator.constant.GenConstants;
+import org.dromara.generator.domain.GenTable;
+import org.dromara.generator.domain.GenTableColumn;
+
+import java.util.*;
+
+/**
+ * 妯℃澘澶勭悊宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class VelocityUtils2 {
+
+ /**
+ * 椤圭洰绌洪棿璺緞
+ */
+ private static final String PROJECT_PATH = "main/java";
+
+ /**
+ * mybatis绌洪棿璺緞
+ */
+ private static final String MYBATIS_PATH = "main/resources/mapper";
+
+ /**
+ * 榛樿涓婄骇鑿滃崟锛岀郴缁熷伐鍏�
+ */
+ private static final String DEFAULT_PARENT_MENU_ID = "3";
+
+ /**
+ * 璁剧疆妯℃澘鍙橀噺淇℃伅
+ *
+ * @return 妯℃澘鍒楄〃
+ */
+ public static VelocityContext prepareContext(GenTable genTable) {
+ String moduleName = genTable.getModuleName();
+ String businessName = genTable.getBusinessName();
+ String packageName = genTable.getPackageName();
+ String tplCategory = genTable.getTplCategory();
+ String functionName = genTable.getFunctionName();
+
+ VelocityContext velocityContext = new VelocityContext();
+ velocityContext.put("tplCategory", genTable.getTplCategory());
+ velocityContext.put("tableName", genTable.getTableName());
+ velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "銆愯濉啓鍔熻兘鍚嶇О銆�");
+ velocityContext.put("ClassName", genTable.getClassName());
+ velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
+ velocityContext.put("moduleName", genTable.getModuleName());
+ velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
+ velocityContext.put("businessName", genTable.getBusinessName());
+ velocityContext.put("basePackage", getPackagePrefix(packageName));
+ velocityContext.put("packageName", packageName);
+ velocityContext.put("author", genTable.getFunctionAuthor());
+ velocityContext.put("datetime", DateUtils.getDate());
+ velocityContext.put("pkColumn", genTable.getPkColumn());
+ velocityContext.put("importList", getImportList(genTable));
+ velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
+ velocityContext.put("columns", genTable.getColumns());
+ velocityContext.put("table", genTable);
+ velocityContext.put("dicts", getDicts(genTable));
+ setMenuVelocityContext(velocityContext, genTable);
+ if (GenConstants.TPL_TREE.equals(tplCategory)) {
+ setTreeVelocityContext(velocityContext, genTable);
+ }
+ return velocityContext;
+ }
+
+ public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) {
+ String options = genTable.getOptions();
+ Dict paramsObj = JsonUtils.parseMap(options);
+ String parentMenuId = getParentMenuId(paramsObj);
+ context.put("parentMenuId", parentMenuId);
+ }
+
+ public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) {
+ String options = genTable.getOptions();
+ Dict paramsObj = JsonUtils.parseMap(options);
+ String treeCode = getTreecode(paramsObj);
+ String treeParentCode = getTreeParentCode(paramsObj);
+ String treeName = getTreeName(paramsObj);
+
+ context.put("treeCode", treeCode);
+ context.put("treeParentCode", treeParentCode);
+ context.put("treeName", treeName);
+ context.put("expandColumn", getExpandColumn(genTable));
+ if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
+ context.put("tree_parent_code", paramsObj.get(GenConstants.TREE_PARENT_CODE));
+ }
+ if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
+ context.put("tree_name", paramsObj.get(GenConstants.TREE_NAME));
+ }
+ }
+
+ /**
+ * 鑾峰彇妯℃澘淇℃伅
+ *
+ * @return 妯℃澘鍒楄〃
+ */
+ public static List<String> getTemplateList(String tplCategory) {
+ List<String> templates = new ArrayList<>();
+ templates.add("vm/java/domain.java.vm");
+ templates.add("vm/java/vo.java.vm");
+ templates.add("vm/java/bo.java.vm");
+ templates.add("vm/java/mapper.java.vm");
+ templates.add("vm/java/service.java.vm");
+ templates.add("vm/java/serviceImpl.java.vm");
+ templates.add("vm/java/controller.java.vm");
+ templates.add("vm/xml/mapper.xml.vm");
+ DataBaseType dataBaseType = DataBaseHelper.getDataBaseType();
+ if (dataBaseType.isOracle()) {
+ templates.add("vm/sql/oracle/sql.vm");
+ } else if (dataBaseType.isPostgreSql()) {
+ templates.add("vm/sql/postgres/sql.vm");
+ } else if (dataBaseType.isSqlServer()) {
+ templates.add("vm/sql/sqlserver/sql.vm");
+ } else {
+ templates.add("vm/sql/sql.vm");
+ }
+ templates.add("vm/ts/api.ts.vm");
+ templates.add("vm/ts/types.ts.vm");
+ if (GenConstants.TPL_CRUD.equals(tplCategory)) {
+ templates.add("vm/vue/index.vue.vm");
+ } else if (GenConstants.TPL_TREE.equals(tplCategory)) {
+ templates.add("vm/vue/index-tree.vue.vm");
+ }
+ return templates;
+ }
+
+ /**
+ * 鑾峰彇鏂囦欢鍚�
+ */
+ public static String getFileName(String template, GenTable genTable) {
+ // 鏂囦欢鍚嶇О
+ String fileName = "";
+ // 鍖呰矾寰�
+ String packageName = genTable.getPackageName();
+ // 妯″潡鍚�
+ String moduleName = genTable.getModuleName();
+ // 澶у啓绫诲悕
+ String className = genTable.getClassName();
+ // 涓氬姟鍚嶇О
+ String businessName = genTable.getBusinessName();
+
+ String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
+ String mybatisPath = MYBATIS_PATH + "/" + moduleName;
+ String vuePath = "vue";
+
+ if (template.contains("domain.java.vm")) {
+ fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
+ }
+ if (template.contains("vo.java.vm")) {
+ fileName = StringUtils.format("{}/domain/vo/{}Vo.java", javaPath, className);
+ }
+ if (template.contains("bo.java.vm")) {
+ fileName = StringUtils.format("{}/domain/bo/{}Bo.java", javaPath, className);
+ }
+ if (template.contains("mapper.java.vm")) {
+ fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className);
+ } else if (template.contains("service.java.vm")) {
+ fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className);
+ } else if (template.contains("serviceImpl.java.vm")) {
+ fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className);
+ } else if (template.contains("controller.java.vm")) {
+ fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className);
+ } else if (template.contains("mapper.xml.vm")) {
+ fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className);
+ } else if (template.contains("sql.vm")) {
+ fileName = businessName + "Menu.sql";
+ } else if (template.contains("api.ts.vm")) {
+ fileName = StringUtils.format("{}/api/{}/{}/index.ts", vuePath, moduleName, businessName);
+ } else if (template.contains("types.ts.vm")) {
+ fileName = StringUtils.format("{}/api/{}/{}/types.ts", vuePath, moduleName, businessName);
+ } else if (template.contains("index.vue.vm")) {
+ fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
+ } else if (template.contains("index-tree.vue.vm")) {
+ fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
+ }
+ return fileName;
+ }
+
+ /**
+ * 鑾峰彇鍖呭墠缂�
+ *
+ * @param packageName 鍖呭悕绉�
+ * @return 鍖呭墠缂�鍚嶇О
+ */
+ public static String getPackagePrefix(String packageName) {
+ int lastIndex = packageName.lastIndexOf(".");
+ return StringUtils.substring(packageName, 0, lastIndex);
+ }
+
+ /**
+ * 鏍规嵁鍒楃被鍨嬭幏鍙栧鍏ュ寘
+ *
+ * @param genTable 涓氬姟琛ㄥ璞�
+ * @return 杩斿洖闇�瑕佸鍏ョ殑鍖呭垪琛�
+ */
+ public static HashSet<String> getImportList(GenTable genTable) {
+ List<GenTableColumn> columns = genTable.getColumns();
+ HashSet<String> importList = new HashSet<>();
+ for (GenTableColumn column : columns) {
+ if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) {
+ importList.add("java.util.Date");
+ importList.add("com.fasterxml.jackson.annotation.JsonFormat");
+ } else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
+ importList.add("java.math.BigDecimal");
+ } else if (!column.isSuperColumn() && "imageUpload".equals(column.getHtmlType())) {
+ importList.add("org.dromara.common.translation.annotation.Translation");
+ importList.add("org.dromara.common.translation.constant.TransConstant");
+ }
+ }
+ return importList;
+ }
+
+ /**
+ * 鏍规嵁鍒楃被鍨嬭幏鍙栧瓧鍏哥粍
+ *
+ * @param genTable 涓氬姟琛ㄥ璞�
+ * @return 杩斿洖瀛楀吀缁�
+ */
+ public static String getDicts(GenTable genTable) {
+ List<GenTableColumn> columns = genTable.getColumns();
+ Set<String> dicts = new HashSet<>();
+ addDicts(dicts, columns);
+ return StringUtils.join(dicts, ", ");
+ }
+
+ /**
+ * 娣诲姞瀛楀吀鍒楄〃
+ *
+ * @param dicts 瀛楀吀鍒楄〃
+ * @param columns 鍒楅泦鍚�
+ */
+ public static void addDicts(Set<String> dicts, List<GenTableColumn> columns) {
+ for (GenTableColumn column : columns) {
+ if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
+ column.getHtmlType(),
+ new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) {
+ dicts.add("'" + column.getDictType() + "'");
+ }
+ }
+ }
+
+ /**
+ * 鑾峰彇鏉冮檺鍓嶇紑
+ *
+ * @param moduleName 妯″潡鍚嶇О
+ * @param businessName 涓氬姟鍚嶇О
+ * @return 杩斿洖鏉冮檺鍓嶇紑
+ */
+ public static String getPermissionPrefix(String moduleName, String businessName) {
+ return StringUtils.format("{}:{}", moduleName, businessName);
+ }
+
+ /**
+ * 鑾峰彇涓婄骇鑿滃崟ID瀛楁
+ *
+ * @param paramsObj 鐢熸垚鍏朵粬閫夐」
+ * @return 涓婄骇鑿滃崟ID瀛楁
+ */
+ public static String getParentMenuId(Dict paramsObj) {
+ if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
+ && StringUtils.isNotEmpty(paramsObj.getStr(GenConstants.PARENT_MENU_ID))) {
+ return paramsObj.getStr(GenConstants.PARENT_MENU_ID);
+ }
+ return DEFAULT_PARENT_MENU_ID;
+ }
+
+ /**
+ * 鑾峰彇鏍戠紪鐮�
+ *
+ * @param paramsObj 鐢熸垚鍏朵粬閫夐」
+ * @return 鏍戠紪鐮�
+ */
+ public static String getTreecode(Map<String, Object> paramsObj) {
+ if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_CODE)) {
+ return StringUtils.toCamelCase(Convert.toStr(paramsObj.get(GenConstants.TREE_CODE)));
+ }
+ return StringUtils.EMPTY;
+ }
+
+ /**
+ * 鑾峰彇鏍戠埗缂栫爜
+ *
+ * @param paramsObj 鐢熸垚鍏朵粬閫夐」
+ * @return 鏍戠埗缂栫爜
+ */
+ public static String getTreeParentCode(Dict paramsObj) {
+ if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
+ return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_PARENT_CODE));
+ }
+ return StringUtils.EMPTY;
+ }
+
+ /**
+ * 鑾峰彇鏍戝悕绉�
+ *
+ * @param paramsObj 鐢熸垚鍏朵粬閫夐」
+ * @return 鏍戝悕绉�
+ */
+ public static String getTreeName(Dict paramsObj) {
+ if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_NAME)) {
+ return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_NAME));
+ }
+ return StringUtils.EMPTY;
+ }
+
+ /**
+ * 鑾峰彇闇�瑕佸湪鍝竴鍒椾笂闈㈡樉绀哄睍寮�鎸夐挳
+ *
+ * @param genTable 涓氬姟琛ㄥ璞�
+ * @return 灞曞紑鎸夐挳鍒楀簭鍙�
+ */
+ public static int getExpandColumn(GenTable genTable) {
+ String options = genTable.getOptions();
+ Dict paramsObj = JsonUtils.parseMap(options);
+ String treeName = paramsObj.getStr(GenConstants.TREE_NAME);
+ int num = 0;
+ for (GenTableColumn column : genTable.getColumns()) {
+ if (column.isList()) {
+ num++;
+ String columnName = column.getColumnName();
+ if (columnName.equals(treeName)) {
+ break;
+ }
+ }
+ }
+ return num;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/.DS_Store b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/.DS_Store
new file mode 100755
index 0000000..5301708
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/.DS_Store
Binary files differ
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/generator.yml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/generator.yml
new file mode 100755
index 0000000..d779d97
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/generator.yml
@@ -0,0 +1,10 @@
+# 浠g爜鐢熸垚
+gen:
+ # 浣滆��
+ author: Lion Li
+ # 榛樿鐢熸垚鍖呰矾寰� system 闇�鏀规垚鑷繁鐨勬ā鍧楀悕绉� 濡� system monitor tool
+ packageName: org.dromara.system
+ # 鑷姩鍘婚櫎琛ㄥ墠缂�锛岄粯璁ゆ槸false
+ autoRemovePre: false
+ # 琛ㄥ墠缂�锛堢敓鎴愮被鍚嶄笉浼氬寘鍚〃鍓嶇紑锛屽涓敤閫楀彿鍒嗛殧锛�
+ tablePrefix: sys_
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml
new file mode 100755
index 0000000..fc1c610
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.generator.mapper.GenTableColumnMapper">
+
+ <resultMap type="org.dromara.generator.domain.GenTableColumn" id="GenTableColumnResult">
+ </resultMap>
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml
new file mode 100755
index 0000000..78aa852
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.generator.mapper.GenTableMapper">
+
+ <!-- 澶氱粨鏋勫祵濂楄嚜鍔ㄦ槧灏勯渶甯︿笂姣忎釜瀹炰綋鐨勪富閿甶d 鍚﹀垯鏄犲皠浼氬け璐� -->
+ <resultMap type="org.dromara.generator.domain.GenTable" id="GenTableResult">
+ <id property="tableId" column="table_id" />
+ <collection property="columns" javaType="java.util.List" resultMap="GenTableColumnResult" />
+ </resultMap>
+
+ <resultMap type="org.dromara.generator.domain.GenTableColumn" id="GenTableColumnResult">
+ <id property="columnId" column="column_id"/>
+ </resultMap>
+
+ <sql id="genSelect">
+ SELECT t.table_id, t.data_name, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark,
+ c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
+ FROM gen_table t
+ LEFT JOIN gen_table_column c ON t.table_id = c.table_id
+ </sql>
+
+ <select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">
+ <include refid="genSelect"/>
+ where t.table_id = #{tableId} order by c.sort
+ </select>
+
+ <select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult">
+ <include refid="genSelect"/>
+ where t.table_name = #{tableName} order by c.sort
+ </select>
+
+ <select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult">
+ <include refid="genSelect"/>
+ order by c.sort
+ </select>
+
+ <select id="selectTableNameList" resultType="java.lang.String">
+ select table_name from gen_table where data_name = #{dataName,jdbcType=VARCHAR}
+ </select>
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/package-info.md b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/package-info.md
new file mode 100755
index 0000000..c938b1e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/mapper/package-info.md
@@ -0,0 +1,3 @@
+java鍖呬娇鐢� `.` 鍒嗗壊 resource 鐩綍浣跨敤 `/` 鍒嗗壊
+<br>
+姝ゆ枃浠剁洰鐨� 闃叉鏂囦欢澶圭矘杩炴壘涓嶅埌 `xml` 鏂囦欢
\ No newline at end of file
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/.DS_Store b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/.DS_Store
new file mode 100755
index 0000000..8770f2e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/.DS_Store
Binary files differ
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/bo.java.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/bo.java.vm
new file mode 100755
index 0000000..511d37c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/bo.java.vm
@@ -0,0 +1,50 @@
+package ${packageName}.domain.bo;
+
+import ${packageName}.domain.${ClassName};
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+#foreach ($import in $importList)
+import ${import};
+#end
+
+/**
+ * ${functionName}涓氬姟瀵硅薄 ${tableName}
+ *
+ * @author ${author}
+ * @date ${datetime}
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ${ClassName}.class, reverseConvertGenerate = false)
+public class ${ClassName}Bo extends BaseEntity {
+
+#foreach ($column in $columns)
+#if(!$table.isSuperColumn($column.javaField) && ($column.query || $column.insert || $column.edit))
+ /**
+ * $column.columnComment
+ */
+#if($column.insert && $column.edit)
+#set($Group="AddGroup.class, EditGroup.class")
+#elseif($column.insert)
+#set($Group="AddGroup.class")
+#elseif($column.edit)
+#set($Group="EditGroup.class")
+#end
+#if($column.required)
+#if($column.javaType == 'String')
+ @NotBlank(message = "$column.columnComment涓嶈兘涓虹┖", groups = { $Group })
+#else
+ @NotNull(message = "$column.columnComment涓嶈兘涓虹┖", groups = { $Group })
+#end
+#end
+ private $column.javaType $column.javaField;
+
+#end
+#end
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/controller.java.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/controller.java.vm
new file mode 100755
index 0000000..6438971
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/controller.java.vm
@@ -0,0 +1,115 @@
+package ${packageName}.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import ${packageName}.domain.vo.${ClassName}Vo;
+import ${packageName}.domain.bo.${ClassName}Bo;
+import ${packageName}.service.I${ClassName}Service;
+#if($table.crud)
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+#elseif($table.tree)
+#end
+
+/**
+ * ${functionName}
+ *
+ * @author ${author}
+ * @date ${datetime}
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/${moduleName}/${businessName}")
+public class ${ClassName}Controller extends BaseController {
+
+ private final I${ClassName}Service ${className}Service;
+
+ /**
+ * 鏌ヨ${functionName}鍒楄〃
+ */
+ @SaCheckPermission("${permissionPrefix}:list")
+ @GetMapping("/list")
+#if($table.crud)
+ public TableDataInfo<${ClassName}Vo> list(${ClassName}Bo bo, PageQuery pageQuery) {
+ return ${className}Service.queryPageList(bo, pageQuery);
+ }
+#elseif($table.tree)
+ public R<List<${ClassName}Vo>> list(${ClassName}Bo bo) {
+ List<${ClassName}Vo> list = ${className}Service.queryList(bo);
+ return R.ok(list);
+ }
+#end
+
+ /**
+ * 瀵煎嚭${functionName}鍒楄〃
+ */
+ @SaCheckPermission("${permissionPrefix}:export")
+ @Log(title = "${functionName}", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(${ClassName}Bo bo, HttpServletResponse response) {
+ List<${ClassName}Vo> list = ${className}Service.queryList(bo);
+ ExcelUtil.exportExcel(list, "${functionName}", ${ClassName}Vo.class, response);
+ }
+
+ /**
+ * 鑾峰彇${functionName}璇︾粏淇℃伅
+ *
+ * @param ${pkColumn.javaField} 涓婚敭
+ */
+ @SaCheckPermission("${permissionPrefix}:query")
+ @GetMapping("/{${pkColumn.javaField}}")
+ public R<${ClassName}Vo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable ${pkColumn.javaType} ${pkColumn.javaField}) {
+ return R.ok(${className}Service.queryById(${pkColumn.javaField}));
+ }
+
+ /**
+ * 鏂板${functionName}
+ */
+ @SaCheckPermission("${permissionPrefix}:add")
+ @Log(title = "${functionName}", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R<Void> add(@Validated(AddGroup.class) @RequestBody ${ClassName}Bo bo) {
+ return toAjax(${className}Service.insertByBo(bo));
+ }
+
+ /**
+ * 淇敼${functionName}
+ */
+ @SaCheckPermission("${permissionPrefix}:edit")
+ @Log(title = "${functionName}", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R<Void> edit(@Validated(EditGroup.class) @RequestBody ${ClassName}Bo bo) {
+ return toAjax(${className}Service.updateByBo(bo));
+ }
+
+ /**
+ * 鍒犻櫎${functionName}
+ *
+ * @param ${pkColumn.javaField}s 涓婚敭涓�
+ */
+ @SaCheckPermission("${permissionPrefix}:remove")
+ @Log(title = "${functionName}", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{${pkColumn.javaField}s}")
+ public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) {
+ return toAjax(${className}Service.deleteWithValidByIds(List.of(${pkColumn.javaField}s), true));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/domain.java.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/domain.java.vm
new file mode 100755
index 0000000..205fb73
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/domain.java.vm
@@ -0,0 +1,60 @@
+package ${packageName}.domain;
+
+#foreach ($column in $columns)
+#if($column.javaField=='tenantId')
+#set($IsTenant=1)
+#end
+#end
+#if($IsTenant==1)
+import org.dromara.common.tenant.core.TenantEntity;
+#else
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+#end
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+#foreach ($import in $importList)
+import ${import};
+#end
+
+import java.io.Serial;
+
+/**
+ * ${functionName}瀵硅薄 ${tableName}
+ *
+ * @author ${author}
+ * @date ${datetime}
+ */
+#if($IsTenant==1)
+#set($Entity="TenantEntity")
+#else
+#set($Entity="BaseEntity")
+#end
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("${tableName}")
+public class ${ClassName} extends ${Entity} {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+#foreach ($column in $columns)
+#if(!$table.isSuperColumn($column.javaField))
+ /**
+ * $column.columnComment
+ */
+#if($column.javaField=='delFlag')
+ @TableLogic
+#end
+#if($column.javaField=='version')
+ @Version
+#end
+#if($column.isPk==1)
+ @TableId(value = "$column.columnName")
+#end
+ private $column.javaType $column.javaField;
+
+#end
+#end
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm
new file mode 100755
index 0000000..0922401
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm
@@ -0,0 +1,15 @@
+package ${packageName}.mapper;
+
+import ${packageName}.domain.${ClassName};
+import ${packageName}.domain.vo.${ClassName}Vo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * ${functionName}Mapper鎺ュ彛
+ *
+ * @author ${author}
+ * @date ${datetime}
+ */
+public interface ${ClassName}Mapper extends BaseMapperPlus<${ClassName}, ${ClassName}Vo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/service.java.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/service.java.vm
new file mode 100755
index 0000000..4db9030
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/service.java.vm
@@ -0,0 +1,72 @@
+package ${packageName}.service;
+
+import ${packageName}.domain.vo.${ClassName}Vo;
+import ${packageName}.domain.bo.${ClassName}Bo;
+#if($table.crud)
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+#end
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * ${functionName}Service鎺ュ彛
+ *
+ * @author ${author}
+ * @date ${datetime}
+ */
+public interface I${ClassName}Service {
+
+ /**
+ * 鏌ヨ${functionName}
+ *
+ * @param ${pkColumn.javaField} 涓婚敭
+ * @return ${functionName}
+ */
+ ${ClassName}Vo queryById(${pkColumn.javaType} ${pkColumn.javaField});
+
+#if($table.crud)
+ /**
+ * 鍒嗛〉鏌ヨ${functionName}鍒楄〃
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return ${functionName}鍒嗛〉鍒楄〃
+ */
+ TableDataInfo<${ClassName}Vo> queryPageList(${ClassName}Bo bo, PageQuery pageQuery);
+#end
+
+ /**
+ * 鏌ヨ绗﹀悎鏉′欢鐨�${functionName}鍒楄〃
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return ${functionName}鍒楄〃
+ */
+ List<${ClassName}Vo> queryList(${ClassName}Bo bo);
+
+ /**
+ * 鏂板${functionName}
+ *
+ * @param bo ${functionName}
+ * @return 鏄惁鏂板鎴愬姛
+ */
+ Boolean insertByBo(${ClassName}Bo bo);
+
+ /**
+ * 淇敼${functionName}
+ *
+ * @param bo ${functionName}
+ * @return 鏄惁淇敼鎴愬姛
+ */
+ Boolean updateByBo(${ClassName}Bo bo);
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄�${functionName}淇℃伅
+ *
+ * @param ids 寰呭垹闄ょ殑涓婚敭闆嗗悎
+ * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+ * @return 鏄惁鍒犻櫎鎴愬姛
+ */
+ Boolean deleteWithValidByIds(Collection<${pkColumn.javaType}> ids, Boolean isValid);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm
new file mode 100755
index 0000000..be6c3bf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm
@@ -0,0 +1,160 @@
+package ${packageName}.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+#if($table.crud)
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+#end
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import ${packageName}.domain.bo.${ClassName}Bo;
+import ${packageName}.domain.vo.${ClassName}Vo;
+import ${packageName}.domain.${ClassName};
+import ${packageName}.mapper.${ClassName}Mapper;
+import ${packageName}.service.I${ClassName}Service;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * ${functionName}Service涓氬姟灞傚鐞�
+ *
+ * @author ${author}
+ * @date ${datetime}
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ${ClassName}ServiceImpl implements I${ClassName}Service {
+
+ private final ${ClassName}Mapper baseMapper;
+
+ /**
+ * 鏌ヨ${functionName}
+ *
+ * @param ${pkColumn.javaField} 涓婚敭
+ * @return ${functionName}
+ */
+ @Override
+ public ${ClassName}Vo queryById(${pkColumn.javaType} ${pkColumn.javaField}){
+ return baseMapper.selectVoById(${pkColumn.javaField});
+ }
+
+#if($table.crud)
+ /**
+ * 鍒嗛〉鏌ヨ${functionName}鍒楄〃
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return ${functionName}鍒嗛〉鍒楄〃
+ */
+ @Override
+ public TableDataInfo<${ClassName}Vo> queryPageList(${ClassName}Bo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper<${ClassName}> lqw = buildQueryWrapper(bo);
+ Page<${ClassName}Vo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+#end
+
+ /**
+ * 鏌ヨ绗﹀悎鏉′欢鐨�${functionName}鍒楄〃
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return ${functionName}鍒楄〃
+ */
+ @Override
+ public List<${ClassName}Vo> queryList(${ClassName}Bo bo) {
+ LambdaQueryWrapper<${ClassName}> lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper<${ClassName}> buildQueryWrapper(${ClassName}Bo bo) {
+ Map<String, Object> params = bo.getParams();
+ LambdaQueryWrapper<${ClassName}> lqw = Wrappers.lambdaQuery();
+#foreach($column in $columns)
+#if($column.query)
+#set($queryType=$column.queryType)
+#set($javaField=$column.javaField)
+#set($javaType=$column.javaType)
+#set($columnName=$column.columnName)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set($mpMethod=$column.queryType.toLowerCase())
+#if($queryType != 'BETWEEN')
+#if($javaType == 'String')
+#set($condition='StringUtils.isNotBlank(bo.get'+$AttrName+'())')
+#else
+#set($condition='bo.get'+$AttrName+'() != null')
+#end
+ lqw.$mpMethod($condition, ${ClassName}::get$AttrName, bo.get$AttrName());
+#else
+ lqw.between(params.get("begin$AttrName") != null && params.get("end$AttrName") != null,
+ ${ClassName}::get$AttrName ,params.get("begin$AttrName"), params.get("end$AttrName"));
+#end
+#end
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#if($column.isPk==1)
+ lqw.orderByAsc(${ClassName}::get$AttrName);
+#end
+#end
+ return lqw;
+ }
+
+ /**
+ * 鏂板${functionName}
+ *
+ * @param bo ${functionName}
+ * @return 鏄惁鏂板鎴愬姛
+ */
+ @Override
+ public Boolean insertByBo(${ClassName}Bo bo) {
+ ${ClassName} add = MapstructUtils.convert(bo, ${ClassName}.class);
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+#set($pk=$pkColumn.javaField.substring(0,1).toUpperCase() + ${pkColumn.javaField.substring(1)})
+ if (flag) {
+ bo.set$pk(add.get$pk());
+ }
+ return flag;
+ }
+
+ /**
+ * 淇敼${functionName}
+ *
+ * @param bo ${functionName}
+ * @return 鏄惁淇敼鎴愬姛
+ */
+ @Override
+ public Boolean updateByBo(${ClassName}Bo bo) {
+ ${ClassName} update = MapstructUtils.convert(bo, ${ClassName}.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+ */
+ private void validEntityBeforeSave(${ClassName} entity){
+ //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+ }
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄�${functionName}淇℃伅
+ *
+ * @param ids 寰呭垹闄ょ殑涓婚敭闆嗗悎
+ * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+ * @return 鏄惁鍒犻櫎鎴愬姛
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection<${pkColumn.javaType}> ids, Boolean isValid) {
+ if(isValid){
+ //TODO 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+ }
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/vo.java.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/vo.java.vm
new file mode 100755
index 0000000..480394c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/vo.java.vm
@@ -0,0 +1,66 @@
+package ${packageName}.domain.vo;
+
+#foreach ($import in $importList)
+import ${import};
+#end
+import ${packageName}.domain.${ClassName};
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * ${functionName}瑙嗗浘瀵硅薄 ${tableName}
+ *
+ * @author ${author}
+ * @date ${datetime}
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ${ClassName}.class)
+public class ${ClassName}Vo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+#foreach ($column in $columns)
+#if($column.list)
+ /**
+ * $column.columnComment
+ */
+#set($parentheseIndex=$column.columnComment.indexOf("锛�"))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if(${column.dictType} && ${column.dictType} != '')
+ @ExcelProperty(value = "${comment}", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "${column.dictType}")
+#elseif($parentheseIndex != -1)
+ @ExcelProperty(value = "${comment}", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(readConverterExp = "$column.readConverterExp()")
+#else
+ @ExcelProperty(value = "${comment}")
+#end
+ private $column.javaType $column.javaField;
+
+#if($column.htmlType == "imageUpload")
+ /**
+ * ${column.columnComment}Url
+ */
+ @Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "${column.javaField}")
+ private String ${column.javaField}Url;
+#end
+#end
+#end
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/api/api.ts.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/api/api.ts.vm
new file mode 100755
index 0000000..f910941
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/api/api.ts.vm
@@ -0,0 +1,41 @@
+import { request } from '@/service/request';
+#set($responseType = "")
+#if($tplCategory == "tree")
+ #set($responseType = "Api.${ModuleName}.${BusinessName}[]")
+#else
+ #set($responseType = "Api.${ModuleName}.${BusinessName}List")
+#end
+
+/** 鑾峰彇${functionName}鍒楄〃 */
+export function fetchGet${BusinessName}List (params?: Api.${ModuleName}.${BusinessName}SearchParams) {
+ return request<$responseType>({
+ url: '/${moduleName}/${businessName}/list',
+ method: 'get',
+ params
+ });
+}
+/** 鏂板${functionName} */
+export function fetchCreate${BusinessName} (data: Api.${ModuleName}.${BusinessName}OperateParams) {
+ return request<boolean>({
+ url: '/${moduleName}/${businessName}',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼${functionName} */
+export function fetchUpdate${BusinessName} (data: Api.${ModuleName}.${BusinessName}OperateParams) {
+ return request<boolean>({
+ url: '/${moduleName}/${businessName}',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎${functionName} */
+export function fetchBatchDelete${BusinessName} (${pkColumn.javaField}s: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/${moduleName}/${businessName}/${${pkColumn.javaField}s.join(',')}`,
+ method: 'delete'
+ });
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/index-tree.vue.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/index-tree.vue.vm
new file mode 100755
index 0000000..5a0a99a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/index-tree.vue.vm
@@ -0,0 +1,232 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import { fetchBatchDelete${BusinessName}, fetchGet${BusinessName}List } from '@/service/api/${moduleName}/${business__name}';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { treeTransform, useNaiveTreeTable, useTableOperate } from '@/hooks/common/table';
+import { useDownload } from '@/hooks/business/download';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import ${BusinessName}OperateDrawer from './modules/${business__name}-operate-drawer.vue';
+import ${BusinessName}Search from './modules/${business__name}-search.vue';
+#if($dictList && $dictList.size() > 0)
+import { useDict } from '@/hooks/business/dict';
+#end
+
+defineOptions({
+ name: '${BusinessName}List'
+});
+
+#if($dictList && $dictList.size() > 0)
+#foreach($dict in $dictList)
+useDict('${dict.type}'#if(!$dict.immediate), false#end);
+#end#end
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.$ModuleName.${BusinessName}SearchParams>({
+#foreach ($column in $columns)
+ #if($column.query)
+ $column.javaField: null#if($foreach.hasNext),#end
+ #end
+#end
+ params: {}
+});
+
+const {
+ columns,
+ columnChecks,
+ data,
+ rows,
+ getData,
+ loading,
+ expandedRowKeys,
+ isCollapse,
+ expandAll,
+ collapseAll,
+ scrollX
+} = useNaiveTreeTable({
+ keyField: '$pkColumn.javaField',
+ api: () => fetchGet${BusinessName}List(searchParams.value),
+ transform: response => treeTransform(response, { idField: '$pkColumn.javaField' }),
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+#foreach ($column in $columns)
+ #if($column.list)
+ {
+ key: '$column.javaField',
+ title: '$column.columnComment',
+ align: 'center',
+ minWidth: 120#if($column.dictType),
+ render(row) {
+ return <DictTag value={row.$column.javaField} dictCode="$column.dictType" />;
+ }#end
+ },
+ #end
+#end
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const addBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:add-2-rounded"
+ tooltipContent={$t('common.add')}
+ onClick={() => addInRow(row)}
+ />
+ );
+ };
+
+ const editBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.$pkColumn.javaField)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.$pkColumn.javaField)}
+ />
+ );
+ };
+
+ const buttons = [];
+ if (hasAuth('${moduleName}:${businessName}:add')) buttons.push(addBtn());
+ if (hasAuth('${moduleName}:${businessName}:edit')) buttons.push(editBtn());
+ if (hasAuth('${moduleName}:${businessName}:remove')) buttons.push(deleteBtn());
+
+ return (
+ <div class="flex-center gap-8px">
+ {buttons.map((btn, index) => (
+ <>
+ {index !== 0 && <NDivider vertical />}
+ {btn}
+ </>
+ ))}
+ </div>
+ );
+ }
+ }
+ ]
+});
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(rows, '$pkColumn.javaField', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDelete${BusinessName}(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete($pkColumn.javaField: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDelete${BusinessName}([$pkColumn.javaField]);
+ if (error) return;
+ onDeleted();
+}
+
+function edit($pkColumn.javaField: CommonType.IdType) {
+ handleEdit($pkColumn.javaField);
+}
+
+function addInRow(row: Api.$ModuleName.${BusinessName}) {
+ editingData.value = jsonClone(row);
+ handleAdd();
+}
+
+function handleExport() {
+ download('/${moduleName}/${businessName}/export', searchParams.value, `${functionName}_#[[${new Date().getTime()}]]#.xlsx`);
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <${BusinessName}Search v-model:model="searchParams" @search="getData" />
+ <NCard title="${functionName}鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('${moduleName}:${businessName}:add')"
+ :show-delete="hasAuth('${moduleName}:${businessName}:remove')"
+ :show-export="false"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ >
+ <template #prefix>
+ <NButton v-if="!isCollapse" :disabled="!data.length" size="small" @click="expandAll">
+ <template #icon>
+ <icon-quill-expand />
+ </template>
+ 鍏ㄩ儴灞曞紑
+ </NButton>
+ <NButton v-if="isCollapse" :disabled="!data.length" size="small" @click="collapseAll">
+ <template #icon>
+ <icon-quill-collapse />
+ </template>
+ 鍏ㄩ儴鏀惰捣
+ </NButton>
+ </template>
+ </TableHeaderOperation>
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ v-model:expanded-row-keys="expandedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.#foreach($column in $columns)#if($column.isPk == '1')$column.javaField#end#end"
+ class="sm:h-full"
+ />
+ <${BusinessName}OperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getData"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/index.vue.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/index.vue.vm
new file mode 100755
index 0000000..f1cc6dc
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/index.vue.vm
@@ -0,0 +1,198 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDelete${BusinessName}, fetchGet${BusinessName}List } from '@/service/api/${moduleName}/${business__name}';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import ${BusinessName}OperateDrawer from './modules/${business__name}-operate-drawer.vue';
+import ${BusinessName}Search from './modules/${business__name}-search.vue';
+#if($dictList && $dictList.size() > 0)
+import { useDict } from '@/hooks/business/dict';
+#end
+
+defineOptions({
+ name: '${BusinessName}List'
+});
+
+#if($dictList && $dictList.size() > 0)
+#foreach($dict in $dictList)
+useDict('${dict.type}'#if(!$dict.immediate), false#end);
+#end#end
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.$ModuleName.${BusinessName}SearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+#foreach ($column in $columns)
+ #if($column.query)
+ $column.javaField: null#if($foreach.hasNext),#end
+ #end
+#end
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGet${BusinessName}List(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+#foreach ($column in $columns)
+ #if($column.list)
+ {
+ key: '$column.javaField',
+ title: '$column.columnComment',
+ align: 'center',
+ minWidth: 120#if($column.dictType),
+ render(row) {
+ return <DictTag value={row.$column.javaField} dictCode="$column.dictType" />;
+ }#end
+ },
+ #end
+#end
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('${moduleName}:${businessName}:edit') || !hasAuth('${moduleName}:${businessName}:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('${moduleName}:${businessName}:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.$pkColumn.javaField)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('${moduleName}:${businessName}:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.$pkColumn.javaField)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+});
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, '$pkColumn.javaField', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDelete${BusinessName}(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete($pkColumn.javaField: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDelete${BusinessName}([$pkColumn.javaField]);
+ if (error) return;
+ onDeleted();
+}
+
+function edit($pkColumn.javaField: CommonType.IdType) {
+ handleEdit($pkColumn.javaField);
+}
+
+function handleExport() {
+ download('/${moduleName}/${businessName}/export', searchParams.value, `${functionName}_#[[${new Date().getTime()}]]#.xlsx`);
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <${BusinessName}Search v-model:model="searchParams" @search="getDataByPage" />
+ <NCard title="${functionName}鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('${moduleName}:${businessName}:add')"
+ :show-delete="hasAuth('${moduleName}:${businessName}:remove')"
+ :show-export="hasAuth('${moduleName}:${businessName}:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ />
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.$pkColumn.javaField"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <${BusinessName}OperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getDataByPage"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/modules/operate-drawer.vue.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/modules/operate-drawer.vue.vm
new file mode 100755
index 0000000..60725e4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/modules/operate-drawer.vue.vm
@@ -0,0 +1,257 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { fetchCreate${BusinessName}, fetchUpdate${BusinessName}#if($tplCategory == 'tree'), fetchGet${BusinessName}List#end } from '@/service/api/${moduleName}/${business__name}';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+#if($tplCategory == 'tree')
+import { handleTree } from '@/utils/common';
+#end
+import { $t } from '@/locales';
+
+defineOptions({
+ name: '${BusinessName}OperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.$ModuleName.${BusinessName} | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+#if($tplCategory == 'tree')
+
+const treeList = ref<Api.Demo.Tree[]>([]);
+#end
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: '鏂板${functionName}',
+ edit: '缂栬緫${functionName}'
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.$ModuleName.${BusinessName}OperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+#foreach($column in $columns)
+ #if($column.insert() || $column.isEdit())
+ ${column.javaField}:#if($column.javaType == 'String' || ($!column.dictType && $column.dictType != '')) ''#else null#end#if($foreach.hasNext),#end
+ #end
+#end
+ };
+}
+
+type RuleKey = Extract<
+ keyof Model,
+#foreach($column in $columns)
+#if($column.required)
+ | '$column.javaField'#if($foreach.hasNext)#end
+#end#end>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+#foreach($column in $columns)
+#if($column.required)
+ $column.javaField: createRequiredRule('${column.columnComment}涓嶈兘涓虹┖')#if($foreach.hasNext),#end
+#end
+#end
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+#if($tplCategory == 'tree')
+ model.value.$treeParentCode = props.rowData?.$treeCode || 0;
+#end
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ #set($operateColumns = [])
+ #foreach($column in $columns)#if($column.insert || $column.edit)#set($dummy = $operateColumns.add($column))#end#end
+ const { #foreach($column in $operateColumns)$column.javaField#if($foreach.hasNext), #end#end } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ #set($addFields = [])
+ #foreach($column in $columns)#if($column.insert)#set($dummy = $addFields.add($column.javaField))#end#end
+ const { error } = await fetchCreate${BusinessName}({ #foreach($field in $addFields)$field#if($foreach.hasNext), #end#end });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ #set($editFields = [])
+ #foreach($column in $columns)#if($column.edit)#set($dummy = $editFields.add($column.javaField))#end#end
+ const { error } = await fetchUpdate${BusinessName}({ #foreach($field in $editFields)$field#if($foreach.hasNext), #end#end });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+#if($tplCategory == 'tree')
+
+async function getTreeList() {
+ const { data, error } = await fetchGet${BusinessName}List();
+ if (error) {
+ return;
+ }
+ const { tree } = handleTree(data);
+ treeList.value = tree;
+}
+#end
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ getTreeList();
+ }
+});
+#if($tplCategory == 'tree')
+
+const treeOptions = computed(() => {
+ return [
+ {
+ id: 0,
+ treeName: '椤剁骇鑺傜偣',
+ children: treeList.value
+ }
+ ];
+});
+#end
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+#set($immediateDictList = [])
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if(($column.insert || $column.edit) && !$column.pk)
+#set($isImmediate = !$column.isList() && !$column.isQuery() && !$immediateDictList.contains($column.dictType))
+ <NFormItem label="$column.columnComment" path="$column.javaField">
+ #if($tplCategory == 'tree' && $column.javaField == $treeParentCode)
+ <NTreeSelect
+ v-model:value="model.$treeParentCode"
+ filterable
+ class="h-full"
+ key-field="$treeCode"
+ label-field="$treeName"
+ :options="treeOptions"
+ :default-expanded-keys="[0]"
+ />
+ #elseif($column.htmlType == "textarea" || $column.htmlType == "editor")
+ <NInput
+ v-model:value="model.$column.javaField"
+ :rows="3"
+ type="textarea"
+ placeholder="璇疯緭鍏�$column.columnComment"
+ />
+ #elseif($column.htmlType == "select" && $column.dictType)
+ <DictSelect
+ v-model:value="model.$column.javaField"
+ placeholder="璇烽�夋嫨$column.columnComment"
+ dict-code="$column.dictType"
+ clearable
+#if($isImmediate)#set($void = $immediateDictList.add($column.dictType))
+ immediate
+#end
+ />
+ #elseif($column.htmlType == "select" && !$column.dictType)
+ <NSelect
+ v-model:value="model.$column.javaField"
+ placeholder="璇烽�夋嫨$column.columnComment"
+ :options="[{ value: '0', label: '璇烽�夋嫨瀛楀吀鐢熸垚' }]"
+ clearable
+ />
+ #elseif($column.htmlType == "radio" && $column.dictType)
+ <DictRadio
+ v-model:value="model.$column.javaField"
+ placeholder="璇烽�夋嫨$column.columnComment"
+ dict-code="$column.dictType"
+ clearable
+#if($isImmediate)#set($void = $immediateDictList.add($column.dictType))
+ immediate
+#end
+ />
+ #elseif($column.htmlType == "radio" && !$column.dictType)
+ <NRadioGroup v-model:value="model.$column.javaField">
+ <NSpace>
+ <NRadio value="0" label="璇烽�夋嫨瀛楀吀鐢熸垚" />
+ </NSpace>
+ </NRadioGroup>
+ #elseif($column.htmlType == "checkbox" && $column.dictType)
+ <DictCheckbox
+ v-model:value="model.$column.javaField"
+ placeholder="璇烽�夋嫨$column.columnComment"
+ dict-code="$column.dictType"
+ clearable
+#if($isImmediate)#set($void = $immediateDictList.add($column.dictType))
+ immediate
+#end
+ />
+ #elseif($column.htmlType == "checkbox" && $column.dictType)
+ <NCheckboxGroup v-model:value="model.$column.javaField">
+ <NSpace>
+ <NCheckbox value="0" label="璇烽�夋嫨瀛楀吀鐢熸垚" />
+ </NSpace>
+ </NCheckboxGroup>
+ #elseif($column.htmlType == 'datetime')
+ <NDatePicker
+ v-model:formatted-value="model.$column.javaField"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ />
+ #elseif($column.htmlType == "imageUpload")
+ <OssUpload v-model:value="model.$column.javaField" upload-type="image" />
+ #elseif($column.htmlType == "fileUpload")
+ <OssUpload v-model:value="model.$column.javaField" upload-type="file" />
+ #elseif($column.htmlType == "editor")
+ <TinymceEditor v-model:value="model.$column.javaField" />
+#else <NInput v-model:value="model.$column.javaField" placeholder="璇疯緭鍏�$column.columnComment" />
+ #end
+ </NFormItem>
+#end
+#end
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/modules/search.vue.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/modules/search.vue.vm
new file mode 100755
index 0000000..4cd4aba
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/modules/search.vue.vm
@@ -0,0 +1,130 @@
+#set($ModuleName=$moduleName.substring(0, 1).toUpperCase() + $moduleName.substring(1))
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: '${BusinessName}Search'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+#foreach ($column in $columns)
+ #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+ #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+const dateRange${AttrName} = ref<[string, string] | null>(null);
+#end#end
+const model = defineModel<Api.$ModuleName.${BusinessName}SearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ #foreach ($column in $columns)
+ #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+ #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ dateRange${AttrName}.value = null;
+ #end
+#end
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+#foreach ($column in $columns)
+ #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+ #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ if (dateRange${AttrName}.value?.length) {
+ model.value.params!.begin${AttrName} = dateRange${AttrName}.value[0];
+ model.value.params!.end${AttrName} = dateRange${AttrName}.value[1];
+ }
+ #end
+#end
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="$moduleName-${business__name}-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+#set($immediateDictList = [])
+#foreach($column in $columns)
+#if($column.query)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ <NFormItemGi span="24 s:12 m:6" label="$column.columnComment" label-width="auto" path="$column.javaField" class="pr-24px">
+ #if($!StrUtil.contains("select, radio, checkbox", $column.htmlType) && $column.dictType)
+ <DictSelect
+ v-model:value="model.$column.javaField"
+ placeholder="璇烽�夋嫨$column.columnComment"
+ dict-code="$column.dictType"
+ clearable
+#if(!$column.isList() && !$immediateDictList.contains($column.dictType))#set($void = $immediateDictList.add($column.dictType))
+ immediate
+#end
+ />
+ #elseif($!StrUtil.contains("select, radio, checkbox", $column.htmlType))
+ <NSelect
+ v-model:value="model.$column.javaField"
+ placeholder="璇烽�夋嫨$column.columnComment"
+ :options="[]"
+ clearable
+ />
+ #elseif($column.htmlType == 'datetime' && $column.queryType != "BETWEEN")
+ <NDatePicker
+ v-model:formatted-value="model.$column.javaField"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ />
+ #elseif($column.htmlType == 'datetime' && $column.queryType == "BETWEEN")
+ <NDatePicker
+ v-model:formatted-value="dateRange${AttrName}"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ />
+#else <NInput v-model:value="model.$column.javaField" placeholder="璇疯緭鍏�$column.columnComment" />
+ #end
+ </NFormItemGi>
+ #end
+#end
+ <NFormItemGi :show-feedback="false" span="24" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/typings/api.d.ts.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/typings/api.d.ts.vm
new file mode 100755
index 0000000..e65af2c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/soy/typings/api.d.ts.vm
@@ -0,0 +1,51 @@
+#set($BaseEntity = ['createDept', 'createBy', 'createTime', 'updateBy', 'updateTime'])
+#set($ModuleName = $moduleName.substring(0, 1).toUpperCase() + $moduleName.substring(1))
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+ /**
+ * namespace ${ModuleName}
+ *
+ * backend api module: "${ModuleName}"
+ */
+ namespace ${ModuleName} {
+ /** ${businessname} */
+ type ${BusinessName} = Common.CommonRecord<{
+ #foreach($column in $columns)#if(!$BaseEntity.contains($column.javaField))
+ /** $column.columnComment */
+ $column.javaField:#if($column.javaField.indexOf("id") != -1 || $column.javaField.indexOf("Id") != -1) CommonType.IdType; #elseif($column.javaType == 'Long' || $column.javaType == 'Integer' || $column.javaType == 'Double' || $column.javaType == 'Float' || $column.javaType == 'BigDecimal') number; #elseif($column.javaType == 'Boolean') boolean; #else string; #end
+ #end#end
+ }>;
+
+ /** ${businessname} search params */
+ type ${BusinessName}SearchParams = CommonType.RecordNullable<
+ Pick<
+ Api.${ModuleName}.${BusinessName},
+ #foreach($column in $columns)
+ #if($column.query && $column.queryType != 'BETWEEN')
+ | '${column.javaField}'
+ #end
+ #end
+ > &
+ Api.Common.CommonSearchParams
+ >;
+
+ /** ${businessname} operate params */
+ type ${BusinessName}OperateParams = CommonType.RecordNullable<
+ Pick<
+ Api.${ModuleName}.${BusinessName},
+ #foreach($column in $columns)
+ #if($column.insert || $column.edit)
+ | '${column.javaField}'
+ #end
+ #end
+ >
+ >;
+
+ /** ${businessname} list */
+ type ${BusinessName}List = Api.Common.PaginatingQueryRecord<${BusinessName}>;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/oracle/sql.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/oracle/sql.vm
new file mode 100755
index 0000000..f6638be
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/oracle/sql.vm
@@ -0,0 +1,19 @@
+-- 鑿滃崟 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 103, 1, sysdate, null, null, '${functionName}鑿滃崟');
+
+-- 鎸夐挳 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[1]}, '${functionName}鏌ヨ', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, sysdate, null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[2]}, '${functionName}鏂板', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, sysdate, null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[3]}, '${functionName}淇敼', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, sysdate, null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[4]}, '${functionName}鍒犻櫎', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, sysdate, null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[5]}, '${functionName}瀵煎嚭', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, sysdate, null, null, '');
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/postgres/sql.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/postgres/sql.vm
new file mode 100755
index 0000000..0923392
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/postgres/sql.vm
@@ -0,0 +1,20 @@
+-- 鑿滃崟 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 103, 1, now(), null, null, '${functionName}鑿滃崟');
+
+-- 鎸夐挳 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[1]}, '${functionName}鏌ヨ', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, now(), null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[2]}, '${functionName}鏂板', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, now(), null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[3]}, '${functionName}淇敼', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, now(), null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[4]}, '${functionName}鍒犻櫎', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, now(), null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[5]}, '${functionName}瀵煎嚭', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, now(), null, null, '');
+
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/sql.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/sql.vm
new file mode 100755
index 0000000..01824c2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/sql.vm
@@ -0,0 +1,19 @@
+-- 鑿滃崟 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 103, 1, sysdate(), null, null, '${functionName}鑿滃崟');
+
+-- 鎸夐挳 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[1]}, '${functionName}鏌ヨ', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, sysdate(), null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[2]}, '${functionName}鏂板', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, sysdate(), null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[3]}, '${functionName}淇敼', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, sysdate(), null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[4]}, '${functionName}鍒犻櫎', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, sysdate(), null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[5]}, '${functionName}瀵煎嚭', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, sysdate(), null, null, '');
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/sqlserver/sql.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/sqlserver/sql.vm
new file mode 100755
index 0000000..bdf166e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/sql/sqlserver/sql.vm
@@ -0,0 +1,19 @@
+-- 鑿滃崟 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 103, 1, getdate(), null, null, '${functionName}鑿滃崟');
+
+-- 鎸夐挳 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[1]}, '${functionName}鏌ヨ', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, getdate(), null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[2]}, '${functionName}鏂板', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, getdate(), null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[3]}, '${functionName}淇敼', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, getdate(), null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[4]}, '${functionName}鍒犻櫎', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, getdate(), null, null, '');
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
+values(${table.menuIds[5]}, '${functionName}瀵煎嚭', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, getdate(), null, null, '');
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/api.ts.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/api.ts.vm
new file mode 100755
index 0000000..3aa4a5f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/api.ts.vm
@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ${BusinessName}VO, ${BusinessName}Form, ${BusinessName}Query } from '@/api/${moduleName}/${businessName}/types';
+
+/**
+ * 鏌ヨ${functionName}鍒楄〃
+ * @param query
+ * @returns {*}
+ */
+
+export const list${BusinessName} = (query?: ${BusinessName}Query): AxiosPromise<${BusinessName}VO[]> => {
+ return request({
+ url: '/${moduleName}/${businessName}/list',
+ method: 'get',
+ params: query
+ });
+};
+
+/**
+ * 鏌ヨ${functionName}璇︾粏
+ * @param ${pkColumn.javaField}
+ */
+export const get${BusinessName} = (${pkColumn.javaField}: string | number): AxiosPromise<${BusinessName}VO> => {
+ return request({
+ url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
+ method: 'get'
+ });
+};
+
+/**
+ * 鏂板${functionName}
+ * @param data
+ */
+export const add${BusinessName} = (data: ${BusinessName}Form) => {
+ return request({
+ url: '/${moduleName}/${businessName}',
+ method: 'post',
+ data: data
+ });
+};
+
+/**
+ * 淇敼${functionName}
+ * @param data
+ */
+export const update${BusinessName} = (data: ${BusinessName}Form) => {
+ return request({
+ url: '/${moduleName}/${businessName}',
+ method: 'put',
+ data: data
+ });
+};
+
+/**
+ * 鍒犻櫎${functionName}
+ * @param ${pkColumn.javaField}
+ */
+export const del${BusinessName} = (${pkColumn.javaField}: string | number | Array<string | number>) => {
+ return request({
+ url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
+ method: 'delete'
+ });
+};
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/types.ts.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/types.ts.vm
new file mode 100755
index 0000000..eeef5f5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/types.ts.vm
@@ -0,0 +1,61 @@
+export interface ${BusinessName}VO {
+#foreach ($column in $columns)
+#if($column.list)
+ /**
+ * $column.columnComment
+ */
+ $column.javaField:#if($column.javaField.indexOf("id") != -1 || $column.javaField.indexOf("Id") != -1) string | number;
+ #elseif($column.javaType == 'Long' || $column.javaType == 'Integer' || $column.javaType == 'Double' || $column.javaType == 'Float' || $column.javaType == 'BigDecimal') number;
+ #elseif($column.javaType == 'Boolean') boolean;
+ #else string;
+ #end
+#if($column.htmlType == "imageUpload")
+ /**
+ * ${column.columnComment}Url
+ */
+ ${column.javaField}Url: string;
+#end
+#end
+#end
+#if ($table.tree)
+ /**
+ * 瀛愬璞�
+ */
+ children: ${BusinessName}VO[];
+#end
+}
+
+export interface ${BusinessName}Form extends BaseEntity {
+#foreach ($column in $columns)
+#if($column.insert || $column.edit)
+ /**
+ * $column.columnComment
+ */
+ $column.javaField?:#if($column.javaField.indexOf("id") != -1 || $column.javaField.indexOf("Id") != -1) string | number;
+ #elseif($column.javaType == 'Long' || $column.javaType == 'Integer' || $column.javaType == 'Double' || $column.javaType == 'Float' || $column.javaType == 'BigDecimal') number;
+ #elseif($column.javaType == 'Boolean') boolean;
+ #else string;
+ #end
+#end
+#end
+}
+
+export interface ${BusinessName}Query #if(!${treeCode})extends PageQuery #end{
+
+#foreach ($column in $columns)
+#if($column.query)
+ /**
+ * $column.columnComment
+ */
+ $column.javaField?:#if($column.javaField.indexOf("id") != -1 || $column.javaField.indexOf("Id") != -1) string | number;
+ #elseif($column.javaType == 'Long' || $column.javaType == 'Integer' || $column.javaType == 'Double' || $column.javaType == 'Float' || $column.javaType == 'BigDecimal') number;
+ #elseif($column.javaType == 'Boolean') boolean;
+ #else string;
+ #end
+#end
+#end
+ /**
+ * 鏃ユ湡鑼冨洿鍙傛暟
+ */
+ params?: any;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
new file mode 100755
index 0000000..fa4e05f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
@@ -0,0 +1,499 @@
+<template>
+ <div class="p-2">
+ <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+ <div v-show="showSearch" class="mb-[10px]">
+ <el-card shadow="hover">
+ <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+#foreach($column in $columns)
+#if($column.query)
+#set($dictType=$column.dictType)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set($parentheseIndex=$column.columnComment.indexOf("锛�"))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.htmlType == "input" || $column.htmlType == "textarea")
+ <el-form-item label="${comment}" prop="${column.javaField}">
+ <el-input v-model="queryParams.${column.javaField}" placeholder="璇疯緭鍏�${comment}" clearable @keyup.enter="handleQuery" />
+ </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+ <el-form-item label="${comment}" prop="${column.javaField}">
+ <el-select v-model="queryParams.${column.javaField}" placeholder="璇烽�夋嫨${comment}" clearable>
+ <el-option v-for="dict in ${dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
+ </el-select>
+ </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+ <el-form-item label="${comment}" prop="${column.javaField}">
+ <el-select v-model="queryParams.${column.javaField}" placeholder="璇烽�夋嫨${comment}" clearable>
+ <el-option label="璇烽�夋嫨瀛楀吀鐢熸垚" value="" />
+ </el-select>
+ </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
+ <el-form-item label="${comment}" prop="${column.javaField}">
+ <el-date-picker clearable
+ v-model="queryParams.${column.javaField}"
+ type="date"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨${comment}"
+ />
+ </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+ <el-form-item label="${comment}" style="width: 308px">
+ <el-date-picker
+ v-model="dateRange${AttrName}"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ type="daterange"
+ range-separator="-"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+ />
+ </el-form-item>
+#end
+#end
+#end
+ <el-form-item>
+ <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+ <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+ </div>
+ </transition>
+
+ <el-card shadow="never">
+ <template #header>
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['${moduleName}:${businessName}:add']">鏂板</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">灞曞紑/鎶樺彔</el-button>
+ </el-col>
+ <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+ </el-row>
+ </template>
+ <el-table
+ ref="${businessName}TableRef"
+ v-loading="loading"
+ :data="${businessName}List"
+ row-key="${treeCode}"
+ border
+ :default-expand-all="isExpandAll"
+ :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+ >
+#foreach($column in $columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("锛�"))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk)
+#elseif($column.list && $column.htmlType == "datetime")
+ <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+ <template #default="scope">
+ <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
+ </template>
+ </el-table-column>
+#elseif($column.list && $column.htmlType == "imageUpload")
+ <el-table-column label="${comment}" align="center" prop="${javaField}Url" width="100">
+ <template #default="scope">
+ <image-preview :src="scope.row.${javaField}Url" :width="50" :height="50"/>
+ </template>
+ </el-table-column>
+#elseif($column.list && $column.dictType && "" != $column.dictType)
+ <el-table-column label="${comment}" align="center" prop="${javaField}">
+ <template #default="scope">
+#if($column.htmlType == "checkbox")
+ <dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
+#else
+ <dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
+#end
+ </template>
+ </el-table-column>
+#elseif($column.list && "" != $javaField)
+#if(${foreach.index} == 1)
+ <el-table-column label="${comment}" prop="${javaField}" />
+#else
+ <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#end
+#end
+#end
+ <el-table-column label="鎿嶄綔" align="center" fixed="right" class-name="small-padding fixed-width">
+ <template #default="scope">
+ <el-tooltip content="淇敼" placement="top">
+ <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']" />
+ </el-tooltip>
+ <el-tooltip content="鏂板" placement="top">
+ <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${moduleName}:${businessName}:add']" />
+ </el-tooltip>
+ <el-tooltip content="鍒犻櫎" placement="top">
+ <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']" />
+ </el-tooltip>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ <!-- 娣诲姞鎴栦慨鏀�${functionName}瀵硅瘽妗� -->
+ <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+ <el-form ref="${businessName}FormRef" :model="form" :rules="rules" label-width="80px">
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if(($column.insert || $column.edit) && !$column.pk)
+#set($parentheseIndex=$column.columnComment.indexOf("锛�"))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#set($dictType=$column.dictType)
+#if("" != $treeParentCode && $column.javaField == $treeParentCode)
+ <el-form-item label="${comment}" prop="${treeParentCode}">
+ <el-tree-select
+ v-model="form.${treeParentCode}"
+ :data="${businessName}Options"
+ :props="{ value: '${treeCode}', label: '${treeName}', children: 'children' }"
+ value-key="${treeCode}"
+ placeholder="璇烽�夋嫨${comment}"
+ check-strictly
+ />
+ </el-form-item>
+#elseif($column.htmlType == "input")
+ <el-form-item label="${comment}" prop="${field}">
+ <el-input v-model="form.${field}" placeholder="璇疯緭鍏�${comment}" />
+ </el-form-item>
+#elseif($column.htmlType == "imageUpload")
+ <el-form-item label="${comment}" prop="${field}">
+ <image-upload v-model="form.${field}"/>
+ </el-form-item>
+#elseif($column.htmlType == "fileUpload")
+ <el-form-item label="${comment}" prop="${field}">
+ <file-upload v-model="form.${field}"/>
+ </el-form-item>
+#elseif($column.htmlType == "editor")
+ <el-form-item label="${comment}">
+ <editor v-model="form.${field}" :min-height="192"/>
+ </el-form-item>
+#elseif($column.htmlType == "select" && "" != $dictType)
+ <el-form-item label="${comment}" prop="${field}">
+ <el-select v-model="form.${field}" placeholder="璇烽�夋嫨${comment}">
+ <el-option
+ v-for="dict in ${dictType}"
+ :key="dict.value"
+ :label="dict.label"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+ :value="parseInt(dict.value)"
+#else
+ :value="dict.value"
+#end
+ ></el-option>
+ </el-select>
+ </el-form-item>
+#elseif($column.htmlType == "select" && $dictType)
+ <el-form-item label="${comment}" prop="${field}">
+ <el-select v-model="form.${field}" placeholder="璇烽�夋嫨${comment}">
+ <el-option label="璇烽�夋嫨瀛楀吀鐢熸垚" value="" />
+ </el-select>
+ </el-form-item>
+#elseif($column.htmlType == "checkbox" && "" != $dictType)
+ <el-form-item label="${comment}" prop="${field}">
+ <el-checkbox-group v-model="form.${field}">
+ <el-checkbox
+ v-for="dict in ${dictType}"
+ :key="dict.value"
+ :label="dict.value">
+ {{dict.label}}
+ </el-checkbox>
+ </el-checkbox-group>
+ </el-form-item>
+#elseif($column.htmlType == "checkbox" && $dictType)
+ <el-form-item label="${comment}" prop="${field}">
+ <el-checkbox-group v-model="form.${field}">
+ <el-checkbox>璇烽�夋嫨瀛楀吀鐢熸垚</el-checkbox>
+ </el-checkbox-group>
+ </el-form-item>
+#elseif($column.htmlType == "radio" && "" != $dictType)
+ <el-form-item label="${comment}" prop="${field}">
+ <el-radio-group v-model="form.${field}">
+ <el-radio
+ v-for="dict in ${dictType}"
+ :key="dict.value"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+ :value="parseInt(dict.value)"
+#else
+ :value="dict.value"
+#end
+ >{{dict.label}}</el-radio>
+ </el-radio-group>
+ </el-form-item>
+#elseif($column.htmlType == "radio" && $dictType)
+ <el-form-item label="${comment}" prop="${field}">
+ <el-radio-group v-model="form.${field}">
+ <el-radio value="1">璇烽�夋嫨瀛楀吀鐢熸垚</el-radio>
+ </el-radio-group>
+ </el-form-item>
+#elseif($column.htmlType == "datetime")
+ <el-form-item label="${comment}" prop="${field}">
+ <el-date-picker clearable
+ v-model="form.${field}"
+ type="datetime"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ placeholder="閫夋嫨${comment}"
+ />
+ </el-form-item>
+#elseif($column.htmlType == "textarea")
+ <el-form-item label="${comment}" prop="${field}">
+ <el-input v-model="form.${field}" type="textarea" placeholder="璇疯緭鍏ュ唴瀹�" />
+ </el-form-item>
+#end
+#end
+#end
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button :loading="buttonLoading" type="primary" @click="submitForm">纭� 瀹�</el-button>
+ <el-button @click="cancel">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup name="${BusinessName}" lang="ts">
+import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
+import { ${BusinessName}VO, ${BusinessName}Query, ${BusinessName}Form } from '@/api/${moduleName}/${businessName}/types';
+
+type ${BusinessName}Option = {
+ ${treeCode}: number;
+ ${treeName}: string;
+ children?: ${BusinessName}Option[];
+}
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;;
+
+#if(${dicts} != '')
+#set($dictsNoSymbol=$dicts.replace("'", ""))
+const { ${dictsNoSymbol} } = toRefs<any>(proxy?.useDict(${dicts}));
+#end
+
+const ${businessName}List = ref<${BusinessName}VO[]>([]);
+const ${businessName}Options = ref<${BusinessName}Option[]>([]);
+const buttonLoading = ref(false);
+const showSearch = ref(true);
+const isExpandAll = ref(true);
+const loading = ref(false);
+
+const queryFormRef = ref<ElFormInstance>();
+const ${businessName}FormRef = ref<ElFormInstance>();
+const ${businessName}TableRef = ref<ElTableInstance>()
+
+const dialog = reactive<DialogOption>({
+ visible: false,
+ title: ''
+});
+
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+const dateRange${AttrName} = ref<[DateModelType, DateModelType]>(['', '']);
+#end
+#end
+
+const initFormData: ${BusinessName}Form = {
+#foreach ($column in $columns)
+#if($column.insert || $column.edit)
+#if($column.htmlType == "checkbox")
+ $column.javaField: []#if($foreach.count != $columns.size()),#end
+#else
+ $column.javaField: undefined#if($foreach.count != $columns.size()),#end
+#end
+#end
+#end
+}
+
+const data = reactive<PageData<${BusinessName}Form, ${BusinessName}Query>>({
+ form: {...initFormData},
+ queryParams: {
+#foreach ($column in $columns)
+#if($column.query)
+#if($column.htmlType != "datetime" || $column.queryType != "BETWEEN")
+ $column.javaField: undefined,
+#end
+#end
+#end
+ params: {
+#foreach ($column in $columns)
+#if($column.query)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+ $column.javaField: undefined#if($foreach.count != $columns.size()),#end
+#end
+#end
+#end
+ }
+ },
+ rules: {
+#foreach ($column in $columns)
+#if($column.insert || $column.edit)
+#if($column.required)
+#set($parentheseIndex=$column.columnComment.indexOf("锛�"))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+ $column.javaField: [
+ { required: true, message: "$comment涓嶈兘涓虹┖", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
+ ]#if($foreach.count != $columns.size()),#end
+#end
+#end
+#end
+ }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ${functionName}鍒楄〃 */
+const getList = async () => {
+ loading.value = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+ queryParams.value.params = {};
+#break
+#end
+#end
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ proxy?.addDateRange(queryParams.value, dateRange${AttrName}.value, '${AttrName}');
+#end
+#end
+ const res = await list${BusinessName}(queryParams.value);
+ const data = proxy?.handleTree<${BusinessName}VO>(res.data, "${treeCode}", "${treeParentCode}");
+ if (data) {
+ ${businessName}List.value = data;
+ loading.value = false;
+ }
+}
+
+/** 鏌ヨ${functionName}涓嬫媺鏍戠粨鏋� */
+const getTreeselect = async () => {
+ const res = await list${BusinessName}();
+ ${businessName}Options.value = [];
+ const data: ${BusinessName}Option = { ${treeCode}: 0, ${treeName}: '椤剁骇鑺傜偣', children: [] };
+ data.children = proxy?.handleTree<${BusinessName}Option>(res.data, "${treeCode}", "${treeParentCode}");
+ ${businessName}Options.value.push(data);
+}
+
+// 鍙栨秷鎸夐挳
+const cancel = () => {
+ reset();
+ dialog.visible = false;
+}
+
+// 琛ㄥ崟閲嶇疆
+const reset = () => {
+ form.value = {...initFormData}
+ ${businessName}FormRef.value?.resetFields();
+}
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ getList();
+}
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ dateRange${AttrName}.value = ['', ''];
+#end
+#end
+ queryFormRef.value?.resetFields();
+ handleQuery();
+}
+
+/** 鏂板鎸夐挳鎿嶄綔 */
+const handleAdd = (row?: ${BusinessName}VO) => {
+ reset();
+ getTreeselect();
+ if (row != null && row.${treeCode}) {
+ form.value.${treeParentCode} = row.${treeCode};
+ } else {
+ form.value.${treeParentCode} = 0;
+ }
+ dialog.visible = true;
+ dialog.title = "娣诲姞${functionName}";
+}
+
+/** 灞曞紑/鎶樺彔鎿嶄綔 */
+const handleToggleExpandAll = () => {
+ isExpandAll.value = !isExpandAll.value;
+ toggleExpandAll(${businessName}List.value, isExpandAll.value)
+}
+
+/** 灞曞紑/鎶樺彔鎿嶄綔 */
+const toggleExpandAll = (data: ${BusinessName}VO[], status: boolean) => {
+ data.forEach((item) => {
+ ${businessName}TableRef.value?.toggleRowExpansion(item, status)
+ if (item.children && item.children.length > 0) toggleExpandAll(item.children, status)
+ })
+}
+
+/** 淇敼鎸夐挳鎿嶄綔 */
+const handleUpdate = async (row: ${BusinessName}VO) => {
+ reset();
+ await getTreeselect();
+ if (row != null) {
+ form.value.${treeParentCode} = row.${treeParentCode};
+ }
+ const res = await get${BusinessName}(row.${pkColumn.javaField});
+ Object.assign(form.value, res.data);
+#foreach ($column in $columns)
+ #if($column.htmlType == "checkbox")
+ form.value.$column.javaField = form.value.${column.javaField}.split(",");
+ #end
+#end
+ dialog.visible = true;
+ dialog.title = "淇敼${functionName}";
+}
+
+/** 鎻愪氦鎸夐挳 */
+const submitForm = () => {
+ ${businessName}FormRef.value?.validate(async (valid: boolean) => {
+ if (valid) {
+ buttonLoading.value = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+ form.value.$column.javaField = form.value.${column.javaField}.join(",");
+#end
+#end
+ if (form.value.${pkColumn.javaField}) {
+ await update${BusinessName}(form.value).finally(() => buttonLoading.value = false);
+ } else {
+ await add${BusinessName}(form.value).finally(() => buttonLoading.value = false);
+ }
+ proxy?.#[[$modal]]#.msgSuccess("鎿嶄綔鎴愬姛");
+ dialog.visible = false;
+ getList();
+ }
+ });
+}
+
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+const handleDelete = async (row: ${BusinessName}VO) => {
+ await proxy?.#[[$modal]]#.confirm('鏄惁纭鍒犻櫎${functionName}缂栧彿涓�"' + row.${pkColumn.javaField} + '"鐨勬暟鎹」锛�');
+ loading.value = true;
+ await del${BusinessName}(row.${pkColumn.javaField}).finally(() => loading.value = false);
+ await getList();
+ proxy?.#[[$modal]]#.msgSuccess("鍒犻櫎鎴愬姛");
+}
+
+onMounted(() => {
+ getList();
+});
+</script>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
new file mode 100755
index 0000000..934b9b9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
@@ -0,0 +1,459 @@
+<template>
+ <div class="p-2">
+ <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+ <div v-show="showSearch" class="mb-[10px]">
+ <el-card shadow="hover">
+ <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+#foreach($column in $columns)
+#if($column.query)
+#set($dictType=$column.dictType)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set($parentheseIndex=$column.columnComment.indexOf("锛�"))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.htmlType == "input" || $column.htmlType == "textarea")
+ <el-form-item label="${comment}" prop="${column.javaField}">
+ <el-input v-model="queryParams.${column.javaField}" placeholder="璇疯緭鍏�${comment}" clearable @keyup.enter="handleQuery" />
+ </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+ <el-form-item label="${comment}" prop="${column.javaField}">
+ <el-select v-model="queryParams.${column.javaField}" placeholder="璇烽�夋嫨${comment}" clearable >
+ <el-option v-for="dict in ${dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
+ </el-select>
+ </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+ <el-form-item label="${comment}" prop="${column.javaField}">
+ <el-select v-model="queryParams.${column.javaField}" placeholder="璇烽�夋嫨${comment}" clearable >
+ <el-option label="璇烽�夋嫨瀛楀吀鐢熸垚" value="" />
+ </el-select>
+ </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
+ <el-form-item label="${comment}" prop="${column.javaField}">
+ <el-date-picker clearable
+ v-model="queryParams.${column.javaField}"
+ type="date"
+ value-format="YYYY-MM-DD"
+ placeholder="璇烽�夋嫨${comment}"
+ />
+ </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+ <el-form-item label="${comment}" style="width: 308px">
+ <el-date-picker
+ v-model="dateRange${AttrName}"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ type="daterange"
+ range-separator="-"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+ />
+ </el-form-item>
+#end
+#end
+#end
+ <el-form-item>
+ <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+ <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+ </div>
+ </transition>
+
+ <el-card shadow="never">
+ <template #header>
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['${moduleName}:${businessName}:add']">鏂板</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['${moduleName}:${businessName}:edit']">淇敼</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['${moduleName}:${businessName}:remove']">鍒犻櫎</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['${moduleName}:${businessName}:export']">瀵煎嚭</el-button>
+ </el-col>
+ <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+ </el-row>
+ </template>
+
+ <el-table v-loading="loading" border :data="${businessName}List" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55" align="center" />
+#foreach($column in $columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("锛�"))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk)
+ <el-table-column label="${comment}" align="center" prop="${javaField}" v-if="${column.list}" />
+#elseif($column.list && $column.htmlType == "datetime")
+ <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+ <template #default="scope">
+ <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
+ </template>
+ </el-table-column>
+#elseif($column.list && $column.htmlType == "imageUpload")
+ <el-table-column label="${comment}" align="center" prop="${javaField}Url" width="100">
+ <template #default="scope">
+ <image-preview :src="scope.row.${javaField}Url" :width="50" :height="50"/>
+ </template>
+ </el-table-column>
+#elseif($column.list && $column.dictType && "" != $column.dictType)
+ <el-table-column label="${comment}" align="center" prop="${javaField}">
+ <template #default="scope">
+#if($column.htmlType == "checkbox")
+ <dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
+#else
+ <dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
+#end
+ </template>
+ </el-table-column>
+#elseif($column.list && "" != $javaField)
+ <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#end
+#end
+ <el-table-column label="鎿嶄綔" align="center" fixed="right" class-name="small-padding fixed-width">
+ <template #default="scope">
+ <el-tooltip content="淇敼" placement="top">
+ <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']"></el-button>
+ </el-tooltip>
+ <el-tooltip content="鍒犻櫎" placement="top">
+ <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']"></el-button>
+ </el-tooltip>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+ </el-card>
+ <!-- 娣诲姞鎴栦慨鏀�${functionName}瀵硅瘽妗� -->
+ <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+ <el-form ref="${businessName}FormRef" :model="form" :rules="rules" label-width="80px">
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if(($column.insert || $column.edit) && !$column.pk)
+#set($parentheseIndex=$column.columnComment.indexOf("锛�"))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#set($dictType=$column.dictType)
+#if($column.htmlType == "input")
+ <el-form-item label="${comment}" prop="${field}">
+ <el-input v-model="form.${field}" placeholder="璇疯緭鍏�${comment}" />
+ </el-form-item>
+#elseif($column.htmlType == "imageUpload")
+ <el-form-item label="${comment}" prop="${field}">
+ <image-upload v-model="form.${field}"/>
+ </el-form-item>
+#elseif($column.htmlType == "fileUpload")
+ <el-form-item label="${comment}" prop="${field}">
+ <file-upload v-model="form.${field}"/>
+ </el-form-item>
+#elseif($column.htmlType == "editor")
+ <el-form-item label="${comment}">
+ <editor v-model="form.${field}" :min-height="192"/>
+ </el-form-item>
+#elseif($column.htmlType == "select" && "" != $dictType)
+ <el-form-item label="${comment}" prop="${field}">
+ <el-select v-model="form.${field}" placeholder="璇烽�夋嫨${comment}">
+ <el-option
+ v-for="dict in ${dictType}"
+ :key="dict.value"
+ :label="dict.label"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+ :value="parseInt(dict.value)"
+#else
+ :value="dict.value"
+#end
+ ></el-option>
+ </el-select>
+ </el-form-item>
+#elseif($column.htmlType == "select" && $dictType)
+ <el-form-item label="${comment}" prop="${field}">
+ <el-select v-model="form.${field}" placeholder="璇烽�夋嫨${comment}">
+ <el-option label="璇烽�夋嫨瀛楀吀鐢熸垚" value="" />
+ </el-select>
+ </el-form-item>
+#elseif($column.htmlType == "checkbox" && "" != $dictType)
+ <el-form-item label="${comment}" prop="${field}">
+ <el-checkbox-group v-model="form.${field}">
+ <el-checkbox
+ v-for="dict in ${dictType}"
+ :key="dict.value"
+ :label="dict.value">
+ {{dict.label}}
+ </el-checkbox>
+ </el-checkbox-group>
+ </el-form-item>
+#elseif($column.htmlType == "checkbox" && $dictType)
+ <el-form-item label="${comment}" prop="${field}">
+ <el-checkbox-group v-model="form.${field}">
+ <el-checkbox>璇烽�夋嫨瀛楀吀鐢熸垚</el-checkbox>
+ </el-checkbox-group>
+ </el-form-item>
+#elseif($column.htmlType == "radio" && "" != $dictType)
+ <el-form-item label="${comment}" prop="${field}">
+ <el-radio-group v-model="form.${field}">
+ <el-radio
+ v-for="dict in ${dictType}"
+ :key="dict.value"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+ :value="parseInt(dict.value)"
+#else
+ :value="dict.value"
+#end
+ >{{dict.label}}</el-radio>
+ </el-radio-group>
+ </el-form-item>
+#elseif($column.htmlType == "radio" && $dictType)
+ <el-form-item label="${comment}" prop="${field}">
+ <el-radio-group v-model="form.${field}">
+ <el-radio value="1">璇烽�夋嫨瀛楀吀鐢熸垚</el-radio>
+ </el-radio-group>
+ </el-form-item>
+#elseif($column.htmlType == "datetime")
+ <el-form-item label="${comment}" prop="${field}">
+ <el-date-picker clearable
+ v-model="form.${field}"
+ type="datetime"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ placeholder="璇烽�夋嫨${comment}">
+ </el-date-picker>
+ </el-form-item>
+#elseif($column.htmlType == "textarea")
+ <el-form-item label="${comment}" prop="${field}">
+ <el-input v-model="form.${field}" type="textarea" placeholder="璇疯緭鍏ュ唴瀹�" />
+ </el-form-item>
+#end
+#end
+#end
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button :loading="buttonLoading" type="primary" @click="submitForm">纭� 瀹�</el-button>
+ <el-button @click="cancel">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup name="${BusinessName}" lang="ts">
+import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from '@/api/${moduleName}/${businessName}';
+import { ${BusinessName}VO, ${BusinessName}Query, ${BusinessName}Form } from '@/api/${moduleName}/${businessName}/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+#if(${dicts} != '')
+#set($dictsNoSymbol=$dicts.replace("'", ""))
+const { ${dictsNoSymbol} } = toRefs<any>(proxy?.useDict(${dicts}));
+#end
+
+const ${businessName}List = ref<${BusinessName}VO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+const dateRange${AttrName} = ref<[DateModelType, DateModelType]>(['', '']);
+#end
+#end
+
+const queryFormRef = ref<ElFormInstance>();
+const ${businessName}FormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+ visible: false,
+ title: ''
+});
+
+const initFormData: ${BusinessName}Form = {
+#foreach ($column in $columns)
+#if($column.insert || $column.edit)
+#if($column.htmlType == "checkbox")
+ $column.javaField: []#if($foreach.count != $columns.size()),#end
+#else
+ $column.javaField: undefined#if($foreach.count != $columns.size()),#end
+#end
+#end
+#end
+}
+const data = reactive<PageData<${BusinessName}Form, ${BusinessName}Query>>({
+ form: {...initFormData},
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+#foreach ($column in $columns)
+#if($column.query)
+#if($column.htmlType != "datetime" || $column.queryType != "BETWEEN")
+ $column.javaField: undefined,
+#end
+#end
+#end
+ params: {
+#foreach ($column in $columns)
+#if($column.query)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+ $column.javaField: undefined#if($foreach.count != $columns.size()),#end
+#end
+#end
+#end
+ }
+ },
+ rules: {
+#foreach ($column in $columns)
+#if($column.insert || $column.edit)
+#if($column.required)
+#set($parentheseIndex=$column.columnComment.indexOf("锛�"))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+ $column.javaField: [
+ { required: true, message: "$comment涓嶈兘涓虹┖", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
+ ]#if($foreach.count != $columns.size()),#end
+#end
+#end
+#end
+ }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ${functionName}鍒楄〃 */
+const getList = async () => {
+ loading.value = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+ queryParams.value.params = {};
+#break
+#end
+#end
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ proxy?.addDateRange(queryParams.value, dateRange${AttrName}.value, '${AttrName}');
+#end
+#end
+ const res = await list${BusinessName}(queryParams.value);
+ ${businessName}List.value = res.rows;
+ total.value = res.total;
+ loading.value = false;
+}
+
+/** 鍙栨秷鎸夐挳 */
+const cancel = () => {
+ reset();
+ dialog.visible = false;
+}
+
+/** 琛ㄥ崟閲嶇疆 */
+const reset = () => {
+ form.value = {...initFormData};
+ ${businessName}FormRef.value?.resetFields();
+}
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ queryParams.value.pageNum = 1;
+ getList();
+}
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ dateRange${AttrName}.value = ['', ''];
+#end
+#end
+ queryFormRef.value?.resetFields();
+ handleQuery();
+}
+
+/** 澶氶�夋閫変腑鏁版嵁 */
+const handleSelectionChange = (selection: ${BusinessName}VO[]) => {
+ ids.value = selection.map(item => item.${pkColumn.javaField});
+ single.value = selection.length != 1;
+ multiple.value = !selection.length;
+}
+
+/** 鏂板鎸夐挳鎿嶄綔 */
+const handleAdd = () => {
+ reset();
+ dialog.visible = true;
+ dialog.title = "娣诲姞${functionName}";
+}
+
+/** 淇敼鎸夐挳鎿嶄綔 */
+const handleUpdate = async (row?: ${BusinessName}VO) => {
+ reset();
+ const _${pkColumn.javaField} = row?.${pkColumn.javaField} || ids.value[0]
+ const res = await get${BusinessName}(_${pkColumn.javaField});
+ Object.assign(form.value, res.data);
+#foreach ($column in $columns)
+ #if($column.htmlType == "checkbox")
+ form.value.$column.javaField = form.value.${column.javaField}.split(",");
+ #end
+#end
+ dialog.visible = true;
+ dialog.title = "淇敼${functionName}";
+}
+
+/** 鎻愪氦鎸夐挳 */
+const submitForm = () => {
+ ${businessName}FormRef.value?.validate(async (valid: boolean) => {
+ if (valid) {
+ buttonLoading.value = true;
+ #foreach ($column in $columns)
+ #if($column.htmlType == "checkbox")
+ form.value.$column.javaField = form.value.${column.javaField}.join(",");
+ #end
+ #end
+ if (form.value.${pkColumn.javaField}) {
+ await update${BusinessName}(form.value).finally(() => buttonLoading.value = false);
+ } else {
+ await add${BusinessName}(form.value).finally(() => buttonLoading.value = false);
+ }
+ proxy?.#[[$modal]]#.msgSuccess("鎿嶄綔鎴愬姛");
+ dialog.visible = false;
+ await getList();
+ }
+ });
+}
+
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+const handleDelete = async (row?: ${BusinessName}VO) => {
+ const _${pkColumn.javaField}s = row?.${pkColumn.javaField} || ids.value;
+ await proxy?.#[[$modal]]#.confirm('鏄惁纭鍒犻櫎${functionName}缂栧彿涓�"' + _${pkColumn.javaField}s + '"鐨勬暟鎹」锛�').finally(() => loading.value = false);
+ await del${BusinessName}(_${pkColumn.javaField}s);
+ proxy?.#[[$modal]]#.msgSuccess("鍒犻櫎鎴愬姛");
+ await getList();
+}
+
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+const handleExport = () => {
+ proxy?.download('${moduleName}/${businessName}/export', {
+ ...queryParams.value
+ }, `${businessName}_#[[${new Date().getTime()}]]#.xlsx`)
+}
+
+onMounted(() => {
+ getList();
+});
+</script>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm
new file mode 100755
index 0000000..1c76ad8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="${packageName}.mapper.${ClassName}Mapper">
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/.flattened-pom.xml
new file mode 100644
index 0000000..57883d4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/.flattened-pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-modules</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-job</artifactId>
+ <version>5.5.3</version>
+ <description>浠诲姟璋冨害</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-job</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/pom.xml
new file mode 100755
index 0000000..2431a1c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-modules</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>jar</packaging>
+ <artifactId>ruoyi-job</artifactId>
+
+ <description>
+ 浠诲姟璋冨害
+ </description>
+
+ <dependencies>
+
+ <!-- 閫氱敤宸ュ叿-->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-json</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-job</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
+
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/entity/BillDto.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/entity/BillDto.java
new file mode 100755
index 0000000..2661e34
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/entity/BillDto.java
@@ -0,0 +1,30 @@
+package org.dromara.job.entity;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class BillDto {
+
+ /**
+ * 璐﹀崟ID
+ */
+ private Long billId;
+
+ /**
+ * 璐﹀崟娓犻亾
+ */
+ private String billChannel;
+
+ /**
+ * 璐﹀崟鏃ユ湡
+ */
+ private String billDate;
+
+ /**
+ * 璐﹀崟閲戦
+ */
+ private BigDecimal billAmount;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/package-info.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/package-info.java
new file mode 100755
index 0000000..2f118b0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/package-info.java
@@ -0,0 +1 @@
+package org.dromara.job;
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/AlipayBillTask.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/AlipayBillTask.java
new file mode 100755
index 0000000..d5c3ea7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/AlipayBillTask.java
@@ -0,0 +1,42 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.dto.JobArgs;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import com.aizuda.snailjob.model.dto.ExecuteResult;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.job.entity.BillDto;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+
+/**
+ * DAG宸ヤ綔娴佷换鍔�-妯℃嫙鏀粯瀹濊处鍗曚换鍔�
+ * <a href="https://juejin.cn/post/7487860254114644019"></a>
+ *
+ * @author 鑰侀┈
+ */
+@Component
+@JobExecutor(name = "alipayBillTask")
+public class AlipayBillTask {
+
+ public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException {
+ BillDto billDto = new BillDto();
+ billDto.setBillId(23456789L);
+ billDto.setBillChannel("alipay");
+ // 璁剧疆娓呯畻鏃ユ湡
+ String settlementDate = (String) jobArgs.getWfContext().get("settlementDate");
+ if (StrUtil.equals(settlementDate, "sysdate")) {
+ settlementDate = DateUtil.today();
+ }
+ billDto.setBillDate(settlementDate);
+ billDto.setBillAmount(new BigDecimal("2345.67"));
+ // 鎶奲illDto瀵硅薄鏀惧叆涓婁笅鏂囪繘琛屼紶閫�
+ jobArgs.appendContext("alipay", JsonUtils.toJsonString(billDto));
+ SnailJobLog.REMOTE.info("涓婁笅鏂�: {}", jobArgs.getWfContext());
+ return ExecuteResult.success(billDto);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/SummaryBillTask.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/SummaryBillTask.java
new file mode 100755
index 0000000..47d7305
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/SummaryBillTask.java
@@ -0,0 +1,45 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.util.StrUtil;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.dto.JobArgs;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import com.aizuda.snailjob.model.dto.ExecuteResult;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.job.entity.BillDto;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+
+/**
+ * DAG宸ヤ綔娴佷换鍔�-妯℃嫙姹囨�昏处鍗曚换鍔�
+ * <a href="https://juejin.cn/post/7487860254114644019"></a>
+ *
+ * @author 鑰侀┈
+ */
+@Component
+@JobExecutor(name = "summaryBillTask")
+public class SummaryBillTask {
+
+ public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException {
+ // 鑾峰緱寰俊璐﹀崟
+ BigDecimal wechatAmount = BigDecimal.valueOf(0);
+ String wechat = (String) jobArgs.getWfContext("wechat");
+ if (StrUtil.isNotBlank(wechat)) {
+ BillDto wechatBillDto = JsonUtils.parseObject(wechat, BillDto.class);
+ wechatAmount = wechatBillDto.getBillAmount();
+ }
+ // 鑾峰緱鏀粯瀹濊处鍗�
+ BigDecimal alipayAmount = BigDecimal.valueOf(0);
+ String alipay = (String) jobArgs.getWfContext("alipay");
+ if (StrUtil.isNotBlank(alipay)) {
+ BillDto alipayBillDto = JsonUtils.parseObject(alipay, BillDto.class);
+ alipayAmount = alipayBillDto.getBillAmount();
+ }
+ // 姹囨�昏处鍗�
+ BigDecimal totalAmount = wechatAmount.add(alipayAmount);
+ SnailJobLog.REMOTE.info("鎬婚噾棰�: {}", totalAmount);
+ return ExecuteResult.success(totalAmount);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestAnnoJobExecutor.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestAnnoJobExecutor.java
new file mode 100755
index 0000000..aa3d99e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestAnnoJobExecutor.java
@@ -0,0 +1,25 @@
+package org.dromara.job.snailjob;
+
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.dto.JobArgs;
+import com.aizuda.snailjob.common.core.util.JsonUtil;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import com.aizuda.snailjob.model.dto.ExecuteResult;
+import org.springframework.stereotype.Component;
+
+/**
+ * 姝e父浠诲姟
+ * <a href="https://juejin.cn/post/7418074037392293914"></a>
+ *
+ * @author 鑰侀┈
+ */
+@Component
+@JobExecutor(name = "testJobExecutor")
+public class TestAnnoJobExecutor {
+
+ public ExecuteResult jobExecute(JobArgs jobArgs) {
+ SnailJobLog.LOCAL.info("testJobExecutor. jobArgs:{}", JsonUtil.toJsonString(jobArgs));
+ SnailJobLog.REMOTE.info("testJobExecutor. jobArgs:{}", JsonUtil.toJsonString(jobArgs));
+ return ExecuteResult.success("娴嬭瘯鎴愬姛");
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestBroadcastJob.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestBroadcastJob.java
new file mode 100755
index 0000000..9cb8309
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestBroadcastJob.java
@@ -0,0 +1,37 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.util.RandomUtil;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.dto.JobArgs;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import com.aizuda.snailjob.model.dto.ExecuteResult;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * 骞挎挱浠诲姟
+ * <a href="https://juejin.cn/post/7422948006150438950"></a>
+ *
+ * @author 鑰侀┈
+ */
+@Slf4j
+@Component
+@JobExecutor(name = "testBroadcastJob")
+public class TestBroadcastJob {
+
+ @Value("${snail-job.port}")
+ private int clientPort;
+
+ public ExecuteResult jobExecute(JobArgs jobArgs) {
+ int randomInt = RandomUtil.randomInt(100);
+ log.info("闅忔満鏁�: {}", randomInt);
+ SnailJobLog.REMOTE.info("闅忔満鏁�: {},瀹㈡埛绔鍙�:{}", randomInt, clientPort);
+ if (randomInt < 50) {
+ throw new RuntimeException("闅忔満鏁板皬浜�50锛屾敹闆嗘棩蹇椾换鍔℃墽琛屽け璐�");
+ }
+ // 鑾峰緱jobArgs 涓紶鍏ョ殑鐩稿姞鐨勪袱涓暟
+ return ExecuteResult.success("闅忔満鏁板ぇ浜�50锛屾敹闆嗘棩蹇椾换鍔℃墽琛屾垚鍔�");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestClassJobExecutor.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestClassJobExecutor.java
new file mode 100755
index 0000000..93da0a9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestClassJobExecutor.java
@@ -0,0 +1,19 @@
+package org.dromara.job.snailjob;
+
+import com.aizuda.snailjob.client.job.core.dto.JobArgs;
+import com.aizuda.snailjob.client.job.core.executor.AbstractJobExecutor;
+import com.aizuda.snailjob.model.dto.ExecuteResult;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author opensnail
+ * @date 2024-05-17
+ */
+@Component
+public class TestClassJobExecutor extends AbstractJobExecutor {
+
+ @Override
+ protected ExecuteResult doJobExecute(JobArgs jobArgs) {
+ return ExecuteResult.success("TestJobExecutor娴嬭瘯鎴愬姛");
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapJobAnnotation.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapJobAnnotation.java
new file mode 100755
index 0000000..f58d772
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapJobAnnotation.java
@@ -0,0 +1,53 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.aizuda.snailjob.client.job.core.MapHandler;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.annotation.MapExecutor;
+import com.aizuda.snailjob.client.job.core.dto.MapArgs;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import com.aizuda.snailjob.model.dto.ExecuteResult;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * Map浠诲姟 鍔ㄦ�佸垎閰� 鍙垎鐗囦笉鍏虫敞缁撴灉
+ * <a href="https://juejin.cn/post/7446362500478894106"></a>
+ *
+ * @author 鑰侀┈
+ */
+@Component
+@JobExecutor(name = "testMapJobAnnotation")
+public class TestMapJobAnnotation {
+
+ @MapExecutor
+ public ExecuteResult doJobMapExecute(MapArgs mapArgs, MapHandler mapHandler) {
+ // 鐢熸垚1~200鏁板�煎苟鍒嗙墖
+ int partitionSize = 50;
+ List<List<Integer>> partition = IntStream.rangeClosed(1, 200)
+ .boxed()
+ .collect(Collectors.groupingBy(i -> (i - 1) / partitionSize))
+ .values()
+ .stream()
+ .toList();
+ SnailJobLog.REMOTE.info("绔彛:{}瀹屾垚鍒嗛厤浠诲姟", SpringUtil.getProperty("server.port"));
+ return mapHandler.doMap(partition, "doCalc");
+ }
+
+ @MapExecutor(taskName = "doCalc")
+ public ExecuteResult doCalc(MapArgs mapArgs) {
+ List<Integer> sourceList = (List<Integer>) mapArgs.getMapResult();
+ // 閬嶅巻sourceList鐨勬瘡涓�涓厓绱�,璁$畻鍑轰竴涓疮鍔犲�紁artitionTotal
+ int partitionTotal = sourceList.stream().mapToInt(i -> i).sum();
+ // 鎵撳嵃鏃ュ織鍒版湇鍔″櫒
+ ThreadUtil.sleep(3, TimeUnit.SECONDS);
+ SnailJobLog.REMOTE.info("绔彛:{},partitionTotal:{}", SpringUtil.getProperty("server.port"), partitionTotal);
+ return ExecuteResult.success(partitionTotal);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapReduceAnnotation1.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapReduceAnnotation1.java
new file mode 100755
index 0000000..f926016
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapReduceAnnotation1.java
@@ -0,0 +1,60 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.aizuda.snailjob.client.job.core.MapHandler;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.annotation.MapExecutor;
+import com.aizuda.snailjob.client.job.core.annotation.ReduceExecutor;
+import com.aizuda.snailjob.client.job.core.dto.MapArgs;
+import com.aizuda.snailjob.client.job.core.dto.ReduceArgs;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import com.aizuda.snailjob.model.dto.ExecuteResult;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * MapReduce浠诲姟 鍔ㄦ�佸垎閰� 鍒嗙墖鍚庡悎骞剁粨鏋�
+ * <a href="https://juejin.cn/post/7448551286506913802"></a>
+ *
+ * @author 鑰侀┈
+ */
+@Component
+@JobExecutor(name = "testMapReduceAnnotation1")
+public class TestMapReduceAnnotation1 {
+
+ @MapExecutor
+ public ExecuteResult rootMapExecute(MapArgs mapArgs, MapHandler mapHandler) {
+ int partitionSize = 50;
+ List<List<Integer>> partition = IntStream.rangeClosed(1, 200)
+ .boxed()
+ .collect(Collectors.groupingBy(i -> (i - 1) / partitionSize))
+ .values()
+ .stream()
+ .toList();
+ SnailJobLog.REMOTE.info("绔彛:{}瀹屾垚鍒嗛厤浠诲姟", SpringUtil.getProperty("server.port"));
+ return mapHandler.doMap(partition, "doCalc");
+ }
+
+ @MapExecutor(taskName = "doCalc")
+ public ExecuteResult doCalc(MapArgs mapArgs) {
+ List<Integer> sourceList = (List<Integer>) mapArgs.getMapResult();
+ // 閬嶅巻sourceList鐨勬瘡涓�涓厓绱�,璁$畻鍑轰竴涓疮鍔犲�紁artitionTotal
+ int partitionTotal = sourceList.stream().mapToInt(i -> i).sum();
+ // 鎵撳嵃鏃ュ織鍒版湇鍔″櫒
+ ThreadUtil.sleep(3, TimeUnit.SECONDS);
+ SnailJobLog.REMOTE.info("绔彛:{},partitionTotal:{}", SpringUtil.getProperty("server.port"), partitionTotal);
+ return ExecuteResult.success(partitionTotal);
+ }
+
+ @ReduceExecutor
+ public ExecuteResult reduceExecute(ReduceArgs reduceArgs) {
+ int reduceTotal = reduceArgs.getMapResult().stream().mapToInt(i -> Integer.parseInt((String) i)).sum();
+ SnailJobLog.REMOTE.info("绔彛:{},reduceTotal:{}", SpringUtil.getProperty("server.port"), reduceTotal);
+ return ExecuteResult.success(reduceTotal);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestStaticShardingJob.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestStaticShardingJob.java
new file mode 100755
index 0000000..060fcd4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestStaticShardingJob.java
@@ -0,0 +1,37 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.convert.Convert;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.dto.JobArgs;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import com.aizuda.snailjob.model.dto.ExecuteResult;
+import org.springframework.stereotype.Component;
+
+/**
+ * 闈欐�佸垎鐗� 鏍规嵁鏈嶅姟绔换鍔″弬鏁板垎鐗�
+ * <a href="https://juejin.cn/post/7426232375703896101"></a>
+ *
+ * @author 鑰侀┈
+ */
+@Component
+@JobExecutor(name = "testStaticShardingJob")
+public class TestStaticShardingJob {
+
+ public ExecuteResult jobExecute(JobArgs jobArgs) {
+ String jobParams = Convert.toStr(jobArgs.getJobParams());
+ SnailJobLog.LOCAL.info("寮�濮嬫墽琛屽垎鐗囦换鍔�,鍙傛暟:{}", jobParams);
+ // 鑾峰緱jobArgs 涓紶鍏ョ殑寮�濮媔d鍜岀粨鏉焛d
+ String[] split = jobParams.split(",");
+ Long fromId = Long.parseLong(split[0]);
+ Long toId = Long.parseLong(split[1]);
+ // 妯℃嫙鏁版嵁搴撴搷浣�,瀵硅寖鍥磇d,杩涜鍔犲瘑澶勭悊
+ try {
+ SnailJobLog.REMOTE.info("寮�濮嬪id鑼冨洿:{}杩涜鍔犲瘑澶勭悊", fromId + "-" + toId);
+ Thread.sleep(3000);
+ SnailJobLog.REMOTE.info("瀵筰d鑼冨洿:{}杩涜鍔犲瘑澶勭悊瀹屾垚", fromId + "-" + toId);
+ } catch (InterruptedException e) {
+ return ExecuteResult.failure("浠诲姟鎵ц澶辫触");
+ }
+ return ExecuteResult.success("鎵ц鍒嗙墖浠诲姟瀹屾垚");
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/WechatBillTask.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/WechatBillTask.java
new file mode 100755
index 0000000..e19a48e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/WechatBillTask.java
@@ -0,0 +1,43 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.job.core.dto.JobArgs;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import com.aizuda.snailjob.model.dto.ExecuteResult;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.job.entity.BillDto;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+
+/**
+ * DAG宸ヤ綔娴佷换鍔�-妯℃嫙寰俊璐﹀崟浠诲姟
+ * <a href="https://juejin.cn/post/7487860254114644019"></a>
+ *
+ * @author 鑰侀┈
+ */
+@Component
+@JobExecutor(name = "wechatBillTask")
+public class WechatBillTask {
+
+ public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException {
+ BillDto billDto = new BillDto();
+ billDto.setBillId(123456789L);
+ billDto.setBillChannel("wechat");
+ // 浠庝笂涓嬫枃涓幏寰楁竻绠楁棩鏈熷苟璁剧疆锛屽鏋滀笂涓嬫枃涓竻绠楁棩鏈�
+ // 鏄痵ysdate璁剧疆涓哄綋鍓嶆棩鏈燂紱鍚﹀垯鍙栫鐞嗛〉闈㈣缃殑鍊�
+ String settlementDate = (String) jobArgs.getWfContext().get("settlementDate");
+ if (StrUtil.equals(settlementDate, "sysdate")) {
+ settlementDate = DateUtil.today();
+ }
+ billDto.setBillDate(settlementDate);
+ billDto.setBillAmount(new BigDecimal("1234.56"));
+ // 鎶奲illDto瀵硅薄鏀惧叆涓婁笅鏂囪繘琛屼紶閫�
+ jobArgs.appendContext("wechat", JsonUtils.toJsonString(billDto));
+ SnailJobLog.REMOTE.info("涓婁笅鏂�: {}", jobArgs.getWfContext());
+ return ExecuteResult.success(billDto);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/.flattened-pom.xml
new file mode 100644
index 0000000..835bd27
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/.flattened-pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-modules</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-qa</artifactId>
+ <version>5.5.3</version>
+ <description>璐ㄩ噺鍒嗘瀽妯″潡</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sms</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mail</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-idempotent</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-log</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-excel</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-security</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-ratelimiter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-translation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sensitive</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-encrypt</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-tenant</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-websocket</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/pom.xml
new file mode 100755
index 0000000..4e1d926
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/pom.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-modules</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-qa</artifactId>
+
+ <description>
+ 璐ㄩ噺鍒嗘瀽妯″潡
+ </description>
+
+ <dependencies>
+
+ <!-- 閫氱敤宸ュ叿-->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sms</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mail</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-redis</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-idempotent</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-log</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-excel</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-security</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-web</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-ratelimiter</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-translation</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sensitive</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-encrypt</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-tenant</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-websocket</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/readMe.md b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/readMe.md
new file mode 100755
index 0000000..7700bd1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/readMe.md
@@ -0,0 +1,499 @@
+
+
+鍔ㄦ�佽繍琛屽弬鏁� my_new_key - > 闇�瑕佸瓨鍌ㄦ暟鎹簱鐨勪笂绾ey锛� sync-interval-ms - > 瀛樺偍鏃堕棿闂撮殧
+java -jar TimescaleDB-Utils.jar \
+ --spring.redis.queue-key=my_new_key \
+ --spring.redis.sync-interval-ms=2000
+
+
+
+//鍗锋帴鏈鸿〃
+-- 鍒涘缓涓昏〃
+CREATE TABLE roller_time_data (
+-- 鏃跺簭琛ㄥ繀闇�鐨勫瓧娈�
+time TIMESTAMPTZ NOT NULL,
+key TEXT NOT NULL, -- 璁句负 NOT NULL 鍥犱负鎮ㄦ湁鍥哄畾鐨� key 鍒楄〃
+ shift INTEGER, -- 鐝 (1浣嶆暟瀛�)
+ equ_no INTEGER, -- 璁惧鍙� (3浣嶆暟瀛�)
+
+ -- 鎮ㄧ殑涓氬姟瀛楁
+ online INTEGER, -- 缃戠粶鐘舵��(0寮傚父 1姝e父)
+ qty DOUBLE PRECISION, -- 浜ч噺
+ bad_qty DOUBLE PRECISION, -- 鍓旈櫎浜ч噺
+ lvbang_val DOUBLE PRECISION, -- 婊ゆ娑堣��
+ juanyanzhi_val DOUBLE PRECISION, -- 鍗风儫绾告秷鑰�
+ shuisongzhi_val DOUBLE PRECISION, -- 姘存澗绾告秷鑰�
+ run_time DOUBLE PRECISION, -- 杩愯鏃堕棿
+ stop_time DOUBLE PRECISION, -- 鍋滄満鏃堕棿
+ stop_times INTEGER, -- 鍋滄満娆℃暟
+ speed INTEGER, -- 杞﹂��
+ run_status INTEGER, -- 杩愯鐘舵��(-1鏂綉 0鍋滄 1浣庨�熻繍琛� 2姝e父杩愯)
+ cy DOUBLE PRECISION, -- 鍌ㄧ儫璁惧鍌ㄩ噺
+ cy_cs INTEGER, -- 鍌ㄧ儫璁惧杞﹂��(1-鍖呰鏈� 6-鍗风儫鏈�)
+ cy_online TEXT, -- (22-鍗风儫鏈鸿繍琛岀姸鎬� 23-鍖呰鏈鸿繍琛岀姸鎬�)
+ rec_qty1 DOUBLE PRECISION, -- 鎺ユ敹鏈洪噺
+ rec_qty2 DOUBLE PRECISION -- 鎺ユ敹鏈洪噺2
+);
+
+-- 灏嗚〃杞崲涓鸿秴琛紙鎸夊ぉ鍒嗗尯锛屾寜key杩涜绌洪棿鍒嗗尯锛�
+SELECT create_hypertable(
+'roller_time_data',
+'time',
+chunk_time_interval => INTERVAL '1 day'
+);
+
+-- 娣诲姞绌洪棿鍒嗗尯缁村害锛堟寜key鍒嗗尯锛�
+-- 鍒嗘垚 8 涓垎鍖猴紝姣忎釜鍒嗗尯澶х害鍖呭惈 4-5 涓� key
+SELECT add_dimension(
+'roller_time_data',
+'key',
+number_partitions => 8
+);
+
+-- 鍒涘缓绱㈠紩浠ユ彁楂樻煡璇㈡�ц兘
+-- 澶嶅悎绱㈠紩锛岄�傚悎鎸夋椂闂村拰key鏌ヨ
+CREATE INDEX idx_roller_time_data_time_key ON roller_time_data (time DESC, key);
+-- 鍗曠嫭绱㈠紩锛岄�傚悎鎸塳ey鏌ヨ
+CREATE INDEX idx_roller_time_data_key_time ON roller_time_data (key, time DESC);
+-- 涓氬姟鏌ヨ甯哥敤瀛楁绱㈠紩
+CREATE INDEX idx_roller_time_data_run_status_key ON roller_time_data (run_status, key, time DESC);
+CREATE INDEX idx_roller_time_data_online_key ON roller_time_data (online, key, time DESC);
+CREATE INDEX idx_roller_time_data_speed_key ON roller_time_data (speed, key, time DESC);
+
+-- 鐝鍜岃澶囧彿绱㈠紩
+CREATE INDEX idx_roller_time_data_shift ON roller_time_data (shift);
+CREATE INDEX idx_roller_time_data_equ_no ON roller_time_data (equ_no);
+
+-- 鍙�夛細濡傛灉鏌愪簺璁惧鏁版嵁閲忕壒鍒ぇ锛屽彲浠ヤ负鐗瑰畾璁惧鍒涘缓涓撶敤绱㈠紩
+-- CREATE INDEX idx_roller_1101 ON roller_time_data (time DESC) WHERE key = '1101';
+
+
+
+
+
+
+//鍖呰鏈�
+-- 鍒涘缓涓昏〃
+CREATE TABLE packer_time_data (
+-- 鏃跺簭琛ㄥ繀闇�鐨勫瓧娈�
+time TIMESTAMPTZ NOT NULL,
+key TEXT NOT NULL,
+ shift INTEGER, -- 鐝 (1浣嶆暟瀛�)
+ equ_no INTEGER, -- 璁惧鍙� (3浣嶆暟瀛�)
+
+ -- 鎮ㄧ殑涓氬姟瀛楁
+ online INTEGER, -- 缃戠粶鐘舵��(0寮傚父锛�1姝e父)
+ qty DOUBLE PRECISION, -- 浜ч噺
+ bad_qty DOUBLE PRECISION, -- 鍓旈櫎浜ч噺
+ xiaohemo_val DOUBLE PRECISION, -- 灏忕洅鑶滄秷鑰�
+ tiaohemo_val DOUBLE PRECISION, -- 鏉$洅鑶滄秷鑰�
+ xiaohezhi_val DOUBLE PRECISION, -- 灏忕洅绾告秷鑰�
+ tiaohezhi_val DOUBLE PRECISION, -- 鏉$洅绾告秷鑰�
+ neichenzhi_val DOUBLE PRECISION, -- 鍐呰‖绾告秷鑰�
+ run_time DOUBLE PRECISION, -- 杩愯鏃堕棿
+ stop_time DOUBLE PRECISION, -- 鍋滄満鏃堕棿
+ stop_times INTEGER, -- 鍋滄満娆℃暟
+ speed INTEGER, -- 杞﹂��
+ run_status INTEGER, -- 杩愯鐘舵��(-1 鏂綉 0鍋滄 1浣庨�熻繍琛� 2姝e父杩愯)
+ ts_qty DOUBLE PRECISION, -- 鎻愬崌鏈轰骇閲�
+ main_qty DOUBLE PRECISION, -- 涓绘満浜ч噺锛堝皬鍖呮満锛�
+ main_bad_qty DOUBLE PRECISION, -- 涓绘満鍓旈櫎閲�
+ tbj_qty DOUBLE PRECISION, -- 閫忓寘鏈轰骇閲�
+ tbj_gd_qty DOUBLE PRECISION, -- 閫忓寘鏈哄墧闄ゅソ鍖�
+ tbj_bad_qty DOUBLE PRECISION, -- 閫忓寘鏈哄墧闄ゅ潖鍖�
+ pbj_qty DOUBLE PRECISION -- 鎺掑寘鏈轰骇閲�
+);
+
+-- 灏嗚〃杞崲涓鸿秴琛紙鎸夊ぉ鍒嗗尯锛�
+SELECT create_hypertable(
+'packer_time_data',
+'time',
+chunk_time_interval => INTERVAL '1 day'
+);
+
+-- 娣诲姞绌洪棿鍒嗗尯缁村害锛堟寜key鍒嗗尯锛屽垎鎴�8涓垎鍖猴級
+SELECT add_dimension(
+'packer_time_data',
+'key',
+number_partitions => 8
+);
+
+-- 鍒涘缓澶嶅悎绱㈠紩锛堟椂闂�+key锛�
+CREATE INDEX idx_packer_time_data_time_key ON packer_time_data (time DESC, key);
+
+-- 鍒涘缓鍙嶅悜澶嶅悎绱㈠紩锛坘ey+鏃堕棿锛�
+CREATE INDEX idx_packer_time_data_key_time ON packer_time_data (key, time DESC);
+
+-- 鍒涘缓杩愯鐘舵�佺浉鍏崇储寮�
+CREATE INDEX idx_packer_run_status_key ON packer_time_data (run_status, key, time DESC);
+
+-- 鍒涘缓缃戠粶鐘舵�佺储寮�
+CREATE INDEX idx_packer_online_key ON packer_time_data (online, key, time DESC);
+
+-- 鍒涘缓杞﹂�熺储寮�
+CREATE INDEX idx_packer_speed_key ON packer_time_data (speed, key, time DESC);
+
+-- 鍒涘缓浜ч噺鐩稿叧绱㈠紩锛堝父鐢ㄧ粺璁℃煡璇級
+CREATE INDEX idx_packer_qty_key ON packer_time_data (qty, key, time DESC);
+
+-- 鍒涘缓鍋滄満鐩稿叧绱㈠紩
+CREATE INDEX idx_packer_stop_key ON packer_time_data (stop_times, key, time DESC);
+
+-- 鐝鍜岃澶囧彿绱㈠紩
+CREATE INDEX idx_packer_time_data_shift ON packer_time_data (shift);
+CREATE INDEX idx_packer_time_data_equ_no ON packer_time_data (equ_no);
+
+
+
+
+
+//瑁呯鏈�
+-- 鍒涘缓涓昏〃
+CREATE TABLE box_time_data (
+-- 鏃跺簭琛ㄥ繀闇�鐨勫瓧娈�
+time TIMESTAMPTZ NOT NULL,
+key TEXT NOT NULL,
+ shift INTEGER, -- 鐝 (1浣嶆暟瀛�)
+ equ_no INTEGER, -- 璁惧鍙� (3浣嶆暟瀛�)
+
+ -- 鎮ㄧ殑涓氬姟瀛楁
+ online INTEGER, -- 缃戠粶鐘舵�� 0寮傚父锛�1姝e父
+ qty1 DOUBLE PRECISION, -- 1#瑁呭皝绠辨満浜ч噺
+ pbj_qty DOUBLE PRECISION -- 鎺掑寘鏈轰骇閲�
+);
+
+-- 灏嗚〃杞崲涓鸿秴琛紙鎸夊ぉ鍒嗗尯锛�
+SELECT create_hypertable(
+'box_time_data',
+'time',
+chunk_time_interval => INTERVAL '1 day'
+);
+
+-- 娣诲姞绌洪棿鍒嗗尯缁村害锛堟寜key鍒嗗尯锛屽垎鎴�8涓垎鍖猴級
+SELECT add_dimension(
+'box_time_data',
+'key',
+number_partitions => 8
+);
+
+-- 鍒涘缓澶嶅悎绱㈠紩锛堟椂闂�+key锛� - 涓绘煡璇㈡ā寮�
+CREATE INDEX idx_box_time_data_time_key ON box_time_data (time DESC, key);
+
+-- 鍒涘缓鍙嶅悜澶嶅悎绱㈠紩锛坘ey+鏃堕棿锛� - 鎸夎澶囨煡璇�
+CREATE INDEX idx_box_time_data_key_time ON box_time_data (key, time DESC);
+
+-- 鍒涘缓缃戠粶鐘舵�佺储寮�
+CREATE INDEX idx_box_online_key ON box_time_data (online, key, time DESC);
+
+-- 鍒涘缓浜ч噺绱㈠紩锛堝父鐢ㄤ簬缁熻鏌ヨ锛�
+CREATE INDEX idx_box_qty1_key ON box_time_data (qty1, key, time DESC);
+CREATE INDEX idx_box_pbj_qty_key ON box_time_data (pbj_qty, key, time DESC);
+
+-- 鍒涘缓澶嶅悎浜ч噺绱㈠紩锛堝悓鏃舵煡璇袱涓骇閲忓瓧娈碉級
+CREATE INDEX idx_box_qtys_key ON box_time_data (qty1, pbj_qty, key, time DESC);
+
+-- 鐝鍜岃澶囧彿绱㈠紩
+CREATE INDEX idx_box_time_data_shift ON box_time_data (shift);
+CREATE INDEX idx_box_time_data_equ_no ON box_time_data (equ_no);
+
+
+
+
+
+//鎴愬瀷鏈�
+-- 鍒涘缓涓昏〃
+CREATE TABLE makeup_time_data (
+-- 鏃跺簭琛ㄥ繀闇�鐨勫瓧娈�
+time TIMESTAMPTZ NOT NULL,
+key TEXT NOT NULL,
+ shift INTEGER, -- 鐝 (1浣嶆暟瀛�)
+ equ_no INTEGER, -- 璁惧鍙� (3浣嶆暟瀛�)
+
+ -- 鎮ㄧ殑涓氬姟瀛楁
+ online INTEGER, -- 缃戠粶鐘舵�� 0寮傚父锛�1姝e父
+ qty DOUBLE PRECISION, -- 浜ч噺
+ bad_qty DOUBLE PRECISION, -- 鍓旈櫎浜ч噺
+ panzhi_val DOUBLE PRECISION, -- 鐩樼焊娑堣��
+ run_time DOUBLE PRECISION, -- 杩愯鏃堕棿
+ stop_time DOUBLE PRECISION, -- 鍋滄満鏃堕棿
+ stop_times INTEGER, -- 鍋滄満娆℃暟
+ speed INTEGER -- 杞﹂��
+);
+
+-- 灏嗚〃杞崲涓鸿秴琛紙鎸夊ぉ鍒嗗尯锛�
+SELECT create_hypertable(
+'makeup_time_data',
+'time',
+chunk_time_interval => INTERVAL '1 day'
+);
+
+-- 娣诲姞绌洪棿鍒嗗尯缁村害锛堟寜key鍒嗗尯锛屽垎鎴�8涓垎鍖猴級
+SELECT add_dimension(
+'makeup_time_data',
+'key',
+number_partitions => 8
+);
+
+-- 鍒涘缓涓绘煡璇㈠鍚堢储寮曪紙鏃堕棿+key锛�
+CREATE INDEX idx_makeup_time_data_time_key ON makeup_time_data (time DESC, key);
+
+-- 鍒涘缓鎸夎澶囨煡璇㈠鍚堢储寮曪紙key+鏃堕棿锛�
+CREATE INDEX idx_makeup_time_data_key_time ON makeup_time_data (key, time DESC);
+
+-- 鍒涘缓缃戠粶鐘舵�佺储寮�
+CREATE INDEX idx_makeup_online_key ON makeup_time_data (online, key, time DESC);
+
+-- 鐝鍜岃澶囧彿绱㈠紩
+CREATE INDEX idx_makeup_time_data_shift ON makeup_time_data (shift);
+CREATE INDEX idx_makeup_time_data_equ_no ON makeup_time_data (equ_no);
+
+
+
+
+
+//鍙戝皠鏈�
+-- 鍒涘缓涓昏〃
+CREATE TABLE trans_time_data (
+-- 鏃跺簭琛ㄥ繀闇�鐨勫瓧娈�
+time TIMESTAMPTZ NOT NULL,
+key TEXT NOT NULL,
+ shift INTEGER, -- 鐝 (1浣嶆暟瀛�)
+ equ_no INTEGER, -- 璁惧鍙� (3浣嶆暟瀛�)
+
+ -- 鎮ㄧ殑涓氬姟瀛楁
+ online INTEGER, -- 缃戠粶鐘舵�� 0寮傚父锛�1姝e父
+
+ -- 绠¢亾婊ゆ璁℃暟锛�1-10鍙风閬擄級
+ p_qty1 DOUBLE PRECISION, -- 绠¢亾1婊ゆ璁℃暟
+ p_qty2 DOUBLE PRECISION, -- 绠¢亾2婊ゆ璁℃暟
+ p_qty3 DOUBLE PRECISION, -- 绠¢亾3婊ゆ璁℃暟
+ p_qty4 DOUBLE PRECISION, -- 绠¢亾4婊ゆ璁℃暟
+ p_qty5 DOUBLE PRECISION, -- 绠¢亾5婊ゆ璁℃暟
+ p_qty6 DOUBLE PRECISION, -- 绠¢亾6婊ゆ璁℃暟
+ p_qty7 DOUBLE PRECISION, -- 绠¢亾7婊ゆ璁℃暟
+ p_qty8 DOUBLE PRECISION, -- 绠¢亾8婊ゆ璁℃暟
+ p_qty9 DOUBLE PRECISION, -- 绠¢亾9婊ゆ璁℃暟
+ p_qty10 DOUBLE PRECISION, -- 绠¢亾10婊ゆ璁℃暟
+
+ -- 绠¢亾婊ゆ閫熷害锛�1-10鍙风閬擄級
+ speed1 DOUBLE PRECISION, -- 绠¢亾1婊ゆ閫熷害
+ speed2 DOUBLE PRECISION, -- 绠¢亾2婊ゆ閫熷害
+ speed3 DOUBLE PRECISION, -- 绠¢亾3婊ゆ閫熷害
+ speed4 DOUBLE PRECISION, -- 绠¢亾4婊ゆ閫熷害
+ speed5 DOUBLE PRECISION, -- 绠¢亾5婊ゆ閫熷害
+ speed6 DOUBLE PRECISION, -- 绠¢亾6婊ゆ閫熷害
+ speed7 DOUBLE PRECISION, -- 绠¢亾7婊ゆ閫熷害
+ speed8 DOUBLE PRECISION, -- 绠¢亾8婊ゆ閫熷害
+ speed9 DOUBLE PRECISION, -- 绠¢亾9婊ゆ閫熷害
+ speed10 DOUBLE PRECISION, -- 绠¢亾10婊ゆ閫熷害
+
+ -- 鍗哥洏鏈虹浉鍏�
+ xp_state INTEGER, -- 鍗哥洏鏈虹姸鎬�
+ tray_qty1 DOUBLE PRECISION -- 鍗哥洏鏈轰骇閲�
+);
+
+-- 灏嗚〃杞崲涓鸿秴琛紙鎸夊ぉ鍒嗗尯锛�
+SELECT create_hypertable(
+'trans_time_data',
+'time',
+chunk_time_interval => INTERVAL '1 day'
+);
+
+-- 娣诲姞绌洪棿鍒嗗尯缁村害锛堟寜key鍒嗗尯锛屽垎鎴�8涓垎鍖猴級
+SELECT add_dimension(
+'trans_time_data',
+'key',
+number_partitions => 8
+);
+
+-- 鍒涘缓涓绘煡璇㈠鍚堢储寮�
+CREATE INDEX idx_trans_time_data_time_key ON trans_time_data (time DESC, key);
+CREATE INDEX idx_trans_time_data_key_time ON trans_time_data (key, time DESC);
+-- 鐝鍜岃澶囧彿绱㈠紩
+CREATE INDEX idx_trans_time_data_shift ON trans_time_data (shift);
+CREATE INDEX idx_trans_time_data_equ_no ON trans_time_data (equ_no);
+
+-- 鍒涘缓缃戠粶鐘舵�佺储寮�
+CREATE INDEX idx_trans_online_key ON trans_time_data (online, key, time DESC);
+
+-- 鍒涘缓鍗哥洏鏈虹姸鎬佺储寮�
+CREATE INDEX idx_trans_xp_state_key ON trans_time_data (xp_state, key, time DESC);
+
+
+
+
+
+//鎻愬崌鏈�
+-- 鍒涘缓涓昏〃
+CREATE TABLE hoister_time_data (
+-- 鏃跺簭琛ㄥ繀闇�鐨勫瓧娈�
+time TIMESTAMPTZ NOT NULL,
+key TEXT NOT NULL,
+ shift INTEGER, -- 鐝 (1浣嶆暟瀛�)
+ equ_no INTEGER, -- 璁惧鍙� (3浣嶆暟瀛�)
+
+ -- 鍩虹瀛楁
+ online INTEGER, -- 缃戠粶鐘舵�� 0寮傚父锛�1姝e父
+ qty DOUBLE PRECISION, -- 浜ч噺锛堟�讳骇閲忥紵锛�
+
+ -- 鎻愬崌鏈虹姸鎬侊紙1-12鍙凤級
+ t_state1 DOUBLE PRECISION, -- 1#鎻愬崌鏈虹姸鎬�
+ t_state2 DOUBLE PRECISION, -- 2#鎻愬崌鏈虹姸鎬�
+ t_state3 DOUBLE PRECISION, -- 3#鎻愬崌鏈虹姸鎬�
+ t_state4 DOUBLE PRECISION, -- 4#鎻愬崌鏈虹姸鎬�
+ t_state5 DOUBLE PRECISION, -- 5#鎻愬崌鏈虹姸鎬�
+ t_state6 DOUBLE PRECISION, -- 6#鎻愬崌鏈虹姸鎬�
+ t_state7 DOUBLE PRECISION, -- 7#鎻愬崌鏈虹姸鎬�
+ t_state8 DOUBLE PRECISION, -- 8#鎻愬崌鏈虹姸鎬�
+ t_state9 DOUBLE PRECISION, -- 9#鎻愬崌鏈虹姸鎬�
+ t_state10 DOUBLE PRECISION, -- 10#鎻愬崌鏈虹姸鎬�
+ t_state11 DOUBLE PRECISION, -- 11#鎻愬崌鏈虹姸鎬�
+ t_state12 DOUBLE PRECISION, -- 12#鎻愬崌鏈虹姸鎬�
+
+ -- 鎺掑寘鏈虹姸鎬侊紙1-4鍙凤級
+ p_state1 DOUBLE PRECISION, -- 1#鎺掑寘鏈虹姸鎬�
+ p_state2 DOUBLE PRECISION, -- 2#鎺掑寘鏈虹姸鎬�
+ p_state3 DOUBLE PRECISION, -- 3#鎺掑寘鏈虹姸鎬�
+ p_state4 DOUBLE PRECISION, -- 4#鎺掑寘鏈虹姸鎬�
+
+ -- 鎻愬崌鏈轰骇閲忥紙1-12鍙凤級
+ t_qty1 DOUBLE PRECISION, -- 1#鎻愬崌鏈轰骇閲�
+ t_qty2 DOUBLE PRECISION, -- 2#鎻愬崌鏈轰骇閲�
+ t_qty3 DOUBLE PRECISION, -- 3#鎻愬崌鏈轰骇閲�
+ t_qty4 DOUBLE PRECISION, -- 4#鎻愬崌鏈轰骇閲�
+ t_qty5 DOUBLE PRECISION, -- 5#鎻愬崌鏈轰骇閲�
+ t_qty6 DOUBLE PRECISION, -- 6#鎻愬崌鏈轰骇閲�
+ t_qty7 DOUBLE PRECISION, -- 7#鎻愬崌鏈轰骇閲�
+ t_qty8 DOUBLE PRECISION, -- 8#鎻愬崌鏈轰骇閲�
+ t_qty9 DOUBLE PRECISION, -- 9#鎻愬崌鏈轰骇閲�
+ t_qty10 DOUBLE PRECISION, -- 10#鎻愬崌鏈轰骇閲�
+ t_qty11 DOUBLE PRECISION, -- 11#鎻愬崌鏈轰骇閲�
+ t_qty12 DOUBLE PRECISION, -- 12#鎻愬崌鏈轰骇閲�
+
+ -- 鎺掑寘鏈轰骇閲忥紙1-4鍙凤級
+ p_qty1 DOUBLE PRECISION, -- 1#鎺掑寘鏈轰骇閲�
+ p_qty2 DOUBLE PRECISION, -- 2#鎺掑寘鏈轰骇閲�
+ p_qty3 DOUBLE PRECISION, -- 3#鎺掑寘鏈轰骇閲�
+ p_qty4 DOUBLE PRECISION -- 4#鎺掑寘鏈轰骇閲�
+);
+
+-- 灏嗚〃杞崲涓鸿秴琛紙鎸夊ぉ鍒嗗尯锛�
+SELECT create_hypertable(
+'hoister_time_data',
+'time',
+chunk_time_interval => INTERVAL '1 day'
+);
+
+-- 娣诲姞绌洪棿鍒嗗尯缁村害锛堟寜key鍒嗗尯锛屽垎鎴�8涓垎鍖猴級
+SELECT add_dimension(
+'hoister_time_data',
+'key',
+number_partitions => 8
+);
+
+-- 鍒涘缓涓绘煡璇㈠鍚堢储寮�
+CREATE INDEX idx_hoister_time_data_time_key ON hoister_time_data (time DESC, key);
+CREATE INDEX idx_hoister_time_data_key_time ON hoister_time_data (key, time DESC);
+
+-- 鍒涘缓缃戠粶鐘舵�佺储寮�
+CREATE INDEX idx_hoister_online_key ON hoister_time_data (online, key, time DESC);
+
+-- 鍒涘缓鎬讳骇閲忕储寮�
+CREATE INDEX idx_hoister_qty_key ON hoister_time_data (qty, key, time DESC);
+
+-- 鍒涘缓鎻愬崌鏈虹姸鎬侀�氱敤绱㈠紩锛堟渶甯哥敤鐨勫嚑涓級
+CREATE INDEX idx_hoister_t_state1_key ON hoister_time_data (t_state1, key, time DESC);
+CREATE INDEX idx_hoister_t_state2_key ON hoister_time_data (t_state2, key, time DESC);
+CREATE INDEX idx_hoister_t_state3_key ON hoister_time_data (t_state3, key, time DESC);
+
+-- 鍒涘缓鎺掑寘鏈虹姸鎬佺储寮�
+CREATE INDEX idx_hoister_p_state1_key ON hoister_time_data (p_state1, key, time DESC);
+CREATE INDEX idx_hoister_p_state2_key ON hoister_time_data (p_state2, key, time DESC);
+
+-- 鐝鍜岃澶囧彿绱㈠紩
+CREATE INDEX idx_hoister_time_data_shift ON hoister_time_data (shift);
+CREATE INDEX idx_hoister_time_data_equ_no ON hoister_time_data (equ_no);
+
+
+
+
+//鍠備笣鏈�
+-- 鍒涘缓涓昏〃
+CREATE TABLE feedmatch_time_data (
+ time TIMESTAMPTZ NOT NULL,
+key TEXT NOT NULL,
+shift INTEGER, -- 鐝 (1浣嶆暟瀛�)
+equ_no INTEGER, -- 璁惧鍙� (3浣嶆暟瀛�)
+
+ -- 鏁版嵁鏇存柊鏃堕棿锛堝瓧绗︿覆鏍煎紡锛�
+ dac_up_time TEXT, -- 鏁版嵁鏇存柊鏃堕棿 鏍煎紡锛�'2024-11-29 09:13:43'
+
+ -- 鍠備笣鏈哄搴旂殑鍌ㄤ笣鏌滐紙1#-4#鍠備笣鏈猴紝姣忎釜瀵瑰簲2涓偍涓濇煖锛�- 鏁村瀷
+ fs11 INTEGER, -- 1#鍠備笣鏈哄搴旂殑绗竴涓偍涓濇煖
+ fs12 INTEGER, -- 1#鍠備笣鏈哄搴旂殑绗簩涓偍涓濇煖
+ fs21 INTEGER, -- 2#鍠備笣鏈哄搴旂殑绗竴涓偍涓濇煖
+ fs22 INTEGER, -- 2#鍠備笣鏈哄搴旂殑绗簩涓偍涓濇煖
+ fs31 INTEGER, -- 3#鍠備笣鏈哄搴旂殑绗竴涓偍涓濇煖
+ fs32 INTEGER, -- 3#鍠備笣鏈哄搴旂殑绗簩涓偍涓濇煖
+ fs41 INTEGER, -- 4#鍠備笣鏈哄搴旂殑绗竴涓偍涓濇煖
+ fs42 INTEGER, -- 4#鍠備笣鏈哄搴旂殑绗簩涓偍涓濇煖
+
+ -- 鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬擄紙1#-12#鏈虹粍锛�- 鏁村瀷
+ pipe01 INTEGER, -- 1#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ pipe02 INTEGER, -- 2#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ pipe03 INTEGER, -- 3#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ pipe04 INTEGER, -- 4#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ pipe05 INTEGER, -- 5#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ pipe06 INTEGER, -- 6#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ pipe07 INTEGER, -- 7#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ pipe08 INTEGER, -- 8#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ pipe09 INTEGER, -- 9#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ pipe10 INTEGER, -- 10#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ pipe11 INTEGER, -- 11#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ pipe12 INTEGER, -- 12#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+
+ -- 鍠備笣鏈虹姸鎬� - 鏁村瀷
+ wsj_state INTEGER -- 鍠備笣鏈虹姸鎬� 1-杩炴帴 0-鏂紑
+);
+
+-- 灏嗚〃杞崲涓鸿秴琛紙鎸夊ぉ鍒嗗尯锛�
+SELECT create_hypertable(
+'feedmatch_time_data',
+'time',
+chunk_time_interval => INTERVAL '1 day'
+);
+
+-- 娣诲姞绌洪棿鍒嗗尯缁村害锛堟寜key鍒嗗尯锛屽垎鎴�8涓垎鍖猴級
+SELECT add_dimension(
+'feedmatch_time_data',
+'key',
+number_partitions => 8
+);
+
+-- 鍒涘缓涓绘煡璇㈠鍚堢储寮�
+CREATE INDEX idx_feedmatch_time_data_time_key ON feedmatch_time_data (time DESC, key);
+CREATE INDEX idx_feedmatch_time_data_key_time ON feedmatch_time_data (key, time DESC);
+
+-- 鐝鍜岃澶囧彿绱㈠紩
+CREATE INDEX idx_feedmatch_time_data_shift ON feedmatch_time_data (shift);
+CREATE INDEX idx_feedmatch_time_data_equ_no ON feedmatch_time_data (equ_no);
+
+//鍚勮〃娣诲姞閫氱敤瀛楁 ALTER TABLE public.trans_time_data ADD COLUMN IF NOT EXISTS create_dept integer, ADD COLUMN IF NOT EXISTS create_by bigint, ADD COLUMN IF NOT EXISTS create_time timestamp, ADD COLUMN IF NOT EXISTS update_by bigint, ADD COLUMN IF NOT EXISTS update_time timestamp, ADD COLUMN IF NOT EXISTS remark varchar(255);
+
+
+
+// 鍘嗗彶鏁版嵁鏇存柊鑴氭湰锛堟墽琛屼竴娆″嵆鍙級
+-- 鏇存柊 roller_time_data
+UPDATE roller_time_data SET shift = CAST(SUBSTRING(key FROM 1 FOR 1) AS INTEGER), equ_no = CAST(SUBSTRING(key FROM 2 FOR 3) AS INTEGER) WHERE shift IS NULL;
+-- 鏇存柊 packer_time_data
+UPDATE packer_time_data SET shift = CAST(SUBSTRING(key FROM 1 FOR 1) AS INTEGER), equ_no = CAST(SUBSTRING(key FROM 2 FOR 3) AS INTEGER) WHERE shift IS NULL;
+-- 鏇存柊 box_time_data
+UPDATE box_time_data SET shift = CAST(SUBSTRING(key FROM 1 FOR 1) AS INTEGER), equ_no = CAST(SUBSTRING(key FROM 2 FOR 3) AS INTEGER) WHERE shift IS NULL;
+-- 鏇存柊 makeup_time_data
+UPDATE makeup_time_data SET shift = CAST(SUBSTRING(key FROM 1 FOR 1) AS INTEGER), equ_no = CAST(SUBSTRING(key FROM 2 FOR 3) AS INTEGER) WHERE shift IS NULL;
+-- 鏇存柊 trans_time_data
+UPDATE trans_time_data SET shift = CAST(SUBSTRING(key FROM 1 FOR 1) AS INTEGER), equ_no = CAST(SUBSTRING(key FROM 2 FOR 3) AS INTEGER) WHERE shift IS NULL;
+-- 鏇存柊 hoister_time_data
+UPDATE hoister_time_data SET shift = CAST(SUBSTRING(key FROM 1 FOR 1) AS INTEGER), equ_no = CAST(SUBSTRING(key FROM 2 FOR 3) AS INTEGER) WHERE shift IS NULL;
+-- 鏇存柊 feedmatch_time_data
+UPDATE feedmatch_time_data SET shift = CAST(SUBSTRING(key FROM 1 FOR 1) AS INTEGER), equ_no = CAST(SUBSTRING(key FROM 2 FOR 3) AS INTEGER) WHERE shift IS NULL;
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/.DS_Store b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/.DS_Store
new file mode 100755
index 0000000..91ee1eb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/.DS_Store
Binary files differ
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/.DS_Store b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/.DS_Store
new file mode 100755
index 0000000..48a2200
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/.DS_Store
Binary files differ
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/.DS_Store b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/.DS_Store
new file mode 100755
index 0000000..5008ddf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/.DS_Store
Binary files differ
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/controller/HoisterTimeDataController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/controller/HoisterTimeDataController.java
new file mode 100755
index 0000000..2673697
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/controller/HoisterTimeDataController.java
@@ -0,0 +1,106 @@
+package org.dromara.qa.controller;
+
+import java.util.Date;
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.qa.domain.vo.HoisterTimeDataVo;
+import org.dromara.qa.domain.bo.HoisterTimeDataBo;
+import org.dromara.qa.service.IHoisterTimeDataService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 鎻愬崌鏈哄垎鏋�
+ *
+ * @author zhuguifei
+ * @date 2026-02-02
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/analy/hoister")
+public class HoisterTimeDataController extends BaseController {
+
+ private final IHoisterTimeDataService hoisterTimeDataService;
+
+ /**
+ * 鏌ヨ鎻愬崌鏈哄垎鏋愬垪琛�
+ */
+ @SaCheckPermission("analy:hoisterData:list")
+ @GetMapping("/list")
+ public TableDataInfo<HoisterTimeDataVo> list(HoisterTimeDataBo bo, PageQuery pageQuery) {
+ return hoisterTimeDataService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 瀵煎嚭鎻愬崌鏈哄垎鏋愬垪琛�
+ */
+ @SaCheckPermission("analy:hoisterData:export")
+ @Log(title = "鎻愬崌鏈哄垎鏋�", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(HoisterTimeDataBo bo, HttpServletResponse response) {
+ List<HoisterTimeDataVo> list = hoisterTimeDataService.queryList(bo);
+ ExcelUtil.exportExcel(list, "鎻愬崌鏈哄垎鏋�", HoisterTimeDataVo.class, response);
+ }
+
+ /**
+ * 鑾峰彇鎻愬崌鏈哄垎鏋愯缁嗕俊鎭�
+ *
+ * @param time 涓婚敭
+ */
+ @SaCheckPermission("analy:hoisterData:query")
+ @GetMapping("/{time}")
+ public R<HoisterTimeDataVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Date time) {
+ return R.ok(hoisterTimeDataService.queryById(time));
+ }
+
+ /**
+ * 鏂板鎻愬崌鏈哄垎鏋�
+ */
+ @SaCheckPermission("analy:hoisterData:add")
+ @Log(title = "鎻愬崌鏈哄垎鏋�", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R<Void> add(@Validated(AddGroup.class) @RequestBody HoisterTimeDataBo bo) {
+ return toAjax(hoisterTimeDataService.insertByBo(bo));
+ }
+
+ /**
+ * 淇敼鎻愬崌鏈哄垎鏋�
+ */
+ @SaCheckPermission("analy:hoisterData:edit")
+ @Log(title = "鎻愬崌鏈哄垎鏋�", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R<Void> edit(@Validated(EditGroup.class) @RequestBody HoisterTimeDataBo bo) {
+ return toAjax(hoisterTimeDataService.updateByBo(bo));
+ }
+
+ /**
+ * 鍒犻櫎鎻愬崌鏈哄垎鏋�
+ *
+ * @param times 涓婚敭涓�
+ */
+ @SaCheckPermission("analy:hoisterData:remove")
+ @Log(title = "鎻愬崌鏈哄垎鏋�", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{times}")
+ public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Date[] times) {
+ return toAjax(hoisterTimeDataService.deleteWithValidByIds(List.of(times), true));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/controller/PackerTimeDataController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/controller/PackerTimeDataController.java
new file mode 100755
index 0000000..2cca532
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/controller/PackerTimeDataController.java
@@ -0,0 +1,106 @@
+package org.dromara.qa.controller;
+
+import java.util.Date;
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.qa.domain.vo.PackerTimeDataVo;
+import org.dromara.qa.domain.bo.PackerTimeDataBo;
+import org.dromara.qa.service.IPackerTimeDataService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 鍖呰鏈哄垎鏋�
+ *
+ * @author zhuguifei
+ * @date 2026-01-29
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/analy/packer")
+public class PackerTimeDataController extends BaseController {
+
+ private final IPackerTimeDataService packerTimeDataService;
+
+ /**
+ * 鏌ヨ鍖呰鏈哄垎鏋愬垪琛�
+ */
+ @SaCheckPermission("analy:packerData:list")
+ @GetMapping("/list")
+ public TableDataInfo<PackerTimeDataVo> list(PackerTimeDataBo bo, PageQuery pageQuery) {
+ return packerTimeDataService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 瀵煎嚭鍖呰鏈哄垎鏋愬垪琛�
+ */
+ @SaCheckPermission("analy:packerData:export")
+ @Log(title = "鍖呰鏈哄垎鏋�", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(PackerTimeDataBo bo, HttpServletResponse response) {
+ List<PackerTimeDataVo> list = packerTimeDataService.queryList(bo);
+ ExcelUtil.exportExcel(list, "鍖呰鏈哄垎鏋�", PackerTimeDataVo.class, response);
+ }
+
+ /**
+ * 鑾峰彇鍖呰鏈哄垎鏋愯缁嗕俊鎭�
+ *
+ * @param time 涓婚敭
+ */
+ @SaCheckPermission("analy:packerData:query")
+ @GetMapping("/{time}")
+ public R<PackerTimeDataVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Date time) {
+ return R.ok(packerTimeDataService.queryById(time));
+ }
+
+ /**
+ * 鏂板鍖呰鏈哄垎鏋�
+ */
+ @SaCheckPermission("analy:packerData:add")
+ @Log(title = "鍖呰鏈哄垎鏋�", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R<Void> add(@Validated(AddGroup.class) @RequestBody PackerTimeDataBo bo) {
+ return toAjax(packerTimeDataService.insertByBo(bo));
+ }
+
+ /**
+ * 淇敼鍖呰鏈哄垎鏋�
+ */
+ @SaCheckPermission("analy:packerData:edit")
+ @Log(title = "鍖呰鏈哄垎鏋�", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R<Void> edit(@Validated(EditGroup.class) @RequestBody PackerTimeDataBo bo) {
+ return toAjax(packerTimeDataService.updateByBo(bo));
+ }
+
+ /**
+ * 鍒犻櫎鍖呰鏈哄垎鏋�
+ *
+ * @param times 涓婚敭涓�
+ */
+ @SaCheckPermission("analy:packerData:remove")
+ @Log(title = "鍖呰鏈哄垎鏋�", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{times}")
+ public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Date[] times) {
+ return toAjax(packerTimeDataService.deleteWithValidByIds(List.of(times), true));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/controller/RollerTimeDataController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/controller/RollerTimeDataController.java
new file mode 100755
index 0000000..f306d0e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/controller/RollerTimeDataController.java
@@ -0,0 +1,120 @@
+package org.dromara.qa.controller;
+
+import java.util.Date;
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.qa.domain.vo.RollerTimeDataVo;
+import org.dromara.qa.domain.bo.RollerTimeDataBo;
+import org.dromara.qa.service.IRollerTimeDataService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 鍗锋帴鏈哄垎鏋�
+ *
+ * @author zhuguifei
+ * @date 2026-01-28
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/analy/roller")
+public class RollerTimeDataController extends BaseController {
+
+ private final IRollerTimeDataService rollerTimeDataService;
+
+ /**
+ * 鏌ヨ鍗锋帴鏈哄垎鏋愬垪琛�
+ */
+ @SaCheckPermission("analy:rollerData:list")
+ @GetMapping("/list")
+ public TableDataInfo<RollerTimeDataVo> list(RollerTimeDataBo bo, PageQuery pageQuery) {
+ return rollerTimeDataService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 鏌ヨ閲囨牱鍚庣殑鍗锋帴鏈哄垎鏋愬垪琛� (姣忓皬鏃舵瘡璁惧60鏉�)
+ */
+ @SaCheckPermission("analy:rollerData:list")
+ @GetMapping("/sampleList")
+ public R<List<RollerTimeDataVo>> sampleList(RollerTimeDataBo bo) {
+ if (bo.getEquNo() == null) {
+ return R.fail("璁惧鍙蜂笉鑳戒负绌�");
+ }
+ List<RollerTimeDataVo> rollerTimeDataVos = rollerTimeDataService.querySampledList(bo);
+ return R.ok(rollerTimeDataVos);
+ }
+
+
+ /**
+ * 瀵煎嚭鍗锋帴鏈哄垎鏋愬垪琛�
+ */
+ @SaCheckPermission("analy:rollerData:export")
+ @Log(title = "鍗锋帴鏈哄垎鏋�", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(RollerTimeDataBo bo, HttpServletResponse response) {
+ List<RollerTimeDataVo> list = rollerTimeDataService.queryList(bo);
+ ExcelUtil.exportExcel(list, "鍗锋帴鏈哄垎鏋�", RollerTimeDataVo.class, response);
+ }
+
+ /**
+ * 鑾峰彇鍗锋帴鏈哄垎鏋愯缁嗕俊鎭�
+ *
+ * @param time 涓婚敭
+ */
+ @SaCheckPermission("analy:rollerData:query")
+ @GetMapping("/{time}")
+ public R<RollerTimeDataVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Date time) {
+ return R.ok(rollerTimeDataService.queryById(time));
+ }
+
+ /**
+ * 鏂板鍗锋帴鏈哄垎鏋�
+ */
+ @SaCheckPermission("analy:rollerData:add")
+ @Log(title = "鍗锋帴鏈哄垎鏋�", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R<Void> add(@Validated(AddGroup.class) @RequestBody RollerTimeDataBo bo) {
+ return toAjax(rollerTimeDataService.insertByBo(bo));
+ }
+
+ /**
+ * 淇敼鍗锋帴鏈哄垎鏋�
+ */
+ @SaCheckPermission("analy:rollerData:edit")
+ @Log(title = "鍗锋帴鏈哄垎鏋�", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R<Void> edit(@Validated(EditGroup.class) @RequestBody RollerTimeDataBo bo) {
+ return toAjax(rollerTimeDataService.updateByBo(bo));
+ }
+
+ /**
+ * 鍒犻櫎鍗锋帴鏈哄垎鏋�
+ *
+ * @param times 涓婚敭涓�
+ */
+ @SaCheckPermission("analy:rollerData:remove")
+ @Log(title = "鍗锋帴鏈哄垎鏋�", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{times}")
+ public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Date[] times) {
+ return toAjax(rollerTimeDataService.deleteWithValidByIds(List.of(times), true));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/BoxTimeData.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/BoxTimeData.java
new file mode 100755
index 0000000..3513417
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/BoxTimeData.java
@@ -0,0 +1,59 @@
+package org.dromara.qa.domain;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.util.Date;
+import java.io.Serial;
+
+/**
+ * 瑁呯鏈哄垎鏋愬璞� box_time_data
+ *
+ * @author zhuguifei
+ * @date 2026-01-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("box_time_data")
+public class BoxTimeData extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃堕棿
+ */
+ private Date time;
+
+ /**
+ * Key
+ */
+ private String key;
+
+ /**
+ * 鐝 (1浣嶆暟瀛�)
+ */
+ private Integer shift;
+
+ /**
+ * 璁惧鍙� (3浣嶆暟瀛�)
+ */
+ private Integer equNo;
+
+ /**
+ * 缃戠粶鐘舵�� 0寮傚父锛�1姝e父
+ */
+ private Integer online;
+
+ /**
+ * 1#瑁呭皝绠辨満浜ч噺
+ */
+ private Double qty1;
+
+ /**
+ * 鎺掑寘鏈轰骇閲�
+ */
+ private Double pbjQty;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/FeedmatchTimeData.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/FeedmatchTimeData.java
new file mode 100755
index 0000000..1fe70b0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/FeedmatchTimeData.java
@@ -0,0 +1,154 @@
+package org.dromara.qa.domain;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.util.Date;
+import java.io.Serial;
+
+/**
+ * 鍠備笣鏈哄垎鏋愬璞� feedmatch_time_data
+ *
+ * @author zhuguifei
+ * @date 2026-01-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("feedmatch_time_data")
+public class FeedmatchTimeData extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃堕棿
+ */
+ private Date time;
+
+ /**
+ * Key
+ */
+ private String key;
+
+ /**
+ * 鐝 (1浣嶆暟瀛�)
+ */
+ private Integer shift;
+
+ /**
+ * 璁惧鍙� (3浣嶆暟瀛�)
+ */
+ private Integer equNo;
+
+ /**
+ * 鏁版嵁鏇存柊鏃堕棿
+ */
+ private String dacUpTime;
+
+ /**
+ * 1#鍠備笣鏈哄搴旂殑绗竴涓偍涓濇煖
+ */
+ private Integer fs11;
+
+ /**
+ * 1#鍠備笣鏈哄搴旂殑绗簩涓偍涓濇煖
+ */
+ private Integer fs12;
+
+ /**
+ * 2#鍠備笣鏈哄搴旂殑绗竴涓偍涓濇煖
+ */
+ private Integer fs21;
+
+ /**
+ * 2#鍠備笣鏈哄搴旂殑绗簩涓偍涓濇煖
+ */
+ private Integer fs22;
+
+ /**
+ * 3#鍠備笣鏈哄搴旂殑绗竴涓偍涓濇煖
+ */
+ private Integer fs31;
+
+ /**
+ * 3#鍠備笣鏈哄搴旂殑绗簩涓偍涓濇煖
+ */
+ private Integer fs32;
+
+ /**
+ * 4#鍠備笣鏈哄搴旂殑绗竴涓偍涓濇煖
+ */
+ private Integer fs41;
+
+ /**
+ * 4#鍠備笣鏈哄搴旂殑绗簩涓偍涓濇煖
+ */
+ private Integer fs42;
+
+ /**
+ * 1#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ */
+ private Integer pipe01;
+
+ /**
+ * 2#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ */
+ private Integer pipe02;
+
+ /**
+ * 3#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ */
+ private Integer pipe03;
+
+ /**
+ * 4#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ */
+ private Integer pipe04;
+
+ /**
+ * 5#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ */
+ private Integer pipe05;
+
+ /**
+ * 6#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ */
+ private Integer pipe06;
+
+ /**
+ * 7#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ */
+ private Integer pipe07;
+
+ /**
+ * 8#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ */
+ private Integer pipe08;
+
+ /**
+ * 9#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ */
+ private Integer pipe09;
+
+ /**
+ * 10#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ */
+ private Integer pipe10;
+
+ /**
+ * 11#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ */
+ private Integer pipe11;
+
+ /**
+ * 12#鏈虹粍瀵瑰簲鐨勫杺涓濇満鍜岀閬�
+ */
+ private Integer pipe12;
+
+ /**
+ * 鍠備笣鏈虹姸鎬� 1-杩炴帴 0-鏂紑
+ */
+ private Integer wsjState;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/HoisterTimeData.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/HoisterTimeData.java
new file mode 100755
index 0000000..f1d3149
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/HoisterTimeData.java
@@ -0,0 +1,222 @@
+package org.dromara.qa.domain;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.io.Serial;
+
+/**
+ * 鎻愬崌鏈哄垎鏋愬璞� hoister_time_data
+ *
+ * @author Lion Li
+ * @date 2026-02-02
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("hoister_time_data")
+public class HoisterTimeData extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃堕棿
+ */
+ private Date time;
+
+ /**
+ * key
+ */
+ private String key;
+
+ /**
+ * 缃戠粶鐘舵�� 0寮傚父锛�1姝e父
+ */
+ private Long online;
+
+ /**
+ * 浜ч噺
+ */
+ private Double qty;
+
+ /**
+ * 1#鎻愬崌鏈虹姸鎬�
+ */
+ private Double tState1;
+
+ /**
+ * 2#鎻愬崌鏈虹姸鎬�
+ */
+ private Double tState2;
+
+ /**
+ * 3#鎻愬崌鏈虹姸鎬�
+ */
+ private Double tState3;
+
+ /**
+ * 4#鎻愬崌鏈虹姸鎬�
+ */
+ private Double tState4;
+
+ /**
+ * 5#鎻愬崌鏈虹姸鎬�
+ */
+ private Double tState5;
+
+ /**
+ * 6#鎻愬崌鏈虹姸鎬�
+ */
+ private Double tState6;
+
+ /**
+ * 7#鎻愬崌鏈虹姸鎬�
+ */
+ private Double tState7;
+
+ /**
+ * 8#鎻愬崌鏈虹姸鎬�
+ */
+ private Double tState8;
+
+ /**
+ * 9#鎻愬崌鏈虹姸鎬�
+ */
+ private Double tState9;
+
+ /**
+ * 10#鎻愬崌鏈虹姸鎬�
+ */
+ private Double tState10;
+
+ /**
+ * 11#鎻愬崌鏈虹姸鎬�
+ */
+ private Double tState11;
+
+ /**
+ * 12#鎻愬崌鏈虹姸鎬�
+ */
+ private Double tState12;
+
+ /**
+ * 1#鎺掑寘鏈虹姸鎬�
+ */
+ private Double pState1;
+
+ /**
+ * 2#鎺掑寘鏈虹姸鎬�
+ */
+ private Double pState2;
+
+ /**
+ * 3#鎺掑寘鏈虹姸鎬�
+ */
+ private Double pState3;
+
+ /**
+ * 4#鎺掑寘鏈虹姸鎬�
+ */
+ private Double pState4;
+
+ /**
+ * 1#鎻愬崌鏈轰骇閲�
+ */
+ private Double tQty1;
+
+ /**
+ * 2#鎻愬崌鏈轰骇閲�
+ */
+ private Double tQty2;
+
+ /**
+ * 3#鎻愬崌鏈轰骇閲�
+ */
+ private Double tQty3;
+
+ /**
+ * 4#鎻愬崌鏈轰骇閲�
+ */
+ private Double tQty4;
+
+ /**
+ * 5#鎻愬崌鏈轰骇閲�
+ */
+ private Double tQty5;
+
+ /**
+ * 6#鎻愬崌鏈轰骇閲�
+ */
+ private Double tQty6;
+
+ /**
+ * 7#鎻愬崌鏈轰骇閲�
+ */
+ private Double tQty7;
+
+ /**
+ * 8#鎻愬崌鏈轰骇閲�
+ */
+ private Double tQty8;
+
+ /**
+ * 9#鎻愬崌鏈轰骇閲�
+ */
+ private Double tQty9;
+
+ /**
+ * 10#鎻愬崌鏈轰骇閲�
+ */
+ private Double tQty10;
+
+ /**
+ * 11#鎻愬崌鏈轰骇閲�
+ */
+ private Double tQty11;
+
+ /**
+ * 12#鎻愬崌鏈轰骇閲�
+ */
+ private Double tQty12;
+
+ /**
+ * 1#鎺掑寘鏈轰骇閲�
+ */
+ private Double pQty1;
+
+ /**
+ * 2#鎺掑寘鏈轰骇閲�
+ */
+ private Double pQty2;
+
+ /**
+ * 3#鎺掑寘鏈轰骇閲�
+ */
+ private Double pQty3;
+
+ /**
+ * 4#鎺掑寘鏈轰骇閲�
+ */
+ private Double pQty4;
+
+ /**
+ * 鐝
+ */
+ private Integer shift;
+
+ /**
+ * 璁惧
+ */
+ private Integer equNo;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/MakeupTimeData.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/MakeupTimeData.java
new file mode 100755
index 0000000..93ba613
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/MakeupTimeData.java
@@ -0,0 +1,84 @@
+package org.dromara.qa.domain;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.util.Date;
+import java.io.Serial;
+
+/**
+ * 鎴愬瀷鏈哄垎鏋愬璞� makeup_time_data
+ *
+ * @author zhuguifei
+ * @date 2026-01-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("makeup_time_data")
+public class MakeupTimeData extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃堕棿
+ */
+ private Date time;
+
+ /**
+ * Key
+ */
+ private String key;
+
+ /**
+ * 鐝 (1浣嶆暟瀛�)
+ */
+ private Integer shift;
+
+ /**
+ * 璁惧鍙� (3浣嶆暟瀛�)
+ */
+ private Integer equNo;
+
+ /**
+ * 缃戠粶鐘舵�� 0寮傚父锛�1姝e父
+ */
+ private Integer online;
+
+ /**
+ * 浜ч噺
+ */
+ private Double qty;
+
+ /**
+ * 鍓旈櫎浜ч噺
+ */
+ private Double badQty;
+
+ /**
+ * 鐩樼焊娑堣��
+ */
+ private Double panzhiVal;
+
+ /**
+ * 杩愯鏃堕棿
+ */
+ private Double runTime;
+
+ /**
+ * 鍋滄満鏃堕棿
+ */
+ private Double stopTime;
+
+ /**
+ * 鍋滄満娆℃暟
+ */
+ private Integer stopTimes;
+
+ /**
+ * 杞﹂��
+ */
+ private Integer speed;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/PackerTimeData.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/PackerTimeData.java
new file mode 100755
index 0000000..4639204
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/PackerTimeData.java
@@ -0,0 +1,147 @@
+package org.dromara.qa.domain;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.io.Serial;
+
+/**
+ * 鍖呰鏈哄垎鏋愬璞� packer_time_data
+ *
+ * @author zhuguifei
+ * @date 2026-01-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("packer_time_data")
+public class PackerTimeData extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃堕棿
+ */
+ private Date time;
+
+ /**
+ * key
+ */
+ private String key;
+
+ /**
+ * 缃戠粶鐘舵��(0寮傚父锛�1姝e父)
+ */
+ private Integer online;
+
+ /**
+ * 浜ч噺
+ */
+ private Double qty;
+
+ /**
+ * 鍓旈櫎浜ч噺
+ */
+ private Double badQty;
+
+ /**
+ * 灏忕洅鑶滄秷鑰�
+ */
+ private Double xiaohemoVal;
+
+ /**
+ * 鏉$洅鑶滄秷鑰�
+ */
+ private Double tiaohemoVal;
+
+ /**
+ * 灏忕洅绾告秷鑰�
+ */
+ private Double xiaohezhiVal;
+
+ /**
+ * 鏉$洅绾告秷鑰�
+ */
+ private Double tiaohezhiVal;
+
+ /**
+ * 鍐呰‖绾告秷鑰�
+ */
+ private Double neichenzhiVal;
+
+ /**
+ * 杩愯鏃堕棿
+ */
+ private Double runTime;
+
+ /**
+ * 鍋滄満鏃堕棿
+ */
+ private Double stopTime;
+
+ /**
+ * 鍋滄満娆℃暟
+ */
+ private Integer stopTimes;
+
+ /**
+ * 杞﹂��
+ */
+ private Integer speed;
+
+ /**
+ * 杩愯鐘舵��(-1 鏂綉 0鍋滄 1浣庨�熻繍琛� 2姝e父杩愯)
+ */
+ private Integer runStatus;
+
+ /**
+ * 鎻愬崌鏈轰骇閲�
+ */
+ private Double tsQty;
+
+ /**
+ * 涓绘満浜ч噺锛堝皬鍖呮満锛�
+ */
+ private Double mainQty;
+
+ /**
+ * 涓绘満鍓旈櫎閲�
+ */
+ private Double mainBadQty;
+
+ /**
+ * 閫忓寘鏈轰骇閲�
+ */
+ private Double tbjQty;
+
+ /**
+ * 閫忓寘鏈哄墧闄ゅソ鍖�
+ */
+ private Double tbjGdQty;
+
+ /**
+ * 閫忓寘鏈哄墧闄ゅ潖鍖�
+ */
+ private Double tbjBadQty;
+
+ /**
+ * 鎺掑寘鏈轰骇閲�
+ */
+ private Double pbjQty;
+
+ /**
+ * 鐝
+ */
+ private Integer shift;
+
+ /**
+ * 璁惧
+ */
+ private Integer equNo;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/RollerTimeData.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/RollerTimeData.java
new file mode 100755
index 0000000..22d1740
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/RollerTimeData.java
@@ -0,0 +1,126 @@
+package org.dromara.qa.domain;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.io.Serial;
+
+/**
+ * 鍗锋帴鏈哄垎鏋愬璞� roller_time_data
+ *
+ * @author zhuguifei
+ * @date 2026-01-28
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("roller_time_data")
+public class RollerTimeData extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃堕棿
+ */
+ private Date time;
+
+ /**
+ * Key
+ */
+ private String key;
+
+ /**
+ * 鐝 (1浣嶆暟瀛�)
+ */
+ private Integer shift;
+
+ /**
+ * 璁惧鍙� (3浣嶆暟瀛�)
+ */
+ private Integer equNo;
+
+ /**
+ * 缃戠粶鐘舵��(0寮傚父 1姝e父)
+ */
+ private Integer online;
+
+ /**
+ * 浜ч噺
+ */
+ private Double qty;
+
+ /**
+ * 鍓旈櫎浜ч噺
+ */
+ private Double badQty;
+
+ /**
+ * 婊ゆ娑堣��
+ */
+ private Double lvbangVal;
+
+ /**
+ * 鍗风儫绾告秷鑰�
+ */
+ private Double juanyanzhiVal;
+
+ /**
+ * 姘存澗绾告秷鑰�
+ */
+ private Double shuisongzhiVal;
+
+ /**
+ * 杩愯鏃堕棿
+ */
+ private Double runTime;
+
+ /**
+ * 鍋滄満鏃堕棿
+ */
+ private Double stopTime;
+
+ /**
+ * 鍋滄満娆℃暟
+ */
+ private Integer stopTimes;
+
+ /**
+ * 杞﹂��
+ */
+ private Integer speed;
+
+ /**
+ * 杩愯鐘舵��(-1鏂綉 0鍋滄 1浣庨�熻繍琛� 2姝e父杩愯)
+ */
+ private Integer runStatus;
+
+ /**
+ * 鍌ㄧ儫璁惧鍌ㄩ噺
+ */
+ private Double cy;
+
+ /**
+ * 鍌ㄧ儫璁惧杞﹂��(1-鍖呰鏈� 6-鍗风儫鏈�)
+ */
+ private Integer cyCs;
+
+ /**
+ * (22-鍗风儫鏈鸿繍琛岀姸鎬� 23-鍖呰鏈鸿繍琛岀姸鎬�)
+ */
+ private String cyOnline;
+
+ /**
+ * 鎺ユ敹鏈洪噺
+ */
+ private Double recQty1;
+
+ /**
+ * 鎺ユ敹鏈洪噺2
+ */
+ private Double recQty2;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/TransTimeData.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/TransTimeData.java
new file mode 100755
index 0000000..e80ba1e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/TransTimeData.java
@@ -0,0 +1,159 @@
+package org.dromara.qa.domain;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.util.Date;
+import java.io.Serial;
+
+/**
+ * 鍙戝皠鏈哄垎鏋愬璞� trans_time_data
+ *
+ * @author zhuguifei
+ * @date 2026-01-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("trans_time_data")
+public class TransTimeData extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃堕棿
+ */
+ private Date time;
+
+ /**
+ * Key
+ */
+ private String key;
+
+ /**
+ * 鐝 (1浣嶆暟瀛�)
+ */
+ private Integer shift;
+
+ /**
+ * 璁惧鍙� (3浣嶆暟瀛�)
+ */
+ private Integer equNo;
+
+ /**
+ * 缃戠粶鐘舵�� 0寮傚父锛�1姝e父
+ */
+ private Integer online;
+
+ /**
+ * 绠¢亾1婊ゆ璁℃暟
+ */
+ private Double pQty1;
+
+ /**
+ * 绠¢亾2婊ゆ璁℃暟
+ */
+ private Double pQty2;
+
+ /**
+ * 绠¢亾3婊ゆ璁℃暟
+ */
+ private Double pQty3;
+
+ /**
+ * 绠¢亾4婊ゆ璁℃暟
+ */
+ private Double pQty4;
+
+ /**
+ * 绠¢亾5婊ゆ璁℃暟
+ */
+ private Double pQty5;
+
+ /**
+ * 绠¢亾6婊ゆ璁℃暟
+ */
+ private Double pQty6;
+
+ /**
+ * 绠¢亾7婊ゆ璁℃暟
+ */
+ private Double pQty7;
+
+ /**
+ * 绠¢亾8婊ゆ璁℃暟
+ */
+ private Double pQty8;
+
+ /**
+ * 绠¢亾9婊ゆ璁℃暟
+ */
+ private Double pQty9;
+
+ /**
+ * 绠¢亾10婊ゆ璁℃暟
+ */
+ private Double pQty10;
+
+ /**
+ * 绠¢亾1婊ゆ閫熷害
+ */
+ private Double speed1;
+
+ /**
+ * 绠¢亾2婊ゆ閫熷害
+ */
+ private Double speed2;
+
+ /**
+ * 绠¢亾3婊ゆ閫熷害
+ */
+ private Double speed3;
+
+ /**
+ * 绠¢亾4婊ゆ閫熷害
+ */
+ private Double speed4;
+
+ /**
+ * 绠¢亾5婊ゆ閫熷害
+ */
+ private Double speed5;
+
+ /**
+ * 绠¢亾6婊ゆ閫熷害
+ */
+ private Double speed6;
+
+ /**
+ * 绠¢亾7婊ゆ閫熷害
+ */
+ private Double speed7;
+
+ /**
+ * 绠¢亾8婊ゆ閫熷害
+ */
+ private Double speed8;
+
+ /**
+ * 绠¢亾9婊ゆ閫熷害
+ */
+ private Double speed9;
+
+ /**
+ * 绠¢亾10婊ゆ閫熷害
+ */
+ private Double speed10;
+
+ /**
+ * 鍗哥洏鏈虹姸鎬�
+ */
+ private Integer xpState;
+
+ /**
+ * 鍗哥洏鏈轰骇閲�
+ */
+ private Double trayQty1;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/bo/HoisterTimeDataBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/bo/HoisterTimeDataBo.java
new file mode 100755
index 0000000..bdd6757
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/bo/HoisterTimeDataBo.java
@@ -0,0 +1,256 @@
+package org.dromara.qa.domain.bo;
+
+import org.dromara.qa.domain.HoisterTimeData;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 鎻愬崌鏈哄垎鏋愪笟鍔″璞� hoister_time_data
+ *
+ * @author zhuguifei
+ * @date 2026-02-02
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = HoisterTimeData.class, reverseConvertGenerate = false)
+public class HoisterTimeDataBo extends BaseEntity {
+
+ /**
+ * 鏃堕棿
+ */
+ @NotNull(message = "鏃堕棿涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private Date time;
+
+ /**
+ * key
+ */
+ @NotBlank(message = "key涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private String key;
+
+ /**
+ * 缃戠粶鐘舵�� 0寮傚父锛�1姝e父
+ */
+ private Long online;
+
+ /**
+ * 浜ч噺
+ */
+ private Double qty;
+
+ /**
+ * 1#鎻愬崌鏈虹姸鎬�
+ */
+ @JsonProperty("tState1")
+ private Double tState1;
+
+ /**
+ * 2#鎻愬崌鏈虹姸鎬�
+ */
+ @JsonProperty("tState2")
+ private Double tState2;
+
+ /**
+ * 3#鎻愬崌鏈虹姸鎬�
+ */
+ @JsonProperty("tState3")
+ private Double tState3;
+
+ /**
+ * 4#鎻愬崌鏈虹姸鎬�
+ */
+ @JsonProperty("tState4")
+ private Double tState4;
+
+ /**
+ * 5#鎻愬崌鏈虹姸鎬�
+ */
+ @JsonProperty("tState5")
+ private Double tState5;
+
+ /**
+ * 6#鎻愬崌鏈虹姸鎬�
+ */
+ @JsonProperty("tState6")
+ private Double tState6;
+
+ /**
+ * 7#鎻愬崌鏈虹姸鎬�
+ */
+ @JsonProperty("tState7")
+ private Double tState7;
+
+ /**
+ * 8#鎻愬崌鏈虹姸鎬�
+ */
+ @JsonProperty("tState8")
+ private Double tState8;
+
+ /**
+ * 9#鎻愬崌鏈虹姸鎬�
+ */
+ @JsonProperty("tState9")
+ private Double tState9;
+
+ /**
+ * 10#鎻愬崌鏈虹姸鎬�
+ */
+ @JsonProperty("tState10")
+ private Double tState10;
+
+ /**
+ * 11#鎻愬崌鏈虹姸鎬�
+ */
+ @JsonProperty("tState11")
+ private Double tState11;
+
+ /**
+ * 12#鎻愬崌鏈虹姸鎬�
+ */
+ @JsonProperty("tState12")
+ private Double tState12;
+
+ /**
+ * 1#鎺掑寘鏈虹姸鎬�
+ */
+ @JsonProperty("pState1")
+ private Double pState1;
+
+ /**
+ * 2#鎺掑寘鏈虹姸鎬�
+ */
+ @JsonProperty("pState2")
+ private Double pState2;
+
+ /**
+ * 3#鎺掑寘鏈虹姸鎬�
+ */
+ @JsonProperty("pState3")
+ private Double pState3;
+
+ /**
+ * 4#鎺掑寘鏈虹姸鎬�
+ */
+ @JsonProperty("pState4")
+ private Double pState4;
+
+ /**
+ * 1#鎻愬崌鏈轰骇閲�
+ */
+ @JsonProperty("tQty1")
+ private Double tQty1;
+
+ /**
+ * 2#鎻愬崌鏈轰骇閲�
+ */
+ @JsonProperty("tQty2")
+ private Double tQty2;
+
+ /**
+ * 3#鎻愬崌鏈轰骇閲�
+ */
+ @JsonProperty("tQty3")
+ private Double tQty3;
+
+ /**
+ * 4#鎻愬崌鏈轰骇閲�
+ */
+ @JsonProperty("tQty4")
+ private Double tQty4;
+
+ /**
+ * 5#鎻愬崌鏈轰骇閲�
+ */
+ @JsonProperty("tQty5")
+ private Double tQty5;
+
+ /**
+ * 6#鎻愬崌鏈轰骇閲�
+ */
+ @JsonProperty("tQty6")
+ private Double tQty6;
+
+ /**
+ * 7#鎻愬崌鏈轰骇閲�
+ */
+ @JsonProperty("tQty7")
+ private Double tQty7;
+
+ /**
+ * 8#鎻愬崌鏈轰骇閲�
+ */
+ @JsonProperty("tQty8")
+ private Double tQty8;
+
+ /**
+ * 9#鎻愬崌鏈轰骇閲�
+ */
+ @JsonProperty("tQty9")
+ private Double tQty9;
+
+ /**
+ * 10#鎻愬崌鏈轰骇閲�
+ */
+ @JsonProperty("tQty10")
+ private Double tQty10;
+
+ /**
+ * 11#鎻愬崌鏈轰骇閲�
+ */
+ @JsonProperty("tQty11")
+ private Double tQty11;
+
+ /**
+ * 12#鎻愬崌鏈轰骇閲�
+ */
+ @JsonProperty("tQty12")
+ private Double tQty12;
+
+ /**
+ * 1#鎺掑寘鏈轰骇閲�
+ */
+ @JsonProperty("pQty1")
+ private Double pQty1;
+
+ /**
+ * 2#鎺掑寘鏈轰骇閲�
+ */
+ @JsonProperty("pQty2")
+ private Double pQty2;
+
+ /**
+ * 3#鎺掑寘鏈轰骇閲�
+ */
+ @JsonProperty("pQty3")
+ private Double pQty3;
+
+ /**
+ * 4#鎺掑寘鏈轰骇閲�
+ */
+ @JsonProperty("pQty4")
+ private Double pQty4;
+
+ /**
+ * 鐝
+ */
+ private Long shift;
+
+ /**
+ * 璁惧
+ */
+ private Long equNo;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/bo/PackerTimeDataBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/bo/PackerTimeDataBo.java
new file mode 100755
index 0000000..96c7ecc
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/bo/PackerTimeDataBo.java
@@ -0,0 +1,149 @@
+package org.dromara.qa.domain.bo;
+
+import org.dromara.qa.domain.PackerTimeData;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * 鍖呰鏈哄垎鏋愪笟鍔″璞� packer_time_data
+ *
+ * @author zhuguifei
+ * @date 2026-01-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = PackerTimeData.class, reverseConvertGenerate = false)
+public class PackerTimeDataBo extends BaseEntity {
+
+ /**
+ * 鏃堕棿
+ */
+ @NotNull(message = "鏃堕棿涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private Date time;
+
+ /**
+ * key
+ */
+ private String key;
+
+ /**
+ * 缃戠粶鐘舵��(0寮傚父锛�1姝e父)
+ */
+ private Integer online;
+
+ /**
+ * 浜ч噺
+ */
+ private Double qty;
+
+ /**
+ * 鍓旈櫎浜ч噺
+ */
+ private Double badQty;
+
+ /**
+ * 灏忕洅鑶滄秷鑰�
+ */
+ private Double xiaohemoVal;
+
+ /**
+ * 鏉$洅鑶滄秷鑰�
+ */
+ private Double tiaohemoVal;
+
+ /**
+ * 灏忕洅绾告秷鑰�
+ */
+ private Double xiaohezhiVal;
+
+ /**
+ * 鏉$洅绾告秷鑰�
+ */
+ private Double tiaohezhiVal;
+
+ /**
+ * 鍐呰‖绾告秷鑰�
+ */
+ private Double neichenzhiVal;
+
+ /**
+ * 杩愯鏃堕棿
+ */
+ private Double runTime;
+
+ /**
+ * 鍋滄満鏃堕棿
+ */
+ private Double stopTime;
+
+ /**
+ * 鍋滄満娆℃暟
+ */
+ private Integer stopTimes;
+
+ /**
+ * 杞﹂��
+ */
+ private Integer speed;
+
+ /**
+ * 杩愯鐘舵��(-1 鏂綉 0鍋滄 1浣庨�熻繍琛� 2姝e父杩愯)
+ */
+ private Integer runStatus;
+
+ /**
+ * 鎻愬崌鏈轰骇閲�
+ */
+ private Double tsQty;
+
+ /**
+ * 涓绘満浜ч噺锛堝皬鍖呮満锛�
+ */
+ private Double mainQty;
+
+ /**
+ * 涓绘満鍓旈櫎閲�
+ */
+ private Double mainBadQty;
+
+ /**
+ * 閫忓寘鏈轰骇閲�
+ */
+ private Double tbjQty;
+
+ /**
+ * 閫忓寘鏈哄墧闄ゅソ鍖�
+ */
+ private Double tbjGdQty;
+
+ /**
+ * 閫忓寘鏈哄墧闄ゅ潖鍖�
+ */
+ private Double tbjBadQty;
+
+ /**
+ * 鎺掑寘鏈轰骇閲�
+ */
+ private Double pbjQty;
+
+ /**
+ * 鐝
+ */
+ @NotNull(message = "鐝涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private Integer shift;
+
+ /**
+ * 璁惧
+ */
+ @NotNull(message = "璁惧涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private Integer equNo;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/bo/RollerTimeDataBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/bo/RollerTimeDataBo.java
new file mode 100755
index 0000000..5b942b7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/bo/RollerTimeDataBo.java
@@ -0,0 +1,127 @@
+package org.dromara.qa.domain.bo;
+
+import org.dromara.qa.domain.RollerTimeData;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * 鍗锋帴鏈哄垎鏋愪笟鍔″璞� roller_time_data
+ *
+ * @author zhuguifei
+ * @date 2026-01-28
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = RollerTimeData.class, reverseConvertGenerate = false)
+public class RollerTimeDataBo extends BaseEntity {
+
+ /**
+ * 鏃堕棿
+ */
+ @NotNull(message = "鏃堕棿涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private Date time;
+
+ /**
+ * Key
+ */
+ @NotBlank(message = "Key涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private String key;
+
+ /**
+ * 鐝 (1浣嶆暟瀛�)
+ */
+ private Integer shift;
+
+ /**
+ * 璁惧鍙� (3浣嶆暟瀛�)
+ */
+ private Integer equNo;
+
+ /**
+ * 缃戠粶鐘舵��(0寮傚父 1姝e父)
+ */
+ private Integer online;
+
+ /**
+ * 浜ч噺
+ */
+ private Double qty;
+
+ /**
+ * 鍓旈櫎浜ч噺
+ */
+ private Double badQty;
+
+ /**
+ * 婊ゆ娑堣��
+ */
+ private Double lvbangVal;
+
+ /**
+ * 鍗风儫绾告秷鑰�
+ */
+ private Double juanyanzhiVal;
+
+ /**
+ * 姘存澗绾告秷鑰�
+ */
+ private Double shuisongzhiVal;
+
+ /**
+ * 杩愯鏃堕棿
+ */
+ private Double runTime;
+
+ /**
+ * 鍋滄満鏃堕棿
+ */
+ private Double stopTime;
+
+ /**
+ * 鍋滄満娆℃暟
+ */
+ private Integer stopTimes;
+
+ /**
+ * 杞﹂��
+ */
+ private Integer speed;
+
+ /**
+ * 杩愯鐘舵��(-1鏂綉 0鍋滄 1浣庨�熻繍琛� 2姝e父杩愯)
+ */
+ private Integer runStatus;
+
+ /**
+ * 鍌ㄧ儫璁惧鍌ㄩ噺
+ */
+ private Double cy;
+
+ /**
+ * 鍌ㄧ儫璁惧杞﹂��(1-鍖呰鏈� 6-鍗风儫鏈�)
+ */
+ private Integer cyCs;
+
+ /**
+ * (22-鍗风儫鏈鸿繍琛岀姸鎬� 23-鍖呰鏈鸿繍琛岀姸鎬�)
+ */
+ private String cyOnline;
+
+ /**
+ * 鎺ユ敹鏈洪噺
+ */
+ private Double recQty1;
+
+ /**
+ * 鎺ユ敹鏈洪噺2
+ */
+ private Double recQty2;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/vo/HoisterTimeDataVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/vo/HoisterTimeDataVo.java
new file mode 100755
index 0000000..5a58179
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/vo/HoisterTimeDataVo.java
@@ -0,0 +1,301 @@
+package org.dromara.qa.domain.vo;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.dromara.qa.domain.HoisterTimeData;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 鎻愬崌鏈哄垎鏋愯鍥惧璞� hoister_time_data
+ *
+ * @author zhuguifei
+ * @date 2026-02-02
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = HoisterTimeData.class)
+public class HoisterTimeDataVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃堕棿
+ */
+ @ExcelProperty(value = "鏃堕棿")
+ private Date time;
+
+ /**
+ * key
+ */
+ @ExcelProperty(value = "key")
+ private String key;
+
+ /**
+ * 缃戠粶鐘舵�� 0寮傚父锛�1姝e父
+ */
+ @ExcelProperty(value = "缃戠粶鐘舵�� 0寮傚父锛�1姝e父")
+ private Long online;
+
+ /**
+ * 浜ч噺
+ */
+ @ExcelProperty(value = "浜ч噺")
+ private Double qty;
+
+ /**
+ * 1#鎻愬崌鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "1#鎻愬崌鏈虹姸鎬�")
+ @JsonProperty("tState1")
+ private Double tState1;
+
+ /**
+ * 2#鎻愬崌鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "2#鎻愬崌鏈虹姸鎬�")
+ @JsonProperty("tState2")
+ private Double tState2;
+
+ /**
+ * 3#鎻愬崌鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "3#鎻愬崌鏈虹姸鎬�")
+ @JsonProperty("tState3")
+ private Double tState3;
+
+ /**
+ * 4#鎻愬崌鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "4#鎻愬崌鏈虹姸鎬�")
+ @JsonProperty("tState4")
+ private Double tState4;
+
+ /**
+ * 5#鎻愬崌鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "5#鎻愬崌鏈虹姸鎬�")
+ @JsonProperty("tState5")
+ private Double tState5;
+
+ /**
+ * 6#鎻愬崌鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "6#鎻愬崌鏈虹姸鎬�")
+ @JsonProperty("tState6")
+ private Double tState6;
+
+ /**
+ * 7#鎻愬崌鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "7#鎻愬崌鏈虹姸鎬�")
+ @JsonProperty("tState7")
+ private Double tState7;
+
+ /**
+ * 8#鎻愬崌鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "8#鎻愬崌鏈虹姸鎬�")
+ @JsonProperty("tState8")
+ private Double tState8;
+
+ /**
+ * 9#鎻愬崌鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "9#鎻愬崌鏈虹姸鎬�")
+ @JsonProperty("tState9")
+ private Double tState9;
+
+ /**
+ * 10#鎻愬崌鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "10#鎻愬崌鏈虹姸鎬�")
+ @JsonProperty("tState10")
+ private Double tState10;
+
+ /**
+ * 11#鎻愬崌鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "11#鎻愬崌鏈虹姸鎬�")
+ @JsonProperty("tState11")
+ private Double tState11;
+
+ /**
+ * 12#鎻愬崌鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "12#鎻愬崌鏈虹姸鎬�")
+ @JsonProperty("tState12")
+ private Double tState12;
+
+ /**
+ * 1#鎺掑寘鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "1#鎺掑寘鏈虹姸鎬�")
+ @JsonProperty("pState1")
+ private Double pState1;
+
+ /**
+ * 2#鎺掑寘鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "2#鎺掑寘鏈虹姸鎬�")
+ @JsonProperty("pState2")
+ private Double pState2;
+
+ /**
+ * 3#鎺掑寘鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "3#鎺掑寘鏈虹姸鎬�")
+ @JsonProperty("pState3")
+ private Double pState3;
+
+ /**
+ * 4#鎺掑寘鏈虹姸鎬�
+ */
+ @ExcelProperty(value = "4#鎺掑寘鏈虹姸鎬�")
+ @JsonProperty("pState4")
+ private Double pState4;
+
+ /**
+ * 1#鎻愬崌鏈轰骇閲�
+ */
+ @ExcelProperty(value = "1#鎻愬崌鏈轰骇閲�")
+ @JsonProperty("tQty1")
+ private Double tQty1;
+
+ /**
+ * 2#鎻愬崌鏈轰骇閲�
+ */
+ @ExcelProperty(value = "2#鎻愬崌鏈轰骇閲�")
+ @JsonProperty("tQty2")
+ private Double tQty2;
+
+ /**
+ * 3#鎻愬崌鏈轰骇閲�
+ */
+ @ExcelProperty(value = "3#鎻愬崌鏈轰骇閲�")
+ @JsonProperty("tQty3")
+ private Double tQty3;
+
+ /**
+ * 4#鎻愬崌鏈轰骇閲�
+ */
+ @ExcelProperty(value = "4#鎻愬崌鏈轰骇閲�")
+ @JsonProperty("tQty4")
+ private Double tQty4;
+
+ /**
+ * 5#鎻愬崌鏈轰骇閲�
+ */
+ @ExcelProperty(value = "5#鎻愬崌鏈轰骇閲�")
+ @JsonProperty("tQty5")
+ private Double tQty5;
+
+ /**
+ * 6#鎻愬崌鏈轰骇閲�
+ */
+ @ExcelProperty(value = "6#鎻愬崌鏈轰骇閲�")
+ @JsonProperty("tQty6")
+ private Double tQty6;
+
+ /**
+ * 7#鎻愬崌鏈轰骇閲�
+ */
+ @ExcelProperty(value = "7#鎻愬崌鏈轰骇閲�")
+ @JsonProperty("tQty7")
+ private Double tQty7;
+
+ /**
+ * 8#鎻愬崌鏈轰骇閲�
+ */
+ @ExcelProperty(value = "8#鎻愬崌鏈轰骇閲�")
+ @JsonProperty("tQty8")
+ private Double tQty8;
+
+ /**
+ * 9#鎻愬崌鏈轰骇閲�
+ */
+ @ExcelProperty(value = "9#鎻愬崌鏈轰骇閲�")
+ @JsonProperty("tQty9")
+ private Double tQty9;
+
+ /**
+ * 10#鎻愬崌鏈轰骇閲�
+ */
+ @ExcelProperty(value = "10#鎻愬崌鏈轰骇閲�")
+ @JsonProperty("tQty10")
+ private Double tQty10;
+
+ /**
+ * 11#鎻愬崌鏈轰骇閲�
+ */
+ @ExcelProperty(value = "11#鎻愬崌鏈轰骇閲�")
+ @JsonProperty("tQty11")
+ private Double tQty11;
+
+ /**
+ * 12#鎻愬崌鏈轰骇閲�
+ */
+ @ExcelProperty(value = "12#鎻愬崌鏈轰骇閲�")
+ @JsonProperty("tQty12")
+ private Double tQty12;
+
+ /**
+ * 1#鎺掑寘鏈轰骇閲�
+ */
+ @ExcelProperty(value = "1#鎺掑寘鏈轰骇閲�")
+ @JsonProperty("pQty1")
+ private Double pQty1;
+
+ /**
+ * 2#鎺掑寘鏈轰骇閲�
+ */
+ @ExcelProperty(value = "2#鎺掑寘鏈轰骇閲�")
+ @JsonProperty("pQty2")
+ private Double pQty2;
+
+ /**
+ * 3#鎺掑寘鏈轰骇閲�
+ */
+ @ExcelProperty(value = "3#鎺掑寘鏈轰骇閲�")
+ @JsonProperty("pQty3")
+ private Double pQty3;
+
+ /**
+ * 4#鎺掑寘鏈轰骇閲�
+ */
+ @ExcelProperty(value = "4#鎺掑寘鏈轰骇閲�")
+ @JsonProperty("pQty4")
+ private Double pQty4;
+
+ /**
+ * 鐝
+ */
+ @ExcelProperty(value = "鐝")
+ private Long shift;
+
+ /**
+ * 璁惧
+ */
+ @ExcelProperty(value = "璁惧")
+ private Long equNo;
+
+ /**
+ * 澶囨敞
+ */
+ @ExcelProperty(value = "澶囨敞")
+ private String remark;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/vo/PackerTimeDataVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/vo/PackerTimeDataVo.java
new file mode 100755
index 0000000..636873b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/vo/PackerTimeDataVo.java
@@ -0,0 +1,179 @@
+package org.dromara.qa.domain.vo;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.dromara.qa.domain.PackerTimeData;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 鍖呰鏈哄垎鏋愯鍥惧璞� packer_time_data
+ *
+ * @author zhuguifei
+ * @date 2026-01-29
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = PackerTimeData.class)
+public class PackerTimeDataVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃堕棿
+ */
+ @ExcelProperty(value = "鏃堕棿")
+ private Date time;
+
+ /**
+ * key
+ */
+ @ExcelProperty(value = "key")
+ private String key;
+
+ /**
+ * 缃戠粶鐘舵��(0寮傚父锛�1姝e父)
+ */
+ @ExcelProperty(value = "缃戠粶鐘舵��(0寮傚父锛�1姝e父)")
+ private Integer online;
+
+ /**
+ * 浜ч噺
+ */
+ @ExcelProperty(value = "浜ч噺")
+ private Double qty;
+
+ /**
+ * 鍓旈櫎浜ч噺
+ */
+ @ExcelProperty(value = "鍓旈櫎浜ч噺")
+ private Double badQty;
+
+ /**
+ * 灏忕洅鑶滄秷鑰�
+ */
+ @ExcelProperty(value = "灏忕洅鑶滄秷鑰�")
+ private Double xiaohemoVal;
+
+ /**
+ * 鏉$洅鑶滄秷鑰�
+ */
+ @ExcelProperty(value = "鏉$洅鑶滄秷鑰�")
+ private Double tiaohemoVal;
+
+ /**
+ * 灏忕洅绾告秷鑰�
+ */
+ @ExcelProperty(value = "灏忕洅绾告秷鑰�")
+ private Double xiaohezhiVal;
+
+ /**
+ * 鏉$洅绾告秷鑰�
+ */
+ @ExcelProperty(value = "鏉$洅绾告秷鑰�")
+ private Double tiaohezhiVal;
+
+ /**
+ * 鍐呰‖绾告秷鑰�
+ */
+ @ExcelProperty(value = "鍐呰‖绾告秷鑰�")
+ private Double neichenzhiVal;
+
+ /**
+ * 杩愯鏃堕棿
+ */
+ @ExcelProperty(value = "杩愯鏃堕棿")
+ private Double runTime;
+
+ /**
+ * 鍋滄満鏃堕棿
+ */
+ @ExcelProperty(value = "鍋滄満鏃堕棿")
+ private Double stopTime;
+
+ /**
+ * 鍋滄満娆℃暟
+ */
+ @ExcelProperty(value = "鍋滄満娆℃暟")
+ private Integer stopTimes;
+
+ /**
+ * 杞﹂��
+ */
+ @ExcelProperty(value = "杞﹂��")
+ private Integer speed;
+
+ /**
+ * 杩愯鐘舵��(-1 鏂綉 0鍋滄 1浣庨�熻繍琛� 2姝e父杩愯)
+ */
+ @ExcelProperty(value = "杩愯鐘舵��(-1 鏂綉 0鍋滄 1浣庨�熻繍琛� 2姝e父杩愯)")
+ private Integer runStatus;
+
+ /**
+ * 鎻愬崌鏈轰骇閲�
+ */
+ @ExcelProperty(value = "鎻愬崌鏈轰骇閲�")
+ private Double tsQty;
+
+ /**
+ * 涓绘満浜ч噺锛堝皬鍖呮満锛�
+ */
+ @ExcelProperty(value = "涓绘満浜ч噺", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(readConverterExp = "灏�=鍖呮満")
+ private Double mainQty;
+
+ /**
+ * 涓绘満鍓旈櫎閲�
+ */
+ @ExcelProperty(value = "涓绘満鍓旈櫎閲�")
+ private Double mainBadQty;
+
+ /**
+ * 閫忓寘鏈轰骇閲�
+ */
+ @ExcelProperty(value = "閫忓寘鏈轰骇閲�")
+ private Double tbjQty;
+
+ /**
+ * 閫忓寘鏈哄墧闄ゅソ鍖�
+ */
+ @ExcelProperty(value = "閫忓寘鏈哄墧闄ゅソ鍖�")
+ private Double tbjGdQty;
+
+ /**
+ * 閫忓寘鏈哄墧闄ゅ潖鍖�
+ */
+ @ExcelProperty(value = "閫忓寘鏈哄墧闄ゅ潖鍖�")
+ private Double tbjBadQty;
+
+ /**
+ * 鎺掑寘鏈轰骇閲�
+ */
+ @ExcelProperty(value = "鎺掑寘鏈轰骇閲�")
+ private Double pbjQty;
+
+ /**
+ * 鐝
+ */
+ @ExcelProperty(value = "鐝")
+ private Integer shift;
+
+ /**
+ * 璁惧
+ */
+ @ExcelProperty(value = "璁惧")
+ private Integer equNo;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/vo/RollerTimeDataVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/vo/RollerTimeDataVo.java
new file mode 100755
index 0000000..fdd4828
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/domain/vo/RollerTimeDataVo.java
@@ -0,0 +1,164 @@
+package org.dromara.qa.domain.vo;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.dromara.qa.domain.RollerTimeData;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 鍗锋帴鏈哄垎鏋愯鍥惧璞� roller_time_data
+ *
+ * @author zhuguifei
+ * @date 2026-01-28
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = RollerTimeData.class)
+public class RollerTimeDataVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃堕棿
+ */
+ @ExcelProperty(value = "鏃堕棿")
+ private Date time;
+
+ /**
+ * Key
+ */
+ @ExcelProperty(value = "Key")
+ private String key;
+
+ /**
+ * 鐝 (1浣嶆暟瀛�)
+ */
+ @ExcelProperty(value = "鐝")
+ private Integer shift;
+
+ /**
+ * 璁惧鍙� (3浣嶆暟瀛�)
+ */
+ @ExcelProperty(value = "璁惧鍙�")
+ private Integer equNo;
+
+ /**
+ * 缃戠粶鐘舵��(0寮傚父 1姝e父)
+ */
+ @ExcelProperty(value = "缃戠粶鐘舵��")
+ private Integer online;
+
+ /**
+ * 浜ч噺
+ */
+ @ExcelProperty(value = "浜ч噺")
+ private Double qty;
+
+ /**
+ * 鍓旈櫎浜ч噺
+ */
+ @ExcelProperty(value = "鍓旈櫎浜ч噺")
+ private Double badQty;
+
+ /**
+ * 婊ゆ娑堣��
+ */
+ @ExcelProperty(value = "婊ゆ娑堣��")
+ private Double lvbangVal;
+
+ /**
+ * 鍗风儫绾告秷鑰�
+ */
+ @ExcelProperty(value = "鍗风儫绾告秷鑰�")
+ private Double juanyanzhiVal;
+
+ /**
+ * 姘存澗绾告秷鑰�
+ */
+ @ExcelProperty(value = "姘存澗绾告秷鑰�")
+ private Double shuisongzhiVal;
+
+ /**
+ * 杩愯鏃堕棿
+ */
+ @ExcelProperty(value = "杩愯鏃堕棿")
+ private Double runTime;
+
+ /**
+ * 鍋滄満鏃堕棿
+ */
+ @ExcelProperty(value = "鍋滄満鏃堕棿")
+ private Double stopTime;
+
+ /**
+ * 鍋滄満娆℃暟
+ */
+ @ExcelProperty(value = "鍋滄満娆℃暟")
+ private Integer stopTimes;
+
+ /**
+ * 杞﹂��
+ */
+ @ExcelProperty(value = "杞﹂��")
+ private Integer speed;
+
+ /**
+ * 杩愯鐘舵��(-1鏂綉 0鍋滄 1浣庨�熻繍琛� 2姝e父杩愯)
+ */
+ @ExcelProperty(value = "杩愯鐘舵��")
+ private Integer runStatus;
+
+ /**
+ * 鍌ㄧ儫璁惧鍌ㄩ噺
+ */
+ @ExcelProperty(value = "鍌ㄧ儫璁惧鍌ㄩ噺")
+ private Double cy;
+
+ /**
+ * 鍌ㄧ儫璁惧杞﹂��(1-鍖呰鏈� 6-鍗风儫鏈�)
+ */
+ @ExcelProperty(value = "鍌ㄧ儫璁惧杞﹂��")
+ private Integer cyCs;
+
+ /**
+ * (22-鍗风儫鏈鸿繍琛岀姸鎬� 23-鍖呰鏈鸿繍琛岀姸鎬�)
+ */
+ @ExcelProperty(value = "(22-鍗风儫鏈鸿繍琛岀姸鎬� 23-鍖呰鏈鸿繍琛岀姸鎬�)")
+ private String cyOnline;
+
+ /**
+ * 鎺ユ敹鏈洪噺
+ */
+ @ExcelProperty(value = "鎺ユ敹鏈洪噺")
+ private Double recQty1;
+
+ /**
+ * 鎺ユ敹鏈洪噺2
+ */
+ @ExcelProperty(value = "鎺ユ敹鏈洪噺2")
+ private Double recQty2;
+
+ /**
+ * 鎻愬崌鏈轰骇閲忥紙鏉ヨ嚜鍖呰鏈� tsQty锛�
+ */
+ @ExcelProperty(value = "鎻愬崌鏈轰骇閲�")
+ private Double tsQty;
+
+ /**
+ * 鍖呰鏈轰骇閲忥紙鏉ヨ嚜鍖呰鏈� qty锛�
+ */
+ @ExcelProperty(value = "鍖呰鏈轰骇閲�")
+ private Double packerQty;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/mapper/HoisterTimeDataMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/mapper/HoisterTimeDataMapper.java
new file mode 100755
index 0000000..290843b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/mapper/HoisterTimeDataMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.qa.mapper;
+
+import org.dromara.qa.domain.HoisterTimeData;
+import org.dromara.qa.domain.vo.HoisterTimeDataVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 鎻愬崌鏈哄垎鏋怣apper鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-02-02
+ */
+public interface HoisterTimeDataMapper extends BaseMapperPlus<HoisterTimeData, HoisterTimeDataVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/mapper/PackerTimeDataMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/mapper/PackerTimeDataMapper.java
new file mode 100755
index 0000000..2ef6d54
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/mapper/PackerTimeDataMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.qa.mapper;
+
+import org.dromara.qa.domain.PackerTimeData;
+import org.dromara.qa.domain.vo.PackerTimeDataVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 鍖呰鏈哄垎鏋怣apper鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-01-29
+ */
+public interface PackerTimeDataMapper extends BaseMapperPlus<PackerTimeData, PackerTimeDataVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/mapper/RollerTimeDataMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/mapper/RollerTimeDataMapper.java
new file mode 100755
index 0000000..e51f709
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/mapper/RollerTimeDataMapper.java
@@ -0,0 +1,28 @@
+package org.dromara.qa.mapper;
+
+import org.dromara.qa.domain.RollerTimeData;
+import org.dromara.qa.domain.bo.RollerTimeDataBo;
+import org.dromara.qa.domain.vo.RollerTimeDataVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+import org.apache.ibatis.annotations.Param;
+
+
+import java.util.List;
+
+/**
+ * 鍗锋帴鏈哄垎鏋怣apper鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-01-28
+ */
+public interface RollerTimeDataMapper extends BaseMapperPlus<RollerTimeData, RollerTimeDataVo> {
+
+ /**
+ * 鏌ヨ閲囨牱鍚庣殑鍗锋帴鏈哄垎鏋愬垪琛�
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return 閲囨牱鍚庣殑鍒楄〃
+ */
+ List<RollerTimeDataVo> selectSampledList(@Param("bo") RollerTimeDataBo bo);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/IHoisterTimeDataService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/IHoisterTimeDataService.java
new file mode 100755
index 0000000..200369e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/IHoisterTimeDataService.java
@@ -0,0 +1,69 @@
+package org.dromara.qa.service;
+
+import org.dromara.qa.domain.vo.HoisterTimeDataVo;
+import org.dromara.qa.domain.bo.HoisterTimeDataBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 鎻愬崌鏈哄垎鏋怱ervice鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-02-02
+ */
+public interface IHoisterTimeDataService {
+
+ /**
+ * 鏌ヨ鎻愬崌鏈哄垎鏋�
+ *
+ * @param time 涓婚敭
+ * @return 鎻愬崌鏈哄垎鏋�
+ */
+ HoisterTimeDataVo queryById(Date time);
+
+ /**
+ * 鍒嗛〉鏌ヨ鎻愬崌鏈哄垎鏋愬垪琛�
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 鎻愬崌鏈哄垎鏋愬垎椤靛垪琛�
+ */
+ TableDataInfo<HoisterTimeDataVo> queryPageList(HoisterTimeDataBo bo, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ绗﹀悎鏉′欢鐨勬彁鍗囨満鍒嗘瀽鍒楄〃
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return 鎻愬崌鏈哄垎鏋愬垪琛�
+ */
+ List<HoisterTimeDataVo> queryList(HoisterTimeDataBo bo);
+
+ /**
+ * 鏂板鎻愬崌鏈哄垎鏋�
+ *
+ * @param bo 鎻愬崌鏈哄垎鏋�
+ * @return 鏄惁鏂板鎴愬姛
+ */
+ Boolean insertByBo(HoisterTimeDataBo bo);
+
+ /**
+ * 淇敼鎻愬崌鏈哄垎鏋�
+ *
+ * @param bo 鎻愬崌鏈哄垎鏋�
+ * @return 鏄惁淇敼鎴愬姛
+ */
+ Boolean updateByBo(HoisterTimeDataBo bo);
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄ゆ彁鍗囨満鍒嗘瀽淇℃伅
+ *
+ * @param ids 寰呭垹闄ょ殑涓婚敭闆嗗悎
+ * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+ * @return 鏄惁鍒犻櫎鎴愬姛
+ */
+ Boolean deleteWithValidByIds(Collection<Date> ids, Boolean isValid);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/IPackerTimeDataService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/IPackerTimeDataService.java
new file mode 100755
index 0000000..a20a5aa
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/IPackerTimeDataService.java
@@ -0,0 +1,69 @@
+package org.dromara.qa.service;
+
+import org.dromara.qa.domain.vo.PackerTimeDataVo;
+import org.dromara.qa.domain.bo.PackerTimeDataBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 鍖呰鏈哄垎鏋怱ervice鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-01-29
+ */
+public interface IPackerTimeDataService {
+
+ /**
+ * 鏌ヨ鍖呰鏈哄垎鏋�
+ *
+ * @param time 涓婚敭
+ * @return 鍖呰鏈哄垎鏋�
+ */
+ PackerTimeDataVo queryById(Date time);
+
+ /**
+ * 鍒嗛〉鏌ヨ鍖呰鏈哄垎鏋愬垪琛�
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 鍖呰鏈哄垎鏋愬垎椤靛垪琛�
+ */
+ TableDataInfo<PackerTimeDataVo> queryPageList(PackerTimeDataBo bo, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ绗﹀悎鏉′欢鐨勫寘瑁呮満鍒嗘瀽鍒楄〃
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return 鍖呰鏈哄垎鏋愬垪琛�
+ */
+ List<PackerTimeDataVo> queryList(PackerTimeDataBo bo);
+
+ /**
+ * 鏂板鍖呰鏈哄垎鏋�
+ *
+ * @param bo 鍖呰鏈哄垎鏋�
+ * @return 鏄惁鏂板鎴愬姛
+ */
+ Boolean insertByBo(PackerTimeDataBo bo);
+
+ /**
+ * 淇敼鍖呰鏈哄垎鏋�
+ *
+ * @param bo 鍖呰鏈哄垎鏋�
+ * @return 鏄惁淇敼鎴愬姛
+ */
+ Boolean updateByBo(PackerTimeDataBo bo);
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄ゅ寘瑁呮満鍒嗘瀽淇℃伅
+ *
+ * @param ids 寰呭垹闄ょ殑涓婚敭闆嗗悎
+ * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+ * @return 鏄惁鍒犻櫎鎴愬姛
+ */
+ Boolean deleteWithValidByIds(Collection<Date> ids, Boolean isValid);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/IRollerTimeDataService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/IRollerTimeDataService.java
new file mode 100755
index 0000000..8dabe96
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/IRollerTimeDataService.java
@@ -0,0 +1,78 @@
+package org.dromara.qa.service;
+
+import org.dromara.qa.domain.vo.RollerTimeDataVo;
+import org.dromara.qa.domain.bo.RollerTimeDataBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 鍗锋帴鏈哄垎鏋怱ervice鎺ュ彛
+ *
+ * @author zhuguifei
+ * @date 2026-01-28
+ */
+public interface IRollerTimeDataService {
+
+ /**
+ * 鏌ヨ鍗锋帴鏈哄垎鏋�
+ *
+ * @param time 涓婚敭
+ * @return 鍗锋帴鏈哄垎鏋�
+ */
+ RollerTimeDataVo queryById(Date time);
+
+ /**
+ * 鍒嗛〉鏌ヨ鍗锋帴鏈哄垎鏋愬垪琛�
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 鍗锋帴鏈哄垎鏋愬垎椤靛垪琛�
+ */
+ TableDataInfo<RollerTimeDataVo> queryPageList(RollerTimeDataBo bo, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ绗﹀悎鏉′欢鐨勫嵎鎺ユ満鍒嗘瀽鍒楄〃
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return 鍗锋帴鏈哄垎鏋愬垪琛�
+ */
+ List<RollerTimeDataVo> queryList(RollerTimeDataBo bo);
+
+ /**
+ * 鏌ヨ閲囨牱鍚庣殑鍗锋帴鏈哄垎鏋愬垪琛� (姣忓皬鏃舵瘡璁惧60鏉�)
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return 閲囨牱鍚庣殑鍒楄〃
+ */
+ List<RollerTimeDataVo> querySampledList(RollerTimeDataBo bo);
+
+
+ /**
+ * 鏂板鍗锋帴鏈哄垎鏋�
+ *
+ * @param bo 鍗锋帴鏈哄垎鏋�
+ * @return 鏄惁鏂板鎴愬姛
+ */
+ Boolean insertByBo(RollerTimeDataBo bo);
+
+ /**
+ * 淇敼鍗锋帴鏈哄垎鏋�
+ *
+ * @param bo 鍗锋帴鏈哄垎鏋�
+ * @return 鏄惁淇敼鎴愬姛
+ */
+ Boolean updateByBo(RollerTimeDataBo bo);
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄ゅ嵎鎺ユ満鍒嗘瀽淇℃伅
+ *
+ * @param ids 寰呭垹闄ょ殑涓婚敭闆嗗悎
+ * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+ * @return 鏄惁鍒犻櫎鎴愬姛
+ */
+ Boolean deleteWithValidByIds(Collection<Date> ids, Boolean isValid);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/impl/HoisterTimeDataServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/impl/HoisterTimeDataServiceImpl.java
new file mode 100755
index 0000000..0a5fec8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/impl/HoisterTimeDataServiceImpl.java
@@ -0,0 +1,138 @@
+package org.dromara.qa.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.qa.domain.bo.HoisterTimeDataBo;
+import org.dromara.qa.domain.vo.HoisterTimeDataVo;
+import org.dromara.qa.domain.HoisterTimeData;
+import org.dromara.qa.mapper.HoisterTimeDataMapper;
+import org.dromara.qa.service.IHoisterTimeDataService;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 鎻愬崌鏈哄垎鏋怱ervice涓氬姟灞傚鐞�
+ *
+ * @author zhuguifei
+ * @date 2026-02-02
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class HoisterTimeDataServiceImpl implements IHoisterTimeDataService {
+
+ private final HoisterTimeDataMapper baseMapper;
+
+ /**
+ * 鏌ヨ鎻愬崌鏈哄垎鏋�
+ *
+ * @param time 涓婚敭
+ * @return 鎻愬崌鏈哄垎鏋�
+ */
+ @Override
+ public HoisterTimeDataVo queryById(Date time){
+ return baseMapper.selectVoById(time);
+ }
+
+ /**
+ * 鍒嗛〉鏌ヨ鎻愬崌鏈哄垎鏋愬垪琛�
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 鎻愬崌鏈哄垎鏋愬垎椤靛垪琛�
+ */
+ @Override
+ public TableDataInfo<HoisterTimeDataVo> queryPageList(HoisterTimeDataBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper<HoisterTimeData> lqw = buildQueryWrapper(bo);
+ Page<HoisterTimeDataVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 鏌ヨ绗﹀悎鏉′欢鐨勬彁鍗囨満鍒嗘瀽鍒楄〃
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return 鎻愬崌鏈哄垎鏋愬垪琛�
+ */
+ @Override
+ public List<HoisterTimeDataVo> queryList(HoisterTimeDataBo bo) {
+ LambdaQueryWrapper<HoisterTimeData> lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper<HoisterTimeData> buildQueryWrapper(HoisterTimeDataBo bo) {
+ Map<String, Object> params = bo.getParams();
+ LambdaQueryWrapper<HoisterTimeData> lqw = Wrappers.lambdaQuery();
+ lqw.eq(bo.getTime() != null, HoisterTimeData::getTime, bo.getTime());
+ lqw.eq(StringUtils.isNotBlank(bo.getKey()), HoisterTimeData::getKey, bo.getKey());
+ lqw.eq(bo.getOnline() != null, HoisterTimeData::getOnline, bo.getOnline());
+ lqw.eq(bo.getShift() != null, HoisterTimeData::getShift, bo.getShift());
+ lqw.eq(bo.getEquNo() != null, HoisterTimeData::getEquNo, bo.getEquNo());
+ lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
+ HoisterTimeData::getTime, params.get("beginTime"), params.get("endTime"));
+ return lqw;
+ }
+
+ /**
+ * 鏂板鎻愬崌鏈哄垎鏋�
+ *
+ * @param bo 鎻愬崌鏈哄垎鏋�
+ * @return 鏄惁鏂板鎴愬姛
+ */
+ @Override
+ public Boolean insertByBo(HoisterTimeDataBo bo) {
+ HoisterTimeData add = MapstructUtils.convert(bo, HoisterTimeData.class);
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setTime(add.getTime());
+ }
+ return flag;
+ }
+
+ /**
+ * 淇敼鎻愬崌鏈哄垎鏋�
+ *
+ * @param bo 鎻愬崌鏈哄垎鏋�
+ * @return 鏄惁淇敼鎴愬姛
+ */
+ @Override
+ public Boolean updateByBo(HoisterTimeDataBo bo) {
+ HoisterTimeData update = MapstructUtils.convert(bo, HoisterTimeData.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+ */
+ private void validEntityBeforeSave(HoisterTimeData entity){
+ //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+ }
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄ゆ彁鍗囨満鍒嗘瀽淇℃伅
+ *
+ * @param ids 寰呭垹闄ょ殑涓婚敭闆嗗悎
+ * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+ * @return 鏄惁鍒犻櫎鎴愬姛
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection<Date> ids, Boolean isValid) {
+ if(isValid){
+ //TODO 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+ }
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/impl/PackerTimeDataServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/impl/PackerTimeDataServiceImpl.java
new file mode 100755
index 0000000..0e53c35
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/impl/PackerTimeDataServiceImpl.java
@@ -0,0 +1,157 @@
+package org.dromara.qa.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.qa.domain.bo.PackerTimeDataBo;
+import org.dromara.qa.domain.vo.PackerTimeDataVo;
+import org.dromara.qa.domain.PackerTimeData;
+import org.dromara.qa.mapper.PackerTimeDataMapper;
+import org.dromara.qa.service.IPackerTimeDataService;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 鍖呰鏈哄垎鏋怱ervice涓氬姟灞傚鐞�
+ *
+ * @author zhuguifei
+ * @date 2026-01-29
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class PackerTimeDataServiceImpl implements IPackerTimeDataService {
+
+ private final PackerTimeDataMapper baseMapper;
+
+ /**
+ * 鏌ヨ鍖呰鏈哄垎鏋�
+ *
+ * @param time 涓婚敭
+ * @return 鍖呰鏈哄垎鏋�
+ */
+ @Override
+ public PackerTimeDataVo queryById(Date time){
+ return baseMapper.selectVoById(time);
+ }
+
+ /**
+ * 鍒嗛〉鏌ヨ鍖呰鏈哄垎鏋愬垪琛�
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 鍖呰鏈哄垎鏋愬垎椤靛垪琛�
+ */
+ @Override
+ public TableDataInfo<PackerTimeDataVo> queryPageList(PackerTimeDataBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper<PackerTimeData> lqw = buildQueryWrapper(bo);
+ Page<PackerTimeDataVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 鏌ヨ绗﹀悎鏉′欢鐨勫寘瑁呮満鍒嗘瀽鍒楄〃
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return 鍖呰鏈哄垎鏋愬垪琛�
+ */
+ @Override
+ public List<PackerTimeDataVo> queryList(PackerTimeDataBo bo) {
+ LambdaQueryWrapper<PackerTimeData> lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper<PackerTimeData> buildQueryWrapper(PackerTimeDataBo bo) {
+ Map<String, Object> params = bo.getParams();
+ LambdaQueryWrapper<PackerTimeData> lqw = Wrappers.lambdaQuery();
+ lqw.eq(bo.getTime() != null, PackerTimeData::getTime, bo.getTime());
+ lqw.eq(StringUtils.isNotBlank(bo.getKey()), PackerTimeData::getKey, bo.getKey());
+ lqw.eq(bo.getOnline() != null, PackerTimeData::getOnline, bo.getOnline());
+ lqw.eq(bo.getQty() != null, PackerTimeData::getQty, bo.getQty());
+ lqw.eq(bo.getBadQty() != null, PackerTimeData::getBadQty, bo.getBadQty());
+ lqw.eq(bo.getXiaohemoVal() != null, PackerTimeData::getXiaohemoVal, bo.getXiaohemoVal());
+ lqw.eq(bo.getTiaohemoVal() != null, PackerTimeData::getTiaohemoVal, bo.getTiaohemoVal());
+ lqw.eq(bo.getXiaohezhiVal() != null, PackerTimeData::getXiaohezhiVal, bo.getXiaohezhiVal());
+ lqw.eq(bo.getTiaohezhiVal() != null, PackerTimeData::getTiaohezhiVal, bo.getTiaohezhiVal());
+ lqw.eq(bo.getNeichenzhiVal() != null, PackerTimeData::getNeichenzhiVal, bo.getNeichenzhiVal());
+ lqw.eq(bo.getRunTime() != null, PackerTimeData::getRunTime, bo.getRunTime());
+ lqw.eq(bo.getStopTime() != null, PackerTimeData::getStopTime, bo.getStopTime());
+ lqw.eq(bo.getStopTimes() != null, PackerTimeData::getStopTimes, bo.getStopTimes());
+ lqw.eq(bo.getSpeed() != null, PackerTimeData::getSpeed, bo.getSpeed());
+ lqw.eq(bo.getRunStatus() != null, PackerTimeData::getRunStatus, bo.getRunStatus());
+ lqw.eq(bo.getTsQty() != null, PackerTimeData::getTsQty, bo.getTsQty());
+ lqw.eq(bo.getMainQty() != null, PackerTimeData::getMainQty, bo.getMainQty());
+ lqw.eq(bo.getMainBadQty() != null, PackerTimeData::getMainBadQty, bo.getMainBadQty());
+ lqw.eq(bo.getTbjQty() != null, PackerTimeData::getTbjQty, bo.getTbjQty());
+ lqw.eq(bo.getTbjGdQty() != null, PackerTimeData::getTbjGdQty, bo.getTbjGdQty());
+ lqw.eq(bo.getTbjBadQty() != null, PackerTimeData::getTbjBadQty, bo.getTbjBadQty());
+ lqw.eq(bo.getPbjQty() != null, PackerTimeData::getPbjQty, bo.getPbjQty());
+ lqw.eq(bo.getShift() != null, PackerTimeData::getShift, bo.getShift());
+ lqw.eq(bo.getEquNo() != null, PackerTimeData::getEquNo, bo.getEquNo());
+ lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
+ PackerTimeData::getTime, params.get("beginTime"), params.get("endTime"));
+ return lqw;
+ }
+
+ /**
+ * 鏂板鍖呰鏈哄垎鏋�
+ *
+ * @param bo 鍖呰鏈哄垎鏋�
+ * @return 鏄惁鏂板鎴愬姛
+ */
+ @Override
+ public Boolean insertByBo(PackerTimeDataBo bo) {
+ PackerTimeData add = MapstructUtils.convert(bo, PackerTimeData.class);
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setTime(add.getTime());
+ }
+ return flag;
+ }
+
+ /**
+ * 淇敼鍖呰鏈哄垎鏋�
+ *
+ * @param bo 鍖呰鏈哄垎鏋�
+ * @return 鏄惁淇敼鎴愬姛
+ */
+ @Override
+ public Boolean updateByBo(PackerTimeDataBo bo) {
+ PackerTimeData update = MapstructUtils.convert(bo, PackerTimeData.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+ */
+ private void validEntityBeforeSave(PackerTimeData entity){
+ //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+ }
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄ゅ寘瑁呮満鍒嗘瀽淇℃伅
+ *
+ * @param ids 寰呭垹闄ょ殑涓婚敭闆嗗悎
+ * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+ * @return 鏄惁鍒犻櫎鎴愬姛
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection<Date> ids, Boolean isValid) {
+ if(isValid){
+ //TODO 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+ }
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/impl/RollerTimeDataServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/impl/RollerTimeDataServiceImpl.java
new file mode 100755
index 0000000..fecb568
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/java/org/dromara/qa/service/impl/RollerTimeDataServiceImpl.java
@@ -0,0 +1,162 @@
+package org.dromara.qa.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.qa.domain.bo.RollerTimeDataBo;
+import org.dromara.qa.domain.vo.RollerTimeDataVo;
+import org.dromara.qa.domain.RollerTimeData;
+import org.dromara.qa.mapper.RollerTimeDataMapper;
+import org.dromara.qa.service.IRollerTimeDataService;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 鍗锋帴鏈哄垎鏋怱ervice涓氬姟灞傚鐞�
+ *
+ * @author zhuguifei
+ * @date 2026-01-28
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class RollerTimeDataServiceImpl implements IRollerTimeDataService {
+
+ private final RollerTimeDataMapper baseMapper;
+
+ /**
+ * 鏌ヨ鍗锋帴鏈哄垎鏋�
+ *
+ * @param time 涓婚敭
+ * @return 鍗锋帴鏈哄垎鏋�
+ */
+ @Override
+ public RollerTimeDataVo queryById(Date time){
+ return baseMapper.selectVoById(time);
+ }
+
+ /**
+ * 鍒嗛〉鏌ヨ鍗锋帴鏈哄垎鏋愬垪琛�
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 鍗锋帴鏈哄垎鏋愬垎椤靛垪琛�
+ */
+ @Override
+ public TableDataInfo<RollerTimeDataVo> queryPageList(RollerTimeDataBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper<RollerTimeData> lqw = buildQueryWrapper(bo);
+ Page<RollerTimeDataVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 鏌ヨ绗﹀悎鏉′欢鐨勫嵎鎺ユ満鍒嗘瀽鍒楄〃
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return 鍗锋帴鏈哄垎鏋愬垪琛�
+ */
+ @Override
+ public List<RollerTimeDataVo> queryList(RollerTimeDataBo bo) {
+ LambdaQueryWrapper<RollerTimeData> lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ /**
+ * 鏌ヨ閲囨牱鍚庣殑鍗锋帴鏈哄垎鏋愬垪琛� (姣忓皬鏃舵瘡璁惧60鏉�)
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return 閲囨牱鍚庣殑鍒楄〃
+ */
+ @Override
+ public List<RollerTimeDataVo> querySampledList(RollerTimeDataBo bo) {
+ return baseMapper.selectSampledList(bo);
+ }
+
+ private LambdaQueryWrapper<RollerTimeData> buildQueryWrapper(RollerTimeDataBo bo) {
+ Map<String, Object> params = bo.getParams();
+ LambdaQueryWrapper<RollerTimeData> lqw = Wrappers.lambdaQuery();
+ lqw.eq(bo.getTime() != null, RollerTimeData::getTime, bo.getTime());
+ lqw.eq(StringUtils.isNotBlank(bo.getKey()), RollerTimeData::getKey, bo.getKey());
+ lqw.eq(bo.getOnline() != null, RollerTimeData::getOnline, bo.getOnline());
+ lqw.eq(bo.getQty() != null, RollerTimeData::getQty, bo.getQty());
+ lqw.eq(bo.getBadQty() != null, RollerTimeData::getBadQty, bo.getBadQty());
+ lqw.eq(bo.getLvbangVal() != null, RollerTimeData::getLvbangVal, bo.getLvbangVal());
+ lqw.eq(bo.getJuanyanzhiVal() != null, RollerTimeData::getJuanyanzhiVal, bo.getJuanyanzhiVal());
+ lqw.eq(bo.getShuisongzhiVal() != null, RollerTimeData::getShuisongzhiVal, bo.getShuisongzhiVal());
+ lqw.eq(bo.getRunTime() != null, RollerTimeData::getRunTime, bo.getRunTime());
+ lqw.eq(bo.getStopTime() != null, RollerTimeData::getStopTime, bo.getStopTime());
+ lqw.eq(bo.getStopTimes() != null, RollerTimeData::getStopTimes, bo.getStopTimes());
+ lqw.eq(bo.getSpeed() != null, RollerTimeData::getSpeed, bo.getSpeed());
+ lqw.eq(bo.getRunStatus() != null, RollerTimeData::getRunStatus, bo.getRunStatus());
+ lqw.eq(bo.getCy() != null, RollerTimeData::getCy, bo.getCy());
+ lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
+ RollerTimeData::getTime, params.get("beginTime"), params.get("endTime"));
+ lqw.eq(bo.getCyCs() != null, RollerTimeData::getCyCs, bo.getCyCs());
+ lqw.eq(StringUtils.isNotBlank(bo.getCyOnline()), RollerTimeData::getCyOnline, bo.getCyOnline());
+ lqw.eq(bo.getRecQty1() != null, RollerTimeData::getRecQty1, bo.getRecQty1());
+ lqw.eq(bo.getRecQty2() != null, RollerTimeData::getRecQty2, bo.getRecQty2());
+ return lqw;
+ }
+
+ /**
+ * 鏂板鍗锋帴鏈哄垎鏋�
+ *
+ * @param bo 鍗锋帴鏈哄垎鏋�
+ * @return 鏄惁鏂板鎴愬姛
+ */
+ @Override
+ public Boolean insertByBo(RollerTimeDataBo bo) {
+ RollerTimeData add = MapstructUtils.convert(bo, RollerTimeData.class);
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setTime(add.getTime());
+ }
+ return flag;
+ }
+
+ /**
+ * 淇敼鍗锋帴鏈哄垎鏋�
+ *
+ * @param bo 鍗锋帴鏈哄垎鏋�
+ * @return 鏄惁淇敼鎴愬姛
+ */
+ @Override
+ public Boolean updateByBo(RollerTimeDataBo bo) {
+ RollerTimeData update = MapstructUtils.convert(bo, RollerTimeData.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+ */
+ private void validEntityBeforeSave(RollerTimeData entity){
+ //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+ }
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄ゅ嵎鎺ユ満鍒嗘瀽淇℃伅
+ *
+ * @param ids 寰呭垹闄ょ殑涓婚敭闆嗗悎
+ * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+ * @return 鏄惁鍒犻櫎鎴愬姛
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection<Date> ids, Boolean isValid) {
+ if(isValid){
+ //TODO 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+ }
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/PackerTimeDataMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/PackerTimeDataMapper.xml
new file mode 100755
index 0000000..0ce829b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/PackerTimeDataMapper.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.qa.mapper.PackerTimeDataMapper">
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/RollerTimeDataMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/RollerTimeDataMapper.xml
new file mode 100755
index 0000000..3d2e86d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-qa/src/main/resources/mapper/qa/RollerTimeDataMapper.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.qa.mapper.RollerTimeDataMapper">
+
+ <select id="selectSampledList" resultType="org.dromara.qa.domain.vo.RollerTimeDataVo">
+ WITH roller_minute AS (
+ SELECT
+ *,
+ ROW_NUMBER() OVER (
+ PARTITION BY equ_no, TO_CHAR(time, 'YYYYMMDDHH24MI')
+ ORDER BY time ASC
+ ) AS rn
+ FROM roller_time_data
+ <where>
+ <if test="bo.equNo != null">
+ AND equ_no = #{bo.equNo}
+ </if>
+ <if test="bo.shift != null">
+ AND shift = #{bo.shift}
+ </if>
+ <if test="bo.params.beginTime != null and bo.params.beginTime != ''">
+ AND time >= TO_TIMESTAMP(#{bo.params.beginTime}, 'YYYY-MM-DD HH24:MI:SS')
+ </if>
+ <if test="bo.params.endTime != null and bo.params.endTime != ''">
+ AND time <= TO_TIMESTAMP(#{bo.params.endTime}, 'YYYY-MM-DD HH24:MI:SS')
+ </if>
+ </where>
+ ),
+ packer_minute AS (
+ SELECT
+ equ_no,
+ time,
+ ts_qty,
+ qty,
+ ROW_NUMBER() OVER (
+ PARTITION BY equ_no, TO_CHAR(time, 'YYYYMMDDHH24MI')
+ ORDER BY time ASC
+ ) AS rn
+ FROM packer_time_data
+ <where>
+ <if test="bo.equNo != null">
+ AND equ_no = (#{bo.equNo}::integer + 100)
+ </if>
+ <if test="bo.shift != null">
+ AND shift = #{bo.shift}
+ </if>
+ <if test="bo.params.beginTime != null and bo.params.beginTime != ''">
+ AND time >= TO_TIMESTAMP(#{bo.params.beginTime}, 'YYYY-MM-DD HH24:MI:SS')
+ </if>
+ <if test="bo.params.endTime != null and bo.params.endTime != ''">
+ AND time <= TO_TIMESTAMP(#{bo.params.endTime}, 'YYYY-MM-DD HH24:MI:SS')
+ </if>
+ </where>
+ )
+ SELECT
+ r.*,
+ p.ts_qty AS tsQty,
+ p.qty AS packerQty
+ FROM roller_minute r
+ LEFT JOIN packer_minute p ON p.equ_no = (r.equ_no + 100)
+ AND TO_CHAR(p.time, 'YYYYMMDDHH24MI') = TO_CHAR(r.time, 'YYYYMMDDHH24MI')
+ AND p.rn = 1
+ WHERE r.rn = 1
+ ORDER BY r.time ASC
+ </select>
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/.flattened-pom.xml
new file mode 100644
index 0000000..65ee993
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/.flattened-pom.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-modules</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-system</artifactId>
+ <version>5.5.3</version>
+ <description>system绯荤粺妯″潡</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-translation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-oss</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-log</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-excel</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sms</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-tenant</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-security</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-idempotent</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sensitive</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-encrypt</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-websocket</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sse</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/pom.xml
new file mode 100755
index 0000000..0fc6d55
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/pom.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-modules</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ruoyi-system</artifactId>
+
+ <description>
+ system绯荤粺妯″潡
+ </description>
+
+ <dependencies>
+ <!-- 閫氱敤宸ュ叿-->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-translation</artifactId>
+ </dependency>
+
+ <!-- OSS鍔熻兘妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-oss</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-log</artifactId>
+ </dependency>
+
+ <!-- excel-->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-excel</artifactId>
+ </dependency>
+
+ <!-- SMS鍔熻兘妯″潡 -->
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sms</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-tenant</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-security</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-web</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-idempotent</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sensitive</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-encrypt</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-websocket</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sse</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/CacheController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/CacheController.java
new file mode 100755
index 0000000..4a82536
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/CacheController.java
@@ -0,0 +1,65 @@
+package org.dromara.system.controller.monitor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.StringUtils;
+import org.redisson.spring.data.connection.RedissonConnectionFactory;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.core.RedisConnectionUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.*;
+
+/**
+ * 缂撳瓨鐩戞帶
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/monitor/cache")
+public class CacheController {
+
+ private final RedissonConnectionFactory connectionFactory;
+
+ /**
+ * 鑾峰彇缂撳瓨鐩戞帶鍒楄〃
+ */
+ @SaCheckPermission("monitor:cache:list")
+ @GetMapping()
+ public R<CacheListInfoVo> getInfo() throws Exception {
+ RedisConnection connection = connectionFactory.getConnection();
+ try {
+ Properties commandStats = connection.commands().info("commandstats");
+ List<Map<String, String>> pieList = new ArrayList<>();
+ if (commandStats != null) {
+ commandStats.stringPropertyNames().forEach(key -> {
+ Map<String, String> data = new HashMap<>(2);
+ String property = commandStats.getProperty(key);
+ data.put("name", StringUtils.removeStart(key, "cmdstat_"));
+ data.put("value", StringUtils.substringBetween(property, "calls=", ",usec"));
+ pieList.add(data);
+ });
+ }
+ return R.ok(new CacheListInfoVo(
+ connection.commands().info(),
+ connection.commands().dbSize(), pieList));
+ } finally {
+ // 褰掕繕杩炴帴缁欒繛鎺ユ睜
+ RedisConnectionUtils.releaseConnection(connection, connectionFactory);
+ }
+ }
+
+ /**
+ * 缂撳瓨鐩戞帶鍒楄〃淇℃伅
+ *
+ * @param info 淇℃伅
+ * @param dbSize 鏁版嵁搴�
+ * @param commandStats 鍛戒护缁熻
+ */
+ public record CacheListInfoVo(Properties info, Long dbSize, List<Map<String, String>> commandStats) {}
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysLogininforController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysLogininforController.java
new file mode 100755
index 0000000..3787170
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysLogininforController.java
@@ -0,0 +1,93 @@
+package org.dromara.system.controller.monitor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.baomidou.lock.annotation.Lock4j;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.CacheConstants;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysLogininforBo;
+import org.dromara.system.domain.vo.SysLogininforVo;
+import org.dromara.system.service.ISysLogininforService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 绯荤粺璁块棶璁板綍
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/monitor/logininfor")
+public class SysLogininforController extends BaseController {
+
+ private final ISysLogininforService logininforService;
+
+ /**
+ * 鑾峰彇绯荤粺璁块棶璁板綍鍒楄〃
+ */
+ @SaCheckPermission("monitor:logininfor:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysLogininforVo> list(SysLogininforBo logininfor, PageQuery pageQuery) {
+ return logininforService.selectPageLogininforList(logininfor, pageQuery);
+ }
+
+ /**
+ * 瀵煎嚭绯荤粺璁块棶璁板綍鍒楄〃
+ */
+ @Log(title = "鐧诲綍鏃ュ織", businessType = BusinessType.EXPORT)
+ @SaCheckPermission("monitor:logininfor:export")
+ @PostMapping("/export")
+ public void export(SysLogininforBo logininfor, HttpServletResponse response) {
+ List<SysLogininforVo> list = logininforService.selectLogininforList(logininfor);
+ ExcelUtil.exportExcel(list, "鐧诲綍鏃ュ織", SysLogininforVo.class, response);
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎鐧诲綍鏃ュ織
+ * @param infoIds 鏃ュ織ids
+ */
+ @SaCheckPermission("monitor:logininfor:remove")
+ @Log(title = "鐧诲綍鏃ュ織", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{infoIds}")
+ public R<Void> remove(@PathVariable Long[] infoIds) {
+ return toAjax(logininforService.deleteLogininforByIds(infoIds));
+ }
+
+ /**
+ * 娓呯悊绯荤粺璁块棶璁板綍
+ */
+ @SaCheckPermission("monitor:logininfor:remove")
+ @Log(title = "鐧诲綍鏃ュ織", businessType = BusinessType.CLEAN)
+ @Lock4j
+ @DeleteMapping("/clean")
+ public R<Void> clean() {
+ logininforService.cleanLogininfor();
+ return R.ok();
+ }
+
+ @SaCheckPermission("monitor:logininfor:unlock")
+ @Log(title = "璐︽埛瑙i攣", businessType = BusinessType.OTHER)
+ @RepeatSubmit()
+ @GetMapping("/unlock/{userName}")
+ public R<Void> unlock(@PathVariable("userName") String userName) {
+ String loginName = CacheConstants.PWD_ERR_CNT_KEY + userName;
+ if (RedisUtils.hasKey(loginName)) {
+ RedisUtils.deleteObject(loginName);
+ }
+ return R.ok();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysOperlogController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysOperlogController.java
new file mode 100755
index 0000000..d2e4c9e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysOperlogController.java
@@ -0,0 +1,77 @@
+package org.dromara.system.controller.monitor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.baomidou.lock.annotation.Lock4j;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysOperLogBo;
+import org.dromara.system.domain.vo.SysOperLogVo;
+import org.dromara.system.service.ISysOperLogService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 鎿嶄綔鏃ュ織璁板綍
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/monitor/operlog")
+public class SysOperlogController extends BaseController {
+
+ private final ISysOperLogService operLogService;
+
+ /**
+ * 鑾峰彇鎿嶄綔鏃ュ織璁板綍鍒楄〃
+ */
+ @SaCheckPermission("monitor:operlog:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysOperLogVo> list(SysOperLogBo operLog, PageQuery pageQuery) {
+ return operLogService.selectPageOperLogList(operLog, pageQuery);
+ }
+
+ /**
+ * 瀵煎嚭鎿嶄綔鏃ュ織璁板綍鍒楄〃
+ */
+ @Log(title = "鎿嶄綔鏃ュ織", businessType = BusinessType.EXPORT)
+ @SaCheckPermission("monitor:operlog:export")
+ @PostMapping("/export")
+ public void export(SysOperLogBo operLog, HttpServletResponse response) {
+ List<SysOperLogVo> list = operLogService.selectOperLogList(operLog);
+ ExcelUtil.exportExcel(list, "鎿嶄綔鏃ュ織", SysOperLogVo.class, response);
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎鎿嶄綔鏃ュ織璁板綍
+ * @param operIds 鏃ュ織ids
+ */
+ @Log(title = "鎿嶄綔鏃ュ織", businessType = BusinessType.DELETE)
+ @SaCheckPermission("monitor:operlog:remove")
+ @DeleteMapping("/{operIds}")
+ public R<Void> remove(@PathVariable Long[] operIds) {
+ return toAjax(operLogService.deleteOperLogByIds(operIds));
+ }
+
+ /**
+ * 娓呯悊鎿嶄綔鏃ュ織璁板綍
+ */
+ @Log(title = "鎿嶄綔鏃ュ織", businessType = BusinessType.CLEAN)
+ @SaCheckPermission("monitor:operlog:remove")
+ @Lock4j
+ @DeleteMapping("/clean")
+ public R<Void> clean() {
+ operLogService.cleanOperLog();
+ return R.ok();
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java
new file mode 100755
index 0000000..0e6f171
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java
@@ -0,0 +1,134 @@
+package org.dromara.system.controller.monitor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.bean.BeanUtil;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.CacheConstants;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.domain.dto.UserOnlineDTO;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.SysUserOnline;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 鍦ㄧ嚎鐢ㄦ埛鐩戞帶
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/monitor/online")
+public class SysUserOnlineController extends BaseController {
+
+ /**
+ * 鑾峰彇鍦ㄧ嚎鐢ㄦ埛鐩戞帶鍒楄〃
+ *
+ * @param ipaddr IP鍦板潃
+ * @param userName 鐢ㄦ埛鍚�
+ */
+ @SaCheckPermission("monitor:online:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysUserOnline> list(String ipaddr, String userName) {
+ // 鑾峰彇鎵�鏈夋湭杩囨湡鐨� token
+ Collection<String> keys = RedisUtils.keys(CacheConstants.ONLINE_TOKEN_KEY + "*");
+ List<UserOnlineDTO> userOnlineDTOList = new ArrayList<>();
+ for (String key : keys) {
+ String token = StringUtils.substringAfterLast(key, ":");
+ // 濡傛灉宸茬粡杩囨湡鍒欒烦杩�
+ if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
+ continue;
+ }
+ userOnlineDTOList.add(RedisUtils.getCacheObject(CacheConstants.ONLINE_TOKEN_KEY + token));
+ }
+ if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) {
+ userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
+ StringUtils.equals(ipaddr, userOnline.getIpaddr()) &&
+ StringUtils.equals(userName, userOnline.getUserName())
+ );
+ } else if (StringUtils.isNotEmpty(ipaddr)) {
+ userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
+ StringUtils.equals(ipaddr, userOnline.getIpaddr())
+ );
+ } else if (StringUtils.isNotEmpty(userName)) {
+ userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
+ StringUtils.equals(userName, userOnline.getUserName())
+ );
+ }
+ Collections.reverse(userOnlineDTOList);
+ userOnlineDTOList.removeAll(Collections.singleton(null));
+ List<SysUserOnline> userOnlineList = BeanUtil.copyToList(userOnlineDTOList, SysUserOnline.class);
+ return TableDataInfo.build(userOnlineList);
+ }
+
+ /**
+ * 寮洪��鐢ㄦ埛
+ *
+ * @param tokenId token鍊�
+ */
+ @SaCheckPermission("monitor:online:forceLogout")
+ @Log(title = "鍦ㄧ嚎鐢ㄦ埛", businessType = BusinessType.FORCE)
+ @RepeatSubmit()
+ @DeleteMapping("/{tokenId}")
+ public R<Void> forceLogout(@PathVariable String tokenId) {
+ try {
+ StpUtil.kickoutByTokenValue(tokenId);
+ } catch (NotLoginException ignored) {
+ }
+ return R.ok();
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鐢ㄦ埛鐧诲綍鍦ㄧ嚎璁惧
+ */
+ @GetMapping()
+ public TableDataInfo<SysUserOnline> getInfo() {
+ // 鑾峰彇鎸囧畾璐﹀彿 id 鐨� token 闆嗗悎
+ List<String> tokenIds = StpUtil.getTokenValueListByLoginId(StpUtil.getLoginIdAsString());
+ List<UserOnlineDTO> userOnlineDTOList = tokenIds.stream()
+ .filter(token -> StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) >= -1)
+ .map(token -> (UserOnlineDTO) RedisUtils.getCacheObject(CacheConstants.ONLINE_TOKEN_KEY + token))
+ .collect(Collectors.toList());
+ //澶嶅埗鍜屽鐞� SysUserOnline 瀵硅薄鍒楄〃
+ Collections.reverse(userOnlineDTOList);
+ userOnlineDTOList.removeAll(Collections.singleton(null));
+ List<SysUserOnline> userOnlineList = BeanUtil.copyToList(userOnlineDTOList, SysUserOnline.class);
+ return TableDataInfo.build(userOnlineList);
+ }
+
+ /**
+ * 寮洪��褰撳墠鍦ㄧ嚎璁惧
+ *
+ * @param tokenId token鍊�
+ */
+ @Log(title = "鍦ㄧ嚎璁惧", businessType = BusinessType.FORCE)
+ @RepeatSubmit()
+ @DeleteMapping("/myself/{tokenId}")
+ public R<Void> remove(@PathVariable("tokenId") String tokenId) {
+ try {
+ // 鑾峰彇鎸囧畾璐﹀彿 id 鐨� token 闆嗗悎
+ List<String> keys = StpUtil.getTokenValueListByLoginId(StpUtil.getLoginIdAsString());
+ keys.stream()
+ .filter(key -> key.equals(tokenId))
+ .findFirst()
+ .ifPresent(key -> StpUtil.kickoutByTokenValue(tokenId));
+ } catch (NotLoginException ignored) {
+ }
+ return R.ok();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java
new file mode 100755
index 0000000..fb87df7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java
@@ -0,0 +1,122 @@
+package org.dromara.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysClientBo;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.system.service.ISysClientService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 瀹㈡埛绔鐞�
+ *
+ * @author Michelle.Chung
+ * @date 2023-06-18
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/client")
+public class SysClientController extends BaseController {
+
+ private final ISysClientService sysClientService;
+
+ /**
+ * 鏌ヨ瀹㈡埛绔鐞嗗垪琛�
+ */
+ @SaCheckPermission("system:client:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysClientVo> list(SysClientBo bo, PageQuery pageQuery) {
+ return sysClientService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 瀵煎嚭瀹㈡埛绔鐞嗗垪琛�
+ */
+ @SaCheckPermission("system:client:export")
+ @Log(title = "瀹㈡埛绔鐞�", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(SysClientBo bo, HttpServletResponse response) {
+ List<SysClientVo> list = sysClientService.queryList(bo);
+ ExcelUtil.exportExcel(list, "瀹㈡埛绔鐞�", SysClientVo.class, response);
+ }
+
+ /**
+ * 鑾峰彇瀹㈡埛绔鐞嗚缁嗕俊鎭�
+ *
+ * @param id 涓婚敭
+ */
+ @SaCheckPermission("system:client:query")
+ @GetMapping("/{id}")
+ public R<SysClientVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long id) {
+ return R.ok(sysClientService.queryById(id));
+ }
+
+ /**
+ * 鏂板瀹㈡埛绔鐞�
+ */
+ @SaCheckPermission("system:client:add")
+ @Log(title = "瀹㈡埛绔鐞�", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R<Void> add(@Validated(AddGroup.class) @RequestBody SysClientBo bo) {
+ if (!sysClientService.checkClickKeyUnique(bo)) {
+ return R.fail("鏂板瀹㈡埛绔�'" + bo.getClientKey() + "'澶辫触锛屽鎴风key宸插瓨鍦�");
+ }
+ return toAjax(sysClientService.insertByBo(bo));
+ }
+
+ /**
+ * 淇敼瀹㈡埛绔鐞�
+ */
+ @SaCheckPermission("system:client:edit")
+ @Log(title = "瀹㈡埛绔鐞�", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysClientBo bo) {
+ if (!sysClientService.checkClickKeyUnique(bo)) {
+ return R.fail("淇敼瀹㈡埛绔�'" + bo.getClientKey() + "'澶辫触锛屽鎴风key宸插瓨鍦�");
+ }
+ return toAjax(sysClientService.updateByBo(bo));
+ }
+
+ /**
+ * 鐘舵�佷慨鏀�
+ */
+ @SaCheckPermission("system:client:edit")
+ @Log(title = "瀹㈡埛绔鐞�", businessType = BusinessType.UPDATE)
+ @PutMapping("/changeStatus")
+ public R<Void> changeStatus(@RequestBody SysClientBo bo) {
+ return toAjax(sysClientService.updateClientStatus(bo.getClientId(), bo.getStatus()));
+ }
+
+ /**
+ * 鍒犻櫎瀹㈡埛绔鐞�
+ *
+ * @param ids 涓婚敭涓�
+ */
+ @SaCheckPermission("system:client:remove")
+ @Log(title = "瀹㈡埛绔鐞�", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long[] ids) {
+ return toAjax(sysClientService.deleteWithValidByIds(List.of(ids), true));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysConfigController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysConfigController.java
new file mode 100755
index 0000000..2be7858
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysConfigController.java
@@ -0,0 +1,142 @@
+package org.dromara.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysConfigBo;
+import org.dromara.system.domain.vo.SysConfigVo;
+import org.dromara.system.service.ISysConfigService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 鍙傛暟閰嶇疆 淇℃伅鎿嶄綔澶勭悊
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/config")
+public class SysConfigController extends BaseController {
+
+ private final ISysConfigService configService;
+
+ /**
+ * 鑾峰彇鍙傛暟閰嶇疆鍒楄〃
+ */
+ @SaCheckPermission("system:config:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysConfigVo> list(SysConfigBo config, PageQuery pageQuery) {
+ return configService.selectPageConfigList(config, pageQuery);
+ }
+
+ /**
+ * 瀵煎嚭鍙傛暟閰嶇疆鍒楄〃
+ */
+ @Log(title = "鍙傛暟绠$悊", businessType = BusinessType.EXPORT)
+ @SaCheckPermission("system:config:export")
+ @PostMapping("/export")
+ public void export(SysConfigBo config, HttpServletResponse response) {
+ List<SysConfigVo> list = configService.selectConfigList(config);
+ ExcelUtil.exportExcel(list, "鍙傛暟鏁版嵁", SysConfigVo.class, response);
+ }
+
+ /**
+ * 鏍规嵁鍙傛暟缂栧彿鑾峰彇璇︾粏淇℃伅
+ *
+ * @param configId 鍙傛暟ID
+ */
+ @SaCheckPermission("system:config:query")
+ @GetMapping(value = "/{configId}")
+ public R<SysConfigVo> getInfo(@PathVariable Long configId) {
+ return R.ok(configService.selectConfigById(configId));
+ }
+
+ /**
+ * 鏍规嵁鍙傛暟閿悕鏌ヨ鍙傛暟鍊�
+ *
+ * @param configKey 鍙傛暟Key
+ */
+ @GetMapping(value = "/configKey/{configKey}")
+ public R<String> getConfigKey(@PathVariable String configKey) {
+ return R.ok("鎿嶄綔鎴愬姛", configService.selectConfigByKey(configKey));
+ }
+
+ /**
+ * 鏂板鍙傛暟閰嶇疆
+ */
+ @SaCheckPermission("system:config:add")
+ @Log(title = "鍙傛暟绠$悊", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping
+ public R<Void> add(@Validated @RequestBody SysConfigBo config) {
+ if (!configService.checkConfigKeyUnique(config)) {
+ return R.fail("鏂板鍙傛暟'" + config.getConfigName() + "'澶辫触锛屽弬鏁伴敭鍚嶅凡瀛樺湪");
+ }
+ configService.insertConfig(config);
+ return R.ok();
+ }
+
+ /**
+ * 淇敼鍙傛暟閰嶇疆
+ */
+ @SaCheckPermission("system:config:edit")
+ @Log(title = "鍙傛暟绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping
+ public R<Void> edit(@Validated @RequestBody SysConfigBo config) {
+ if (!configService.checkConfigKeyUnique(config)) {
+ return R.fail("淇敼鍙傛暟'" + config.getConfigName() + "'澶辫触锛屽弬鏁伴敭鍚嶅凡瀛樺湪");
+ }
+ configService.updateConfig(config);
+ return R.ok();
+ }
+
+ /**
+ * 鏍规嵁鍙傛暟閿悕淇敼鍙傛暟閰嶇疆
+ */
+ @SaCheckPermission("system:config:edit")
+ @Log(title = "鍙傛暟绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping("/updateByKey")
+ public R<Void> updateByKey(@RequestBody SysConfigBo config) {
+ configService.updateConfig(config);
+ return R.ok();
+ }
+
+ /**
+ * 鍒犻櫎鍙傛暟閰嶇疆
+ *
+ * @param configIds 鍙傛暟ID涓�
+ */
+ @SaCheckPermission("system:config:remove")
+ @Log(title = "鍙傛暟绠$悊", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{configIds}")
+ public R<Void> remove(@PathVariable Long[] configIds) {
+ configService.deleteConfigByIds(Arrays.asList(configIds));
+ return R.ok();
+ }
+
+ /**
+ * 鍒锋柊鍙傛暟缂撳瓨
+ */
+ @SaCheckPermission("system:config:remove")
+ @Log(title = "鍙傛暟绠$悊", businessType = BusinessType.CLEAN)
+ @DeleteMapping("/refreshCache")
+ public R<Void> refreshCache() {
+ configService.resetConfigCache();
+ return R.ok();
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java
new file mode 100755
index 0000000..3acb947
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java
@@ -0,0 +1,146 @@
+package org.dromara.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.convert.Convert;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysDeptBo;
+import org.dromara.system.domain.vo.SysDeptVo;
+import org.dromara.system.service.ISysDeptService;
+import org.dromara.system.service.ISysPostService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 閮ㄩ棬淇℃伅
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dept")
+public class SysDeptController extends BaseController {
+
+ private final ISysDeptService deptService;
+ private final ISysPostService postService;
+
+ /**
+ * 鑾峰彇閮ㄩ棬鍒楄〃
+ */
+ @SaCheckPermission("system:dept:list")
+ @GetMapping("/list")
+ public R<List<SysDeptVo>> list(SysDeptBo dept) {
+ List<SysDeptVo> depts = deptService.selectDeptList(dept);
+ return R.ok(depts);
+ }
+
+ /**
+ * 鏌ヨ閮ㄩ棬鍒楄〃锛堟帓闄よ妭鐐癸級
+ *
+ * @param deptId 閮ㄩ棬ID
+ */
+ @SaCheckPermission("system:dept:list")
+ @GetMapping("/list/exclude/{deptId}")
+ public R<List<SysDeptVo>> excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) {
+ List<SysDeptVo> depts = deptService.selectDeptList(new SysDeptBo());
+ depts.removeIf(d -> d.getDeptId().equals(deptId)
+ || StringUtils.splitList(d.getAncestors()).contains(Convert.toStr(deptId)));
+ return R.ok(depts);
+ }
+
+ /**
+ * 鏍规嵁閮ㄩ棬缂栧彿鑾峰彇璇︾粏淇℃伅
+ *
+ * @param deptId 閮ㄩ棬ID
+ */
+ @SaCheckPermission("system:dept:query")
+ @GetMapping(value = "/{deptId}")
+ public R<SysDeptVo> getInfo(@PathVariable Long deptId) {
+ deptService.checkDeptDataScope(deptId);
+ return R.ok(deptService.selectDeptById(deptId));
+ }
+
+ /**
+ * 鏂板閮ㄩ棬
+ */
+ @SaCheckPermission("system:dept:add")
+ @Log(title = "閮ㄩ棬绠$悊", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping
+ public R<Void> add(@Validated @RequestBody SysDeptBo dept) {
+ if (!deptService.checkDeptNameUnique(dept)) {
+ return R.fail("鏂板閮ㄩ棬'" + dept.getDeptName() + "'澶辫触锛岄儴闂ㄥ悕绉板凡瀛樺湪");
+ }
+ return toAjax(deptService.insertDept(dept));
+ }
+
+ /**
+ * 淇敼閮ㄩ棬
+ */
+ @SaCheckPermission("system:dept:edit")
+ @Log(title = "閮ㄩ棬绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping
+ public R<Void> edit(@Validated @RequestBody SysDeptBo dept) {
+ Long deptId = dept.getDeptId();
+ deptService.checkDeptDataScope(deptId);
+ if (!deptService.checkDeptNameUnique(dept)) {
+ return R.fail("淇敼閮ㄩ棬'" + dept.getDeptName() + "'澶辫触锛岄儴闂ㄥ悕绉板凡瀛樺湪");
+ } else if (dept.getParentId().equals(deptId)) {
+ return R.fail("淇敼閮ㄩ棬'" + dept.getDeptName() + "'澶辫触锛屼笂绾ч儴闂ㄤ笉鑳芥槸鑷繁");
+ } else if (StringUtils.equals(SystemConstants.DISABLE, dept.getStatus())) {
+ if (deptService.selectNormalChildrenDeptById(deptId) > 0) {
+ return R.fail("璇ラ儴闂ㄥ寘鍚湭鍋滅敤鐨勫瓙閮ㄩ棬!");
+ } else if (deptService.checkDeptExistUser(deptId)) {
+ return R.fail("璇ラ儴闂ㄤ笅瀛樺湪宸插垎閰嶇敤鎴凤紝涓嶈兘绂佺敤!");
+ }
+ }
+ return toAjax(deptService.updateDept(dept));
+ }
+
+ /**
+ * 鍒犻櫎閮ㄩ棬
+ *
+ * @param deptId 閮ㄩ棬ID
+ */
+ @SaCheckPermission("system:dept:remove")
+ @Log(title = "閮ㄩ棬绠$悊", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{deptId}")
+ public R<Void> remove(@PathVariable Long deptId) {
+ if (SystemConstants.DEFAULT_DEPT_ID.equals(deptId)) {
+ return R.warn("榛樿閮ㄩ棬,涓嶅厑璁稿垹闄�");
+ }
+ if (deptService.hasChildByDeptId(deptId)) {
+ return R.warn("瀛樺湪涓嬬骇閮ㄩ棬,涓嶅厑璁稿垹闄�");
+ }
+ if (deptService.checkDeptExistUser(deptId)) {
+ return R.warn("閮ㄩ棬瀛樺湪鐢ㄦ埛,涓嶅厑璁稿垹闄�");
+ }
+ if (postService.countPostByDeptId(deptId) > 0) {
+ return R.warn("閮ㄩ棬瀛樺湪宀椾綅,涓嶅厑璁稿垹闄�");
+ }
+ deptService.checkDeptDataScope(deptId);
+ return toAjax(deptService.deleteDeptById(deptId));
+ }
+
+ /**
+ * 鑾峰彇閮ㄩ棬閫夋嫨妗嗗垪琛�
+ *
+ * @param deptIds 閮ㄩ棬ID涓�
+ */
+ @SaCheckPermission("system:dept:query")
+ @GetMapping("/optionselect")
+ public R<List<SysDeptVo>> optionselect(@RequestParam(required = false) Long[] deptIds) {
+ return R.ok(deptService.selectDeptByIds(deptIds == null ? null : List.of(deptIds)));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java
new file mode 100755
index 0000000..ae6d580
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java
@@ -0,0 +1,127 @@
+package org.dromara.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysDictDataBo;
+import org.dromara.system.domain.vo.SysDictDataVo;
+import org.dromara.system.service.ISysDictDataService;
+import org.dromara.system.service.ISysDictTypeService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 鏁版嵁瀛楀吀淇℃伅
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dict/data")
+public class SysDictDataController extends BaseController {
+
+ private final ISysDictDataService dictDataService;
+ private final ISysDictTypeService dictTypeService;
+
+ /**
+ * 鏌ヨ瀛楀吀鏁版嵁鍒楄〃
+ */
+ @SaCheckPermission("system:dict:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysDictDataVo> list(SysDictDataBo dictData, PageQuery pageQuery) {
+ return dictDataService.selectPageDictDataList(dictData, pageQuery);
+ }
+
+ /**
+ * 瀵煎嚭瀛楀吀鏁版嵁鍒楄〃
+ */
+ @Log(title = "瀛楀吀鏁版嵁", businessType = BusinessType.EXPORT)
+ @SaCheckPermission("system:dict:export")
+ @PostMapping("/export")
+ public void export(SysDictDataBo dictData, HttpServletResponse response) {
+ List<SysDictDataVo> list = dictDataService.selectDictDataList(dictData);
+ ExcelUtil.exportExcel(list, "瀛楀吀鏁版嵁", SysDictDataVo.class, response);
+ }
+
+ /**
+ * 鏌ヨ瀛楀吀鏁版嵁璇︾粏
+ *
+ * @param dictCode 瀛楀吀code
+ */
+ @SaCheckPermission("system:dict:query")
+ @GetMapping(value = "/{dictCode}")
+ public R<SysDictDataVo> getInfo(@PathVariable Long dictCode) {
+ return R.ok(dictDataService.selectDictDataById(dictCode));
+ }
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鏌ヨ瀛楀吀鏁版嵁淇℃伅
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ */
+ @GetMapping(value = "/type/{dictType}")
+ public R<List<SysDictDataVo>> dictType(@PathVariable String dictType) {
+ List<SysDictDataVo> data = dictTypeService.selectDictDataByType(dictType);
+ if (ObjectUtil.isNull(data)) {
+ data = new ArrayList<>();
+ }
+ return R.ok(data);
+ }
+
+ /**
+ * 鏂板瀛楀吀绫诲瀷
+ */
+ @SaCheckPermission("system:dict:add")
+ @Log(title = "瀛楀吀鏁版嵁", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping
+ public R<Void> add(@Validated @RequestBody SysDictDataBo dict) {
+ if (!dictDataService.checkDictDataUnique(dict)) {
+ return R.fail("鏂板瀛楀吀鏁版嵁'" + dict.getDictValue() + "'澶辫触锛屽瓧鍏搁敭鍊煎凡瀛樺湪");
+ }
+ dictDataService.insertDictData(dict);
+ return R.ok();
+ }
+
+ /**
+ * 淇敼淇濆瓨瀛楀吀绫诲瀷
+ */
+ @SaCheckPermission("system:dict:edit")
+ @Log(title = "瀛楀吀鏁版嵁", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping
+ public R<Void> edit(@Validated @RequestBody SysDictDataBo dict) {
+ if (!dictDataService.checkDictDataUnique(dict)) {
+ return R.fail("淇敼瀛楀吀鏁版嵁'" + dict.getDictValue() + "'澶辫触锛屽瓧鍏搁敭鍊煎凡瀛樺湪");
+ }
+ dictDataService.updateDictData(dict);
+ return R.ok();
+ }
+
+ /**
+ * 鍒犻櫎瀛楀吀绫诲瀷
+ *
+ * @param dictCodes 瀛楀吀code涓�
+ */
+ @SaCheckPermission("system:dict:remove")
+ @Log(title = "瀛楀吀绫诲瀷", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{dictCodes}")
+ public R<Void> remove(@PathVariable Long[] dictCodes) {
+ dictDataService.deleteDictDataByIds(Arrays.asList(dictCodes));
+ return R.ok();
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictTypeController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictTypeController.java
new file mode 100755
index 0000000..d88e6ec
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictTypeController.java
@@ -0,0 +1,131 @@
+package org.dromara.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.baomidou.lock.annotation.Lock4j;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysDictTypeBo;
+import org.dromara.system.domain.vo.SysDictTypeVo;
+import org.dromara.system.service.ISysDictTypeService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 鏁版嵁瀛楀吀淇℃伅
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dict/type")
+public class SysDictTypeController extends BaseController {
+
+ private final ISysDictTypeService dictTypeService;
+
+ /**
+ * 鏌ヨ瀛楀吀绫诲瀷鍒楄〃
+ */
+ @SaCheckPermission("system:dict:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysDictTypeVo> list(SysDictTypeBo dictType, PageQuery pageQuery) {
+ return dictTypeService.selectPageDictTypeList(dictType, pageQuery);
+ }
+
+ /**
+ * 瀵煎嚭瀛楀吀绫诲瀷鍒楄〃
+ */
+ @Log(title = "瀛楀吀绫诲瀷", businessType = BusinessType.EXPORT)
+ @SaCheckPermission("system:dict:export")
+ @PostMapping("/export")
+ public void export(SysDictTypeBo dictType, HttpServletResponse response) {
+ List<SysDictTypeVo> list = dictTypeService.selectDictTypeList(dictType);
+ ExcelUtil.exportExcel(list, "瀛楀吀绫诲瀷", SysDictTypeVo.class, response);
+ }
+
+ /**
+ * 鏌ヨ瀛楀吀绫诲瀷璇︾粏
+ *
+ * @param dictId 瀛楀吀ID
+ */
+ @SaCheckPermission("system:dict:query")
+ @GetMapping(value = "/{dictId}")
+ public R<SysDictTypeVo> getInfo(@PathVariable Long dictId) {
+ return R.ok(dictTypeService.selectDictTypeById(dictId));
+ }
+
+ /**
+ * 鏂板瀛楀吀绫诲瀷
+ */
+ @SaCheckPermission("system:dict:add")
+ @Log(title = "瀛楀吀绫诲瀷", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping
+ public R<Void> add(@Validated @RequestBody SysDictTypeBo dict) {
+ if (!dictTypeService.checkDictTypeUnique(dict)) {
+ return R.fail("鏂板瀛楀吀'" + dict.getDictName() + "'澶辫触锛屽瓧鍏哥被鍨嬪凡瀛樺湪");
+ }
+ dictTypeService.insertDictType(dict);
+ return R.ok();
+ }
+
+ /**
+ * 淇敼瀛楀吀绫诲瀷
+ */
+ @SaCheckPermission("system:dict:edit")
+ @Log(title = "瀛楀吀绫诲瀷", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping
+ public R<Void> edit(@Validated @RequestBody SysDictTypeBo dict) {
+ if (!dictTypeService.checkDictTypeUnique(dict)) {
+ return R.fail("淇敼瀛楀吀'" + dict.getDictName() + "'澶辫触锛屽瓧鍏哥被鍨嬪凡瀛樺湪");
+ }
+ dictTypeService.updateDictType(dict);
+ return R.ok();
+ }
+
+ /**
+ * 鍒犻櫎瀛楀吀绫诲瀷
+ *
+ * @param dictIds 瀛楀吀ID涓�
+ */
+ @SaCheckPermission("system:dict:remove")
+ @Log(title = "瀛楀吀绫诲瀷", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{dictIds}")
+ public R<Void> remove(@PathVariable Long[] dictIds) {
+ dictTypeService.deleteDictTypeByIds(Arrays.asList(dictIds));
+ return R.ok();
+ }
+
+ /**
+ * 鍒锋柊瀛楀吀缂撳瓨
+ */
+ @SaCheckPermission("system:dict:remove")
+ @Log(title = "瀛楀吀绫诲瀷", businessType = BusinessType.CLEAN)
+ @Lock4j
+ @DeleteMapping("/refreshCache")
+ public R<Void> refreshCache() {
+ dictTypeService.resetDictCache();
+ return R.ok();
+ }
+
+ /**
+ * 鑾峰彇瀛楀吀閫夋嫨妗嗗垪琛�
+ */
+ @GetMapping("/optionselect")
+ public R<List<SysDictTypeVo>> optionselect() {
+ List<SysDictTypeVo> dictTypes = dictTypeService.selectDictTypeAll();
+ return R.ok(dictTypes);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java
new file mode 100755
index 0000000..7d0ca00
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java
@@ -0,0 +1,213 @@
+package org.dromara.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaCheckRole;
+import cn.dev33.satoken.annotation.SaMode;
+import cn.hutool.core.lang.tree.Tree;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.constant.TenantConstants;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.SysMenu;
+import org.dromara.system.domain.bo.SysMenuBo;
+import org.dromara.system.domain.vo.RouterVo;
+import org.dromara.system.domain.vo.SysMenuVo;
+import org.dromara.system.service.ISysMenuService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鑿滃崟淇℃伅
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/menu")
+public class SysMenuController extends BaseController {
+
+ private final ISysMenuService menuService;
+
+ /**
+ * 鑾峰彇璺敱淇℃伅
+ *
+ * @return 璺敱淇℃伅
+ */
+ @GetMapping("/getRouters")
+ public R<List<RouterVo>> getRouters() {
+ List<SysMenu> menus = menuService.selectMenuTreeByUserId(LoginHelper.getUserId());
+ return R.ok(menuService.buildMenus(menus));
+ }
+
+ /**
+ * 鑾峰彇鑿滃崟鍒楄〃
+ */
+ @SaCheckRole(value = {
+ TenantConstants.SUPER_ADMIN_ROLE_KEY,
+ TenantConstants.TENANT_ADMIN_ROLE_KEY
+ }, mode = SaMode.OR)
+ @SaCheckPermission("system:menu:list")
+ @GetMapping("/list")
+ public R<List<SysMenuVo>> list(SysMenuBo menu) {
+ List<SysMenuVo> menus = menuService.selectMenuList(menu, LoginHelper.getUserId());
+ return R.ok(menus);
+ }
+
+ /**
+ * 鏍规嵁鑿滃崟缂栧彿鑾峰彇璇︾粏淇℃伅
+ *
+ * @param menuId 鑿滃崟ID
+ */
+ @SaCheckRole(value = {
+ TenantConstants.SUPER_ADMIN_ROLE_KEY,
+ TenantConstants.TENANT_ADMIN_ROLE_KEY
+ }, mode = SaMode.OR)
+ @SaCheckPermission("system:menu:query")
+ @GetMapping(value = "/{menuId}")
+ public R<SysMenuVo> getInfo(@PathVariable Long menuId) {
+ return R.ok(menuService.selectMenuById(menuId));
+ }
+
+ /**
+ * 鑾峰彇鑿滃崟涓嬫媺鏍戝垪琛�
+ */
+ @SaCheckPermission("system:menu:query")
+ @GetMapping("/treeselect")
+ public R<List<Tree<Long>>> treeselect(SysMenuBo menu) {
+ List<SysMenuVo> menus = menuService.selectMenuList(menu, LoginHelper.getUserId());
+ return R.ok(menuService.buildMenuTreeSelect(menus));
+ }
+
+ /**
+ * 鍔犺浇瀵瑰簲瑙掕壊鑿滃崟鍒楄〃鏍�
+ *
+ * @param roleId 瑙掕壊ID
+ */
+ @SaCheckPermission("system:menu:query")
+ @GetMapping(value = "/roleMenuTreeselect/{roleId}")
+ public R<MenuTreeSelectVo> roleMenuTreeselect(@PathVariable("roleId") Long roleId) {
+ List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
+ MenuTreeSelectVo selectVo = new MenuTreeSelectVo(
+ menuService.selectMenuListByRoleId(roleId),
+ menuService.buildMenuTreeSelect(menus));
+ return R.ok(selectVo);
+ }
+
+ /**
+ * 鍔犺浇瀵瑰簲绉熸埛濂楅鑿滃崟鍒楄〃鏍�
+ *
+ * @param packageId 绉熸埛濂楅ID
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:menu:query")
+ @GetMapping(value = "/tenantPackageMenuTreeselect/{packageId}")
+ public R<MenuTreeSelectVo> tenantPackageMenuTreeselect(@PathVariable("packageId") Long packageId) {
+ List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
+ List<Tree<Long>> list = menuService.buildMenuTreeSelect(menus);
+ // 鍒犻櫎绉熸埛绠$悊鑿滃崟
+ list.removeIf(menu -> menu.getId() == 6L);
+ List<Long> ids = new ArrayList<>();
+ if (packageId > 0L) {
+ ids = menuService.selectMenuListByPackageId(packageId);
+ }
+ MenuTreeSelectVo selectVo = new MenuTreeSelectVo(ids, list);
+ return R.ok(selectVo);
+ }
+
+ /**
+ * 鏂板鑿滃崟
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:menu:add")
+ @Log(title = "鑿滃崟绠$悊", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping
+ public R<Void> add(@Validated @RequestBody SysMenuBo menu) {
+ if (!menuService.checkMenuNameUnique(menu)) {
+ return R.fail("鏂板鑿滃崟'" + menu.getMenuName() + "'澶辫触锛岃彍鍗曞悕绉板凡瀛樺湪");
+ } else if (SystemConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
+ return R.fail("鏂板鑿滃崟'" + menu.getMenuName() + "'澶辫触锛屽湴鍧�蹇呴』浠ttp(s)://寮�澶�");
+ } else if (!menuService.checkRouteConfigUnique(menu)) {
+ return R.fail("鏂板鑿滃崟'" + menu.getMenuName() + "'澶辫触锛岃矾鐢卞悕绉版垨鍦板潃宸插瓨鍦�");
+ }
+ return toAjax(menuService.insertMenu(menu));
+ }
+
+ /**
+ * 淇敼鑿滃崟
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:menu:edit")
+ @Log(title = "鑿滃崟绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping
+ public R<Void> edit(@Validated @RequestBody SysMenuBo menu) {
+ if (!menuService.checkMenuNameUnique(menu)) {
+ return R.fail("淇敼鑿滃崟'" + menu.getMenuName() + "'澶辫触锛岃彍鍗曞悕绉板凡瀛樺湪");
+ } else if (SystemConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
+ return R.fail("淇敼鑿滃崟'" + menu.getMenuName() + "'澶辫触锛屽湴鍧�蹇呴』浠ttp(s)://寮�澶�");
+ } else if (menu.getMenuId().equals(menu.getParentId())) {
+ return R.fail("淇敼鑿滃崟'" + menu.getMenuName() + "'澶辫触锛屼笂绾ц彍鍗曚笉鑳介�夋嫨鑷繁");
+ } else if (!menuService.checkRouteConfigUnique(menu)) {
+ return R.fail("淇敼鑿滃崟'" + menu.getMenuName() + "'澶辫触锛岃矾鐢卞悕绉版垨鍦板潃宸插瓨鍦�");
+ }
+ return toAjax(menuService.updateMenu(menu));
+ }
+
+ /**
+ * 鍒犻櫎鑿滃崟
+ *
+ * @param menuId 鑿滃崟ID
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:menu:remove")
+ @Log(title = "鑿滃崟绠$悊", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{menuId}")
+ public R<Void> remove(@PathVariable("menuId") Long menuId) {
+ if (menuService.hasChildByMenuId(menuId)) {
+ return R.warn("瀛樺湪瀛愯彍鍗�,涓嶅厑璁稿垹闄�");
+ }
+ if (menuService.checkMenuExistRole(menuId)) {
+ return R.warn("鑿滃崟宸插垎閰�,涓嶅厑璁稿垹闄�");
+ }
+ return toAjax(menuService.deleteMenuById(menuId));
+ }
+
+ /**
+ * 瑙掕壊鑿滃崟鍒楄〃鏍戜俊鎭�
+ *
+ * @param checkedKeys 閫変腑鑿滃崟鍒楄〃
+ * @param menus 鑿滃崟涓嬫媺鏍戠粨鏋勫垪琛�
+ */
+ public record MenuTreeSelectVo(List<Long> checkedKeys, List<Tree<Long>> menus) {
+ }
+
+ /**
+ * 鎵归噺绾ц仈鍒犻櫎鑿滃崟
+ *
+ * @param menuIds 鑿滃崟ID涓�
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:menu:remove")
+ @Log(title = "鑿滃崟绠$悊", businessType = BusinessType.DELETE)
+ @DeleteMapping("/cascade/{menuIds}")
+ public R<Void> remove(@PathVariable("menuIds") Long[] menuIds) {
+ List<Long> menuIdList = List.of(menuIds);
+ if (menuService.hasChildByMenuId(menuIdList)) {
+ return R.warn("瀛樺湪瀛愯彍鍗�,涓嶅厑璁稿垹闄�");
+ }
+ menuService.deleteMenuById(menuIdList);
+ return R.ok();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java
new file mode 100755
index 0000000..86abb9a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java
@@ -0,0 +1,93 @@
+package org.dromara.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.sse.utils.SseMessageUtils;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysNoticeBo;
+import org.dromara.system.domain.vo.SysNoticeVo;
+import org.dromara.system.service.ISysNoticeService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 鍏憡 淇℃伅鎿嶄綔澶勭悊
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/notice")
+public class SysNoticeController extends BaseController {
+
+ private final ISysNoticeService noticeService;
+ private final DictService dictService;
+
+ /**
+ * 鑾峰彇閫氱煡鍏憡鍒楄〃
+ */
+ @SaCheckPermission("system:notice:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysNoticeVo> list(SysNoticeBo notice, PageQuery pageQuery) {
+ return noticeService.selectPageNoticeList(notice, pageQuery);
+ }
+
+ /**
+ * 鏍规嵁閫氱煡鍏憡缂栧彿鑾峰彇璇︾粏淇℃伅
+ *
+ * @param noticeId 鍏憡ID
+ */
+ @SaCheckPermission("system:notice:query")
+ @GetMapping(value = "/{noticeId}")
+ public R<SysNoticeVo> getInfo(@PathVariable Long noticeId) {
+ return R.ok(noticeService.selectNoticeById(noticeId));
+ }
+
+ /**
+ * 鏂板閫氱煡鍏憡
+ */
+ @SaCheckPermission("system:notice:add")
+ @Log(title = "閫氱煡鍏憡", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping
+ public R<Void> add(@Validated @RequestBody SysNoticeBo notice) {
+ int rows = noticeService.insertNotice(notice);
+ if (rows <= 0) {
+ return R.fail();
+ }
+ String type = dictService.getDictLabel("sys_notice_type", notice.getNoticeType());
+ SseMessageUtils.publishAll("[" + type + "] " + notice.getNoticeTitle());
+ return R.ok();
+ }
+
+ /**
+ * 淇敼閫氱煡鍏憡
+ */
+ @SaCheckPermission("system:notice:edit")
+ @Log(title = "閫氱煡鍏憡", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping
+ public R<Void> edit(@Validated @RequestBody SysNoticeBo notice) {
+ return toAjax(noticeService.updateNotice(notice));
+ }
+
+ /**
+ * 鍒犻櫎閫氱煡鍏憡
+ *
+ * @param noticeIds 鍏憡ID涓�
+ */
+ @SaCheckPermission("system:notice:remove")
+ @Log(title = "閫氱煡鍏憡", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{noticeIds}")
+ public R<Void> remove(@PathVariable Long[] noticeIds) {
+ return toAjax(noticeService.deleteNoticeByIds(noticeIds));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssConfigController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssConfigController.java
new file mode 100755
index 0000000..2369980
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssConfigController.java
@@ -0,0 +1,106 @@
+package org.dromara.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.core.validate.QueryGroup;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysOssConfigBo;
+import org.dromara.system.domain.vo.SysOssConfigVo;
+import org.dromara.system.service.ISysOssConfigService;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 瀵硅薄瀛樺偍閰嶇疆
+ *
+ * @author Lion Li
+ * @author 瀛よ垷鐑熼洦
+ * @date 2021-08-13
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/resource/oss/config")
+public class SysOssConfigController extends BaseController {
+
+ private final ISysOssConfigService ossConfigService;
+
+ /**
+ * 鏌ヨ瀵硅薄瀛樺偍閰嶇疆鍒楄〃
+ */
+ @SaCheckPermission("system:ossConfig:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo, PageQuery pageQuery) {
+ return ossConfigService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 鑾峰彇瀵硅薄瀛樺偍閰嶇疆璇︾粏淇℃伅
+ *
+ * @param ossConfigId OSS閰嶇疆ID
+ */
+ @SaCheckPermission("system:ossConfig:list")
+ @GetMapping("/{ossConfigId}")
+ public R<SysOssConfigVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long ossConfigId) {
+ return R.ok(ossConfigService.queryById(ossConfigId));
+ }
+
+ /**
+ * 鏂板瀵硅薄瀛樺偍閰嶇疆
+ */
+ @SaCheckPermission("system:ossConfig:add")
+ @Log(title = "瀵硅薄瀛樺偍閰嶇疆", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) {
+ return toAjax(ossConfigService.insertByBo(bo));
+ }
+
+ /**
+ * 淇敼瀵硅薄瀛樺偍閰嶇疆
+ */
+ @SaCheckPermission("system:ossConfig:edit")
+ @Log(title = "瀵硅薄瀛樺偍閰嶇疆", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) {
+ return toAjax(ossConfigService.updateByBo(bo));
+ }
+
+ /**
+ * 鍒犻櫎瀵硅薄瀛樺偍閰嶇疆
+ *
+ * @param ossConfigIds OSS閰嶇疆ID涓�
+ */
+ @SaCheckPermission("system:ossConfig:remove")
+ @Log(title = "瀵硅薄瀛樺偍閰嶇疆", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ossConfigIds}")
+ public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long[] ossConfigIds) {
+ return toAjax(ossConfigService.deleteWithValidByIds(List.of(ossConfigIds), true));
+ }
+
+ /**
+ * 鐘舵�佷慨鏀�
+ */
+ @SaCheckPermission("system:ossConfig:edit")
+ @Log(title = "瀵硅薄瀛樺偍鐘舵�佷慨鏀�", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping("/changeStatus")
+ public R<Void> changeStatus(@RequestBody SysOssConfigBo bo) {
+ return toAjax(ossConfigService.updateOssConfigStatus(bo));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java
new file mode 100755
index 0000000..35c1a89
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java
@@ -0,0 +1,104 @@
+package org.dromara.system.controller.system;
+
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.QueryGroup;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysOssBo;
+import org.dromara.system.domain.vo.SysOssUploadVo;
+import org.dromara.system.domain.vo.SysOssVo;
+import org.dromara.system.service.ISysOssService;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 鏂囦欢涓婁紶 鎺у埗灞�
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/resource/oss")
+public class SysOssController extends BaseController {
+
+ private final ISysOssService ossService;
+
+ /**
+ * 鏌ヨOSS瀵硅薄瀛樺偍鍒楄〃
+ */
+ @SaCheckPermission("system:oss:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo, PageQuery pageQuery) {
+ return ossService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 鏌ヨOSS瀵硅薄鍩轰簬id涓�
+ *
+ * @param ossIds OSS瀵硅薄ID涓�
+ */
+ @SaCheckPermission("system:oss:query")
+ @GetMapping("/listByIds/{ossIds}")
+ public R<List<SysOssVo>> listByIds(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long[] ossIds) {
+ List<SysOssVo> list = ossService.listByIds(Arrays.asList(ossIds));
+ return R.ok(list);
+ }
+
+ /**
+ * 涓婁紶OSS瀵硅薄瀛樺偍
+ *
+ * @param file 鏂囦欢
+ */
+ @SaCheckPermission("system:oss:upload")
+ @Log(title = "OSS瀵硅薄瀛樺偍", businessType = BusinessType.INSERT)
+ @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public R<SysOssUploadVo> upload(@RequestPart("file") MultipartFile file) {
+ SysOssVo oss = ossService.upload(file);
+ SysOssUploadVo uploadVo = new SysOssUploadVo();
+ uploadVo.setUrl(oss.getUrl());
+ uploadVo.setFileName(oss.getOriginalName());
+ uploadVo.setOssId(oss.getOssId().toString());
+ return R.ok(uploadVo);
+ }
+
+ /**
+ * 涓嬭浇OSS瀵硅薄
+ *
+ * @param ossId OSS瀵硅薄ID
+ */
+ @SaCheckPermission("system:oss:download")
+ @GetMapping("/download/{ossId}")
+ public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
+ ossService.download(ossId, response);
+ }
+
+ /**
+ * 鍒犻櫎OSS瀵硅薄瀛樺偍
+ *
+ * @param ossIds OSS瀵硅薄ID涓�
+ */
+ @SaCheckPermission("system:oss:remove")
+ @Log(title = "OSS瀵硅薄瀛樺偍", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ossIds}")
+ public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long[] ossIds) {
+ return toAjax(ossService.deleteWithValidByIds(List.of(ossIds), true));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java
new file mode 100755
index 0000000..0f1b80c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java
@@ -0,0 +1,151 @@
+package org.dromara.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysDeptBo;
+import org.dromara.system.domain.bo.SysPostBo;
+import org.dromara.system.domain.vo.SysPostVo;
+import org.dromara.system.service.ISysDeptService;
+import org.dromara.system.service.ISysPostService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 宀椾綅淇℃伅鎿嶄綔澶勭悊
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/post")
+public class SysPostController extends BaseController {
+
+ private final ISysPostService postService;
+ private final ISysDeptService deptService;
+
+ /**
+ * 鑾峰彇宀椾綅鍒楄〃
+ */
+ @SaCheckPermission("system:post:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysPostVo> list(SysPostBo post, PageQuery pageQuery) {
+ return postService.selectPagePostList(post, pageQuery);
+ }
+
+ /**
+ * 瀵煎嚭宀椾綅鍒楄〃
+ */
+ @Log(title = "宀椾綅绠$悊", businessType = BusinessType.EXPORT)
+ @SaCheckPermission("system:post:export")
+ @PostMapping("/export")
+ public void export(SysPostBo post, HttpServletResponse response) {
+ List<SysPostVo> list = postService.selectPostList(post);
+ ExcelUtil.exportExcel(list, "宀椾綅鏁版嵁", SysPostVo.class, response);
+ }
+
+ /**
+ * 鏍规嵁宀椾綅缂栧彿鑾峰彇璇︾粏淇℃伅
+ *
+ * @param postId 宀椾綅ID
+ */
+ @SaCheckPermission("system:post:query")
+ @GetMapping(value = "/{postId}")
+ public R<SysPostVo> getInfo(@PathVariable Long postId) {
+ return R.ok(postService.selectPostById(postId));
+ }
+
+ /**
+ * 鏂板宀椾綅
+ */
+ @SaCheckPermission("system:post:add")
+ @Log(title = "宀椾綅绠$悊", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping
+ public R<Void> add(@Validated @RequestBody SysPostBo post) {
+ if (!postService.checkPostNameUnique(post)) {
+ return R.fail("鏂板宀椾綅'" + post.getPostName() + "'澶辫触锛屽矖浣嶅悕绉板凡瀛樺湪");
+ } else if (!postService.checkPostCodeUnique(post)) {
+ return R.fail("鏂板宀椾綅'" + post.getPostName() + "'澶辫触锛屽矖浣嶇紪鐮佸凡瀛樺湪");
+ }
+ return toAjax(postService.insertPost(post));
+ }
+
+ /**
+ * 淇敼宀椾綅
+ */
+ @SaCheckPermission("system:post:edit")
+ @Log(title = "宀椾綅绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping
+ public R<Void> edit(@Validated @RequestBody SysPostBo post) {
+ if (!postService.checkPostNameUnique(post)) {
+ return R.fail("淇敼宀椾綅'" + post.getPostName() + "'澶辫触锛屽矖浣嶅悕绉板凡瀛樺湪");
+ } else if (!postService.checkPostCodeUnique(post)) {
+ return R.fail("淇敼宀椾綅'" + post.getPostName() + "'澶辫触锛屽矖浣嶇紪鐮佸凡瀛樺湪");
+ } else if (SystemConstants.DISABLE.equals(post.getStatus())
+ && postService.countUserPostById(post.getPostId()) > 0) {
+ return R.fail("璇ュ矖浣嶄笅瀛樺湪宸插垎閰嶇敤鎴凤紝涓嶈兘绂佺敤!");
+ }
+ return toAjax(postService.updatePost(post));
+ }
+
+ /**
+ * 鍒犻櫎宀椾綅
+ *
+ * @param postIds 宀椾綅ID涓�
+ */
+ @SaCheckPermission("system:post:remove")
+ @Log(title = "宀椾綅绠$悊", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{postIds}")
+ public R<Void> remove(@PathVariable Long[] postIds) {
+ return toAjax(postService.deletePostByIds(Arrays.asList(postIds)));
+ }
+
+ /**
+ * 鑾峰彇宀椾綅閫夋嫨妗嗗垪琛�
+ *
+ * @param postIds 宀椾綅ID涓�
+ * @param deptId 閮ㄩ棬id
+ */
+ @SaCheckPermission("system:post:query")
+ @GetMapping("/optionselect")
+ public R<List<SysPostVo>> optionselect(@RequestParam(required = false) Long[] postIds, @RequestParam(required = false) Long deptId) {
+ List<SysPostVo> list = new ArrayList<>();
+ if (ObjectUtil.isNotNull(deptId)) {
+ SysPostBo post = new SysPostBo();
+ post.setDeptId(deptId);
+ list = postService.selectPostList(post);
+ } else if (postIds != null) {
+ list = postService.selectPostByIds(List.of(postIds));
+ }
+ return R.ok(list);
+ }
+
+ /**
+ * 鑾峰彇閮ㄩ棬鏍戝垪琛�
+ */
+ @SaCheckPermission("system:post:list")
+ @GetMapping("/deptTree")
+ public R<List<Tree<Long>>> deptTree(SysDeptBo dept) {
+ return R.ok(deptService.selectDeptTreeList(dept));
+ }
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java
new file mode 100755
index 0000000..2edab3e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java
@@ -0,0 +1,149 @@
+package org.dromara.system.controller.system;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.crypto.digest.BCrypt;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.file.MimeTypeUtils;
+import org.dromara.common.encrypt.annotation.ApiEncrypt;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.helper.DataPermissionHelper;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysUserBo;
+import org.dromara.system.domain.bo.SysUserPasswordBo;
+import org.dromara.system.domain.bo.SysUserProfileBo;
+import org.dromara.system.domain.vo.ProfileUserVo;
+import org.dromara.system.domain.vo.SysOssVo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.service.ISysOssService;
+import org.dromara.system.service.ISysUserService;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Arrays;
+
+/**
+ * 涓汉淇℃伅 涓氬姟澶勭悊
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/user/profile")
+public class SysProfileController extends BaseController {
+
+ private final ISysUserService userService;
+ private final ISysOssService ossService;
+
+ /**
+ * 涓汉淇℃伅
+ */
+ @GetMapping
+ public R<ProfileVo> profile() {
+ SysUserVo user = userService.selectUserById(LoginHelper.getUserId());
+ String roleGroup = userService.selectUserRoleGroup(user.getUserId());
+ String postGroup = userService.selectUserPostGroup(user.getUserId());
+ // 鍗曠嫭鍋氫竴涓獀o涓撻棬缁欎釜浜轰腑蹇冪敤 閬垮厤鏁版嵁琚劚鏁�
+ ProfileUserVo profileUser = BeanUtil.toBean(user, ProfileUserVo.class);
+ ProfileVo profileVo = new ProfileVo(profileUser, roleGroup, postGroup);
+ return R.ok(profileVo);
+ }
+
+ /**
+ * 淇敼鐢ㄦ埛淇℃伅
+ */
+ @RepeatSubmit
+ @Log(title = "涓汉淇℃伅", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public R<Void> updateProfile(@Validated @RequestBody SysUserProfileBo profile) {
+ SysUserBo user = BeanUtil.toBean(profile, SysUserBo.class);
+ user.setUserId(LoginHelper.getUserId());
+ String username = LoginHelper.getUsername();
+ if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
+ return R.fail("淇敼鐢ㄦ埛'" + username + "'澶辫触锛屾墜鏈哄彿鐮佸凡瀛樺湪");
+ }
+ if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
+ return R.fail("淇敼鐢ㄦ埛'" + username + "'澶辫触锛岄偖绠辫处鍙峰凡瀛樺湪");
+ }
+ int rows = DataPermissionHelper.ignore(() -> userService.updateUserProfile(user));
+ if (rows > 0) {
+ return R.ok();
+ }
+ return R.fail("淇敼涓汉淇℃伅寮傚父锛岃鑱旂郴绠$悊鍛�");
+ }
+
+ /**
+ * 閲嶇疆瀵嗙爜
+ *
+ * @param bo 鏂版棫瀵嗙爜
+ */
+ @RepeatSubmit
+ @ApiEncrypt
+ @Log(title = "涓汉淇℃伅", businessType = BusinessType.UPDATE)
+ @PutMapping("/updatePwd")
+ public R<Void> updatePwd(@Validated @RequestBody SysUserPasswordBo bo) {
+ SysUserVo user = userService.selectUserById(LoginHelper.getUserId());
+ String password = user.getPassword();
+ if (!BCrypt.checkpw(bo.getOldPassword(), password)) {
+ return R.fail("淇敼瀵嗙爜澶辫触锛屾棫瀵嗙爜閿欒");
+ }
+ if (BCrypt.checkpw(bo.getNewPassword(), password)) {
+ return R.fail("鏂板瘑鐮佷笉鑳戒笌鏃у瘑鐮佺浉鍚�");
+ }
+ int rows = DataPermissionHelper.ignore(() -> userService.resetUserPwd(user.getUserId(), BCrypt.hashpw(bo.getNewPassword())));
+ if (rows > 0) {
+ return R.ok();
+ }
+ return R.fail("淇敼瀵嗙爜寮傚父锛岃鑱旂郴绠$悊鍛�");
+ }
+
+ /**
+ * 澶村儚涓婁紶
+ *
+ * @param avatarfile 鐢ㄦ埛澶村儚
+ */
+ @RepeatSubmit
+ @Log(title = "鐢ㄦ埛澶村儚", businessType = BusinessType.UPDATE)
+ @PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public R<AvatarVo> avatar(@RequestPart("avatarfile") MultipartFile avatarfile) {
+ if (ObjectUtil.isNotNull(avatarfile) && !avatarfile.isEmpty()) {
+ String extension = FileUtil.extName(avatarfile.getOriginalFilename());
+ if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
+ return R.fail("鏂囦欢鏍煎紡涓嶆纭紝璇蜂笂浼�" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "鏍煎紡");
+ }
+ SysOssVo oss = ossService.upload(avatarfile);
+ String avatar = oss.getUrl();
+ boolean updateSuccess = DataPermissionHelper.ignore(() -> userService.updateUserAvatar(LoginHelper.getUserId(), oss.getOssId()));
+ if (updateSuccess) {
+ return R.ok(new AvatarVo(avatar));
+ }
+ }
+ return R.fail("涓婁紶鍥剧墖寮傚父锛岃鑱旂郴绠$悊鍛�");
+ }
+
+ /**
+ * 鐢ㄦ埛澶村儚淇℃伅
+ *
+ * @param imgUrl 澶村儚鍦板潃
+ */
+ public record AvatarVo(String imgUrl) {}
+
+ /**
+ * 鐢ㄦ埛涓汉淇℃伅
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @param roleGroup 鐢ㄦ埛鎵�灞炶鑹茬粍
+ * @param postGroup 鐢ㄦ埛鎵�灞炲矖浣嶇粍
+ */
+ public record ProfileVo(ProfileUserVo user, String roleGroup, String postGroup) {}
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java
new file mode 100755
index 0000000..755a9ec
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java
@@ -0,0 +1,246 @@
+package org.dromara.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.lang.tree.Tree;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.SysUserRole;
+import org.dromara.system.domain.bo.SysDeptBo;
+import org.dromara.system.domain.bo.SysRoleBo;
+import org.dromara.system.domain.bo.SysUserBo;
+import org.dromara.system.domain.vo.SysRoleVo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.service.ISysDeptService;
+import org.dromara.system.service.ISysRoleService;
+import org.dromara.system.service.ISysUserService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 瑙掕壊淇℃伅
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/role")
+public class SysRoleController extends BaseController {
+
+ private final ISysRoleService roleService;
+ private final ISysUserService userService;
+ private final ISysDeptService deptService;
+
+ /**
+ * 鑾峰彇瑙掕壊淇℃伅鍒楄〃
+ */
+ @SaCheckPermission("system:role:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysRoleVo> list(SysRoleBo role, PageQuery pageQuery) {
+ return roleService.selectPageRoleList(role, pageQuery);
+ }
+
+ /**
+ * 瀵煎嚭瑙掕壊淇℃伅鍒楄〃
+ */
+ @Log(title = "瑙掕壊绠$悊", businessType = BusinessType.EXPORT)
+ @SaCheckPermission("system:role:export")
+ @PostMapping("/export")
+ public void export(SysRoleBo role, HttpServletResponse response) {
+ List<SysRoleVo> list = roleService.selectRoleList(role);
+ ExcelUtil.exportExcel(list, "瑙掕壊鏁版嵁", SysRoleVo.class, response);
+ }
+
+ /**
+ * 鏍规嵁瑙掕壊缂栧彿鑾峰彇璇︾粏淇℃伅
+ *
+ * @param roleId 瑙掕壊ID
+ */
+ @SaCheckPermission("system:role:query")
+ @GetMapping(value = "/{roleId}")
+ public R<SysRoleVo> getInfo(@PathVariable Long roleId) {
+ roleService.checkRoleDataScope(roleId);
+ return R.ok(roleService.selectRoleById(roleId));
+ }
+
+ /**
+ * 鏂板瑙掕壊
+ */
+ @SaCheckPermission("system:role:add")
+ @Log(title = "瑙掕壊绠$悊", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping
+ public R<Void> add(@Validated @RequestBody SysRoleBo role) {
+ roleService.checkRoleAllowed(role);
+ if (!roleService.checkRoleNameUnique(role)) {
+ return R.fail("鏂板瑙掕壊'" + role.getRoleName() + "'澶辫触锛岃鑹插悕绉板凡瀛樺湪");
+ } else if (!roleService.checkRoleKeyUnique(role)) {
+ return R.fail("鏂板瑙掕壊'" + role.getRoleName() + "'澶辫触锛岃鑹叉潈闄愬凡瀛樺湪");
+ }
+ return toAjax(roleService.insertRole(role));
+
+ }
+
+ /**
+ * 淇敼淇濆瓨瑙掕壊
+ */
+ @SaCheckPermission("system:role:edit")
+ @Log(title = "瑙掕壊绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping
+ public R<Void> edit(@Validated @RequestBody SysRoleBo role) {
+ roleService.checkRoleAllowed(role);
+ roleService.checkRoleDataScope(role.getRoleId());
+ if (!roleService.checkRoleNameUnique(role)) {
+ return R.fail("淇敼瑙掕壊'" + role.getRoleName() + "'澶辫触锛岃鑹插悕绉板凡瀛樺湪");
+ } else if (!roleService.checkRoleKeyUnique(role)) {
+ return R.fail("淇敼瑙掕壊'" + role.getRoleName() + "'澶辫触锛岃鑹叉潈闄愬凡瀛樺湪");
+ }
+
+ if (roleService.updateRole(role) > 0) {
+ roleService.cleanOnlineUserByRole(role.getRoleId());
+ return R.ok();
+ }
+ return R.fail("淇敼瑙掕壊'" + role.getRoleName() + "'澶辫触锛岃鑱旂郴绠$悊鍛�");
+ }
+
+ /**
+ * 淇敼淇濆瓨鏁版嵁鏉冮檺
+ */
+ @SaCheckPermission("system:role:edit")
+ @Log(title = "瑙掕壊绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping("/dataScope")
+ public R<Void> dataScope(@RequestBody SysRoleBo role) {
+ roleService.checkRoleAllowed(role);
+ roleService.checkRoleDataScope(role.getRoleId());
+ return toAjax(roleService.authDataScope(role));
+ }
+
+ /**
+ * 鐘舵�佷慨鏀�
+ */
+ @SaCheckPermission("system:role:edit")
+ @Log(title = "瑙掕壊绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping("/changeStatus")
+ public R<Void> changeStatus(@RequestBody SysRoleBo role) {
+ roleService.checkRoleAllowed(role);
+ roleService.checkRoleDataScope(role.getRoleId());
+ return toAjax(roleService.updateRoleStatus(role.getRoleId(), role.getStatus()));
+ }
+
+ /**
+ * 鍒犻櫎瑙掕壊
+ *
+ * @param roleIds 瑙掕壊ID涓�
+ */
+ @SaCheckPermission("system:role:remove")
+ @Log(title = "瑙掕壊绠$悊", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{roleIds}")
+ public R<Void> remove(@PathVariable Long[] roleIds) {
+ return toAjax(roleService.deleteRoleByIds(List.of(roleIds)));
+ }
+
+ /**
+ * 鑾峰彇瑙掕壊閫夋嫨妗嗗垪琛�
+ *
+ * @param roleIds 瑙掕壊ID涓�
+ */
+ @SaCheckPermission("system:role:query")
+ @GetMapping("/optionselect")
+ public R<List<SysRoleVo>> optionselect(@RequestParam(required = false) Long[] roleIds) {
+ return R.ok(roleService.selectRoleByIds(roleIds == null ? null : List.of(roleIds)));
+ }
+
+ /**
+ * 鏌ヨ宸插垎閰嶇敤鎴疯鑹插垪琛�
+ */
+ @SaCheckPermission("system:role:list")
+ @GetMapping("/authUser/allocatedList")
+ public TableDataInfo<SysUserVo> allocatedList(SysUserBo user, PageQuery pageQuery) {
+ return userService.selectAllocatedList(user, pageQuery);
+ }
+
+ /**
+ * 鏌ヨ鏈垎閰嶇敤鎴疯鑹插垪琛�
+ */
+ @SaCheckPermission("system:role:list")
+ @GetMapping("/authUser/unallocatedList")
+ public TableDataInfo<SysUserVo> unallocatedList(SysUserBo user, PageQuery pageQuery) {
+ return userService.selectUnallocatedList(user, pageQuery);
+ }
+
+ /**
+ * 鍙栨秷鎺堟潈鐢ㄦ埛
+ */
+ @SaCheckPermission("system:role:edit")
+ @Log(title = "瑙掕壊绠$悊", businessType = BusinessType.GRANT)
+ @RepeatSubmit()
+ @PutMapping("/authUser/cancel")
+ public R<Void> cancelAuthUser(@RequestBody SysUserRole userRole) {
+ return toAjax(roleService.deleteAuthUser(userRole));
+ }
+
+ /**
+ * 鎵归噺鍙栨秷鎺堟潈鐢ㄦ埛
+ *
+ * @param roleId 瑙掕壊ID
+ * @param userIds 鐢ㄦ埛ID涓�
+ */
+ @SaCheckPermission("system:role:edit")
+ @Log(title = "瑙掕壊绠$悊", businessType = BusinessType.GRANT)
+ @RepeatSubmit()
+ @PutMapping("/authUser/cancelAll")
+ public R<Void> cancelAuthUserAll(Long roleId, Long[] userIds) {
+ return toAjax(roleService.deleteAuthUsers(roleId, userIds));
+ }
+
+ /**
+ * 鎵归噺閫夋嫨鐢ㄦ埛鎺堟潈
+ *
+ * @param roleId 瑙掕壊ID
+ * @param userIds 鐢ㄦ埛ID涓�
+ */
+ @SaCheckPermission("system:role:edit")
+ @Log(title = "瑙掕壊绠$悊", businessType = BusinessType.GRANT)
+ @RepeatSubmit()
+ @PutMapping("/authUser/selectAll")
+ public R<Void> selectAuthUserAll(Long roleId, Long[] userIds) {
+ roleService.checkRoleDataScope(roleId);
+ return toAjax(roleService.insertAuthUsers(roleId, userIds));
+ }
+
+ /**
+ * 鑾峰彇瀵瑰簲瑙掕壊閮ㄩ棬鏍戝垪琛�
+ *
+ * @param roleId 瑙掕壊ID
+ */
+ @SaCheckPermission("system:role:list")
+ @GetMapping(value = "/deptTree/{roleId}")
+ public R<DeptTreeSelectVo> roleDeptTreeselect(@PathVariable("roleId") Long roleId) {
+ DeptTreeSelectVo selectVo = new DeptTreeSelectVo(
+ deptService.selectDeptListByRoleId(roleId),
+ deptService.selectDeptTreeList(new SysDeptBo()));
+ return R.ok(selectVo);
+ }
+
+ /**
+ * 瑙掕壊閮ㄩ棬鍒楄〃鏍戜俊鎭�
+ *
+ * @param checkedKeys 閫変腑閮ㄩ棬鍒楄〃
+ * @param depts 涓嬫媺鏍戠粨鏋勫垪琛�
+ */
+ public record DeptTreeSelectVo(List<Long> checkedKeys, List<Tree<Long>> depts) {}
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysSocialController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysSocialController.java
new file mode 100755
index 0000000..b0281cf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysSocialController.java
@@ -0,0 +1,38 @@
+package org.dromara.system.controller.system;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.vo.SysSocialVo;
+import org.dromara.system.service.ISysSocialService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 绀句細鍖栧叧绯�
+ *
+ * @author thiszhc
+ * @date 2023-06-16
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/social")
+public class SysSocialController extends BaseController {
+
+ private final ISysSocialService socialUserService;
+
+ /**
+ * 鏌ヨ绀句細鍖栧叧绯诲垪琛�
+ */
+ @GetMapping("/list")
+ public R<List<SysSocialVo>> list() {
+ return R.ok(socialUserService.queryListByUserId(LoginHelper.getUserId()));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantController.java
new file mode 100755
index 0000000..b29e681
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantController.java
@@ -0,0 +1,211 @@
+package org.dromara.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaCheckRole;
+import com.baomidou.lock.annotation.Lock4j;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.TenantConstants;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.encrypt.annotation.ApiEncrypt;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysTenantBo;
+import org.dromara.system.domain.vo.SysTenantVo;
+import org.dromara.system.service.ISysTenantService;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 绉熸埛绠$悊
+ *
+ * @author Michelle.Chung
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/tenant")
+@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
+public class SysTenantController extends BaseController {
+
+ private final ISysTenantService tenantService;
+
+ /**
+ * 鏌ヨ绉熸埛鍒楄〃
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenant:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysTenantVo> list(SysTenantBo bo, PageQuery pageQuery) {
+ return tenantService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 瀵煎嚭绉熸埛鍒楄〃
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenant:export")
+ @Log(title = "绉熸埛绠$悊", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(SysTenantBo bo, HttpServletResponse response) {
+ List<SysTenantVo> list = tenantService.queryList(bo);
+ ExcelUtil.exportExcel(list, "绉熸埛", SysTenantVo.class, response);
+ }
+
+ /**
+ * 鑾峰彇绉熸埛璇︾粏淇℃伅
+ *
+ * @param id 涓婚敭
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenant:query")
+ @GetMapping("/{id}")
+ public R<SysTenantVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long id) {
+ return R.ok(tenantService.queryById(id));
+ }
+
+ /**
+ * 鏂板绉熸埛
+ */
+ @ApiEncrypt
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenant:add")
+ @Log(title = "绉熸埛绠$悊", businessType = BusinessType.INSERT)
+ @Lock4j
+ @RepeatSubmit()
+ @PostMapping()
+ public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantBo bo) {
+ if (!tenantService.checkCompanyNameUnique(bo)) {
+ return R.fail("鏂板绉熸埛'" + bo.getCompanyName() + "'澶辫触锛屼紒涓氬悕绉板凡瀛樺湪");
+ }
+ return toAjax(TenantHelper.ignore(() -> tenantService.insertByBo(bo)));
+ }
+
+ /**
+ * 淇敼绉熸埛
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenant:edit")
+ @Log(title = "绉熸埛绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantBo bo) {
+ tenantService.checkTenantAllowed(bo.getTenantId());
+ if (!tenantService.checkCompanyNameUnique(bo)) {
+ return R.fail("淇敼绉熸埛'" + bo.getCompanyName() + "'澶辫触锛屽叕鍙稿悕绉板凡瀛樺湪");
+ }
+ return toAjax(tenantService.updateByBo(bo));
+ }
+
+ /**
+ * 鐘舵�佷慨鏀�
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenant:edit")
+ @Log(title = "绉熸埛绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping("/changeStatus")
+ public R<Void> changeStatus(@RequestBody SysTenantBo bo) {
+ tenantService.checkTenantAllowed(bo.getTenantId());
+ return toAjax(tenantService.updateTenantStatus(bo));
+ }
+
+ /**
+ * 鍒犻櫎绉熸埛
+ *
+ * @param ids 涓婚敭涓�
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenant:remove")
+ @Log(title = "绉熸埛绠$悊", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long[] ids) {
+ return toAjax(tenantService.deleteWithValidByIds(List.of(ids), true));
+ }
+
+ /**
+ * 鍔ㄦ�佸垏鎹㈢鎴�
+ *
+ * @param tenantId 绉熸埛ID
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @GetMapping("/dynamic/{tenantId}")
+ public R<Void> dynamicTenant(@NotBlank(message = "绉熸埛ID涓嶈兘涓虹┖") @PathVariable String tenantId) {
+ TenantHelper.setDynamic(tenantId, true);
+ return R.ok();
+ }
+
+ /**
+ * 娓呴櫎鍔ㄦ�佺鎴�
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @GetMapping("/dynamic/clear")
+ public R<Void> dynamicClear() {
+ TenantHelper.clearDynamic();
+ return R.ok();
+ }
+
+
+ /**
+ * 鍚屾绉熸埛濂楅
+ *
+ * @param tenantId 绉熸埛id
+ * @param packageId 濂楅id
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenant:edit")
+ @Log(title = "绉熸埛绠$悊", businessType = BusinessType.UPDATE)
+ @Lock4j
+ @GetMapping("/syncTenantPackage")
+ public R<Void> syncTenantPackage(@NotBlank(message = "绉熸埛ID涓嶈兘涓虹┖") String tenantId,
+ @NotNull(message = "濂楅ID涓嶈兘涓虹┖") Long packageId) {
+ return toAjax(TenantHelper.ignore(() -> tenantService.syncTenantPackage(tenantId, packageId)));
+ }
+
+ /**
+ * 鍚屾绉熸埛瀛楀吀
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @Log(title = "绉熸埛绠$悊", businessType = BusinessType.INSERT)
+ @Lock4j
+ @GetMapping("/syncTenantDict")
+ public R<Void> syncTenantDict() {
+ if (!TenantHelper.isEnable()) {
+ return R.fail("褰撳墠鏈紑鍚鎴锋ā寮�");
+ }
+ tenantService.syncTenantDict();
+ return R.ok("鍚屾绉熸埛瀛楀吀鎴愬姛");
+ }
+
+ /**
+ * 鍚屾绉熸埛鍙傛暟閰嶇疆
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @Log(title = "绉熸埛绠$悊", businessType = BusinessType.INSERT)
+ @Lock4j
+ @GetMapping("/syncTenantConfig")
+ public R<Void> syncTenantConfig() {
+ if (!TenantHelper.isEnable()) {
+ return R.fail("褰撳墠鏈紑鍚鎴锋ā寮�");
+ }
+ tenantService.syncTenantConfig();
+ return R.ok("鍚屾绉熸埛鍙傛暟閰嶇疆鎴愬姛");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantPackageController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantPackageController.java
new file mode 100755
index 0000000..0f724be
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantPackageController.java
@@ -0,0 +1,143 @@
+package org.dromara.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaCheckRole;
+import org.dromara.common.core.constant.TenantConstants;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysTenantPackageBo;
+import org.dromara.system.domain.vo.SysTenantPackageVo;
+import org.dromara.system.service.ISysTenantPackageService;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 绉熸埛濂楅绠$悊
+ *
+ * @author Michelle.Chung
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/tenant/package")
+@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
+public class SysTenantPackageController extends BaseController {
+
+ private final ISysTenantPackageService tenantPackageService;
+
+ /**
+ * 鏌ヨ绉熸埛濂楅鍒楄〃
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenantPackage:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysTenantPackageVo> list(SysTenantPackageBo bo, PageQuery pageQuery) {
+ return tenantPackageService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 鏌ヨ绉熸埛濂楅涓嬫媺閫夊垪琛�
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenantPackage:list")
+ @GetMapping("/selectList")
+ public R<List<SysTenantPackageVo>> selectList() {
+ return R.ok(tenantPackageService.selectList());
+ }
+
+ /**
+ * 瀵煎嚭绉熸埛濂楅鍒楄〃
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenantPackage:export")
+ @Log(title = "绉熸埛濂楅", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(SysTenantPackageBo bo, HttpServletResponse response) {
+ List<SysTenantPackageVo> list = tenantPackageService.queryList(bo);
+ ExcelUtil.exportExcel(list, "绉熸埛濂楅", SysTenantPackageVo.class, response);
+ }
+
+ /**
+ * 鑾峰彇绉熸埛濂楅璇︾粏淇℃伅
+ *
+ * @param packageId 涓婚敭
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenantPackage:query")
+ @GetMapping("/{packageId}")
+ public R<SysTenantPackageVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long packageId) {
+ return R.ok(tenantPackageService.queryById(packageId));
+ }
+
+ /**
+ * 鏂板绉熸埛濂楅
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenantPackage:add")
+ @Log(title = "绉熸埛濂楅", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantPackageBo bo) {
+ if (!tenantPackageService.checkPackageNameUnique(bo)) {
+ return R.fail("鏂板濂楅'" + bo.getPackageName() + "'澶辫触锛屽椁愬悕绉板凡瀛樺湪");
+ }
+ return toAjax(tenantPackageService.insertByBo(bo));
+ }
+
+ /**
+ * 淇敼绉熸埛濂楅
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenantPackage:edit")
+ @Log(title = "绉熸埛濂楅", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantPackageBo bo) {
+ if (!tenantPackageService.checkPackageNameUnique(bo)) {
+ return R.fail("淇敼濂楅'" + bo.getPackageName() + "'澶辫触锛屽椁愬悕绉板凡瀛樺湪");
+ }
+ return toAjax(tenantPackageService.updateByBo(bo));
+ }
+
+ /**
+ * 鐘舵�佷慨鏀�
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenantPackage:edit")
+ @Log(title = "绉熸埛濂楅", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping("/changeStatus")
+ public R<Void> changeStatus(@RequestBody SysTenantPackageBo bo) {
+ return toAjax(tenantPackageService.updatePackageStatus(bo));
+ }
+
+ /**
+ * 鍒犻櫎绉熸埛濂楅
+ *
+ * @param packageIds 涓婚敭涓�
+ */
+ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+ @SaCheckPermission("system:tenantPackage:remove")
+ @Log(title = "绉熸埛濂楅", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{packageIds}")
+ public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long[] packageIds) {
+ return toAjax(tenantPackageService.deleteWithValidByIds(List.of(packageIds), true));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java
new file mode 100755
index 0000000..2847fc2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java
@@ -0,0 +1,308 @@
+package org.dromara.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.crypto.digest.BCrypt;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.annotation.ApiEncrypt;
+import org.dromara.common.excel.core.ExcelResult;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.helper.DataPermissionHelper;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysDeptBo;
+import org.dromara.system.domain.bo.SysPostBo;
+import org.dromara.system.domain.bo.SysRoleBo;
+import org.dromara.system.domain.bo.SysUserBo;
+import org.dromara.system.domain.vo.*;
+import org.dromara.system.listener.SysUserImportListener;
+import org.dromara.system.service.*;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鐢ㄦ埛淇℃伅
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/user")
+public class SysUserController extends BaseController {
+
+ private final ISysUserService userService;
+ private final ISysRoleService roleService;
+ private final ISysPostService postService;
+ private final ISysDeptService deptService;
+ private final ISysTenantService tenantService;
+
+ /**
+ * 鑾峰彇鐢ㄦ埛鍒楄〃
+ */
+ @SaCheckPermission("system:user:list")
+ @GetMapping("/list")
+ public TableDataInfo<SysUserVo> list(SysUserBo user, PageQuery pageQuery) {
+ return userService.selectPageUserList(user, pageQuery);
+ }
+
+ /**
+ * 瀵煎嚭鐢ㄦ埛鍒楄〃
+ */
+ @Log(title = "鐢ㄦ埛绠$悊", businessType = BusinessType.EXPORT)
+ @SaCheckPermission("system:user:export")
+ @PostMapping("/export")
+ public void export(SysUserBo user, HttpServletResponse response) {
+ List<SysUserExportVo> list = userService.selectUserExportList(user);
+ ExcelUtil.exportExcel(list, "鐢ㄦ埛鏁版嵁", SysUserExportVo.class, response);
+ }
+
+ /**
+ * 瀵煎叆鏁版嵁
+ *
+ * @param file 瀵煎叆鏂囦欢
+ * @param updateSupport 鏄惁鏇存柊宸插瓨鍦ㄦ暟鎹�
+ */
+ @Log(title = "鐢ㄦ埛绠$悊", businessType = BusinessType.IMPORT)
+ @SaCheckPermission("system:user:import")
+ @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public R<Void> importData(@RequestPart("file") MultipartFile file, boolean updateSupport) throws Exception {
+ ExcelResult<SysUserImportVo> result = ExcelUtil.importExcel(file.getInputStream(), SysUserImportVo.class, new SysUserImportListener(updateSupport));
+ return R.ok(result.getAnalysis());
+ }
+
+ /**
+ * 鑾峰彇瀵煎叆妯℃澘
+ */
+ @PostMapping("/importTemplate")
+ public void importTemplate(HttpServletResponse response) {
+ ExcelUtil.exportExcel(new ArrayList<>(), "鐢ㄦ埛鏁版嵁", SysUserImportVo.class, response);
+ }
+
+ /**
+ * 鑾峰彇鐢ㄦ埛淇℃伅
+ *
+ * @return 鐢ㄦ埛淇℃伅
+ */
+ @GetMapping("/getInfo")
+ public R<UserInfoVo> getInfo() {
+ UserInfoVo userInfoVo = new UserInfoVo();
+ LoginUser loginUser = LoginHelper.getLoginUser();
+ if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
+ // 瓒呯骇绠$悊鍛� 濡傛灉閲嶆柊鍔犺浇鐢ㄦ埛淇℃伅闇�娓呴櫎鍔ㄦ�佺鎴�
+ TenantHelper.clearDynamic();
+ }
+
+ SysUserVo user = DataPermissionHelper.ignore(() -> userService.selectUserById(loginUser.getUserId()));
+ if (ObjectUtil.isNull(user)) {
+ return R.fail("娌℃湁鏉冮檺璁块棶鐢ㄦ埛鏁版嵁!");
+ }
+ userInfoVo.setUser(user);
+ userInfoVo.setPermissions(loginUser.getMenuPermission());
+ userInfoVo.setRoles(loginUser.getRolePermission());
+ return R.ok(userInfoVo);
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛缂栧彿鑾峰彇璇︾粏淇℃伅
+ *
+ * @param userId 鐢ㄦ埛ID
+ */
+ @SaCheckPermission("system:user:query")
+ @GetMapping(value = {"/", "/{userId}"})
+ public R<SysUserInfoVo> getInfo(@PathVariable(value = "userId", required = false) Long userId) {
+ SysUserInfoVo userInfoVo = new SysUserInfoVo();
+ if (ObjectUtil.isNotNull(userId)) {
+ userService.checkUserDataScope(userId);
+ SysUserVo sysUser = userService.selectUserById(userId);
+ userInfoVo.setUser(sysUser);
+ userInfoVo.setRoleIds(roleService.selectRoleListByUserId(userId));
+ Long deptId = sysUser.getDeptId();
+ if (ObjectUtil.isNotNull(deptId)) {
+ SysPostBo postBo = new SysPostBo();
+ postBo.setDeptId(deptId);
+ userInfoVo.setPosts(postService.selectPostList(postBo));
+ userInfoVo.setPostIds(postService.selectPostListByUserId(userId));
+ }
+ }
+ SysRoleBo roleBo = new SysRoleBo();
+ roleBo.setStatus(SystemConstants.NORMAL);
+ List<SysRoleVo> roles = roleService.selectRoleList(roleBo);
+ userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin()));
+ return R.ok(userInfoVo);
+ }
+
+ /**
+ * 鏂板鐢ㄦ埛
+ */
+ @SaCheckPermission("system:user:add")
+ @Log(title = "鐢ㄦ埛绠$悊", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping
+ public R<Void> add(@Validated @RequestBody SysUserBo user) {
+ deptService.checkDeptDataScope(user.getDeptId());
+ if (!userService.checkUserNameUnique(user)) {
+ return R.fail("鏂板鐢ㄦ埛'" + user.getUserName() + "'澶辫触锛岀櫥褰曡处鍙峰凡瀛樺湪");
+ } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
+ return R.fail("鏂板鐢ㄦ埛'" + user.getUserName() + "'澶辫触锛屾墜鏈哄彿鐮佸凡瀛樺湪");
+ } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
+ return R.fail("鏂板鐢ㄦ埛'" + user.getUserName() + "'澶辫触锛岄偖绠辫处鍙峰凡瀛樺湪");
+ }
+ if (TenantHelper.isEnable()) {
+ if (!tenantService.checkAccountBalance(TenantHelper.getTenantId())) {
+ return R.fail("褰撳墠绉熸埛涓嬬敤鎴峰悕棰濅笉瓒筹紝璇疯仈绯荤鐞嗗憳");
+ }
+ }
+ user.setPassword(BCrypt.hashpw(user.getPassword()));
+ return toAjax(userService.insertUser(user));
+ }
+
+ /**
+ * 淇敼鐢ㄦ埛
+ */
+ @SaCheckPermission("system:user:edit")
+ @Log(title = "鐢ㄦ埛绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping
+ public R<Void> edit(@Validated @RequestBody SysUserBo user) {
+ userService.checkUserAllowed(user.getUserId());
+ userService.checkUserDataScope(user.getUserId());
+ deptService.checkDeptDataScope(user.getDeptId());
+ if (!userService.checkUserNameUnique(user)) {
+ return R.fail("淇敼鐢ㄦ埛'" + user.getUserName() + "'澶辫触锛岀櫥褰曡处鍙峰凡瀛樺湪");
+ } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
+ return R.fail("淇敼鐢ㄦ埛'" + user.getUserName() + "'澶辫触锛屾墜鏈哄彿鐮佸凡瀛樺湪");
+ } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
+ return R.fail("淇敼鐢ㄦ埛'" + user.getUserName() + "'澶辫触锛岄偖绠辫处鍙峰凡瀛樺湪");
+ }
+ return toAjax(userService.updateUser(user));
+ }
+
+ /**
+ * 鍒犻櫎鐢ㄦ埛
+ *
+ * @param userIds 瑙掕壊ID涓�
+ */
+ @SaCheckPermission("system:user:remove")
+ @Log(title = "鐢ㄦ埛绠$悊", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{userIds}")
+ public R<Void> remove(@PathVariable Long[] userIds) {
+ if (ArrayUtil.contains(userIds, LoginHelper.getUserId())) {
+ return R.fail("褰撳墠鐢ㄦ埛涓嶈兘鍒犻櫎");
+ }
+ return toAjax(userService.deleteUserByIds(userIds));
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID涓叉壒閲忚幏鍙栫敤鎴峰熀纭�淇℃伅
+ *
+ * @param userIds 鐢ㄦ埛ID涓�
+ * @param deptId 閮ㄩ棬ID
+ */
+ @SaCheckPermission("system:user:query")
+ @GetMapping("/optionselect")
+ public R<List<SysUserVo>> optionselect(@RequestParam(required = false) Long[] userIds,
+ @RequestParam(required = false) Long deptId) {
+ return R.ok(userService.selectUserByIds(ArrayUtil.isEmpty(userIds) ? null : List.of(userIds), deptId));
+ }
+
+ /**
+ * 閲嶇疆瀵嗙爜
+ */
+ @ApiEncrypt
+ @SaCheckPermission("system:user:resetPwd")
+ @Log(title = "鐢ㄦ埛绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping("/resetPwd")
+ public R<Void> resetPwd(@RequestBody SysUserBo user) {
+ userService.checkUserAllowed(user.getUserId());
+ userService.checkUserDataScope(user.getUserId());
+ user.setPassword(BCrypt.hashpw(user.getPassword()));
+ return toAjax(userService.resetUserPwd(user.getUserId(), user.getPassword()));
+ }
+
+ /**
+ * 鐘舵�佷慨鏀�
+ */
+ @SaCheckPermission("system:user:edit")
+ @Log(title = "鐢ㄦ埛绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping("/changeStatus")
+ public R<Void> changeStatus(@RequestBody SysUserBo user) {
+ userService.checkUserAllowed(user.getUserId());
+ userService.checkUserDataScope(user.getUserId());
+ return toAjax(userService.updateUserStatus(user.getUserId(), user.getStatus()));
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛缂栧彿鑾峰彇鎺堟潈瑙掕壊
+ *
+ * @param userId 鐢ㄦ埛ID
+ */
+ @SaCheckPermission("system:user:query")
+ @GetMapping("/authRole/{userId}")
+ public R<SysUserInfoVo> authRole(@PathVariable Long userId) {
+ userService.checkUserDataScope(userId);
+ SysUserVo user = userService.selectUserById(userId);
+ List<SysRoleVo> roles = roleService.selectRolesAuthByUserId(userId);
+ SysUserInfoVo userInfoVo = new SysUserInfoVo();
+ userInfoVo.setUser(user);
+ userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin()));
+ return R.ok(userInfoVo);
+ }
+
+ /**
+ * 鐢ㄦ埛鎺堟潈瑙掕壊
+ *
+ * @param userId 鐢ㄦ埛Id
+ * @param roleIds 瑙掕壊ID涓�
+ */
+ @SaCheckPermission("system:user:edit")
+ @Log(title = "鐢ㄦ埛绠$悊", businessType = BusinessType.GRANT)
+ @RepeatSubmit()
+ @PutMapping("/authRole")
+ public R<Void> insertAuthRole(Long userId, Long[] roleIds) {
+ userService.checkUserDataScope(userId);
+ userService.insertUserAuth(userId, roleIds);
+ return R.ok();
+ }
+
+ /**
+ * 鑾峰彇閮ㄩ棬鏍戝垪琛�
+ */
+ @SaCheckPermission("system:user:list")
+ @GetMapping("/deptTree")
+ public R<List<Tree<Long>>> deptTree(SysDeptBo dept) {
+ return R.ok(deptService.selectDeptTreeList(dept));
+ }
+
+ /**
+ * 鑾峰彇閮ㄩ棬涓嬬殑鎵�鏈夌敤鎴蜂俊鎭�
+ */
+ @SaCheckPermission("system:user:list")
+ @GetMapping("/list/dept/{deptId}")
+ public R<List<SysUserVo>> listByDept(@PathVariable @NotNull Long deptId) {
+ return R.ok(userService.selectUserListByDept(deptId));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysCache.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysCache.java
new file mode 100755
index 0000000..e398a20
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysCache.java
@@ -0,0 +1,47 @@
+package org.dromara.system.domain;
+
+import org.dromara.common.core.utils.StringUtils;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 缂撳瓨淇℃伅
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class SysCache {
+
+ /**
+ * 缂撳瓨鍚嶇О
+ */
+ private String cacheName = "";
+
+ /**
+ * 缂撳瓨閿悕
+ */
+ private String cacheKey = "";
+
+ /**
+ * 缂撳瓨鍐呭
+ */
+ private String cacheValue = "";
+
+ /**
+ * 澶囨敞
+ */
+ private String remark = "";
+
+ public SysCache(String cacheName, String remark) {
+ this.cacheName = cacheName;
+ this.remark = remark;
+ }
+
+ public SysCache(String cacheName, String cacheKey, String cacheValue) {
+ this.cacheName = StringUtils.replace(cacheName, ":", "");
+ this.cacheKey = StringUtils.replace(cacheKey, cacheName, "");
+ this.cacheValue = cacheValue;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysClient.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysClient.java
new file mode 100755
index 0000000..ee2475d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysClient.java
@@ -0,0 +1,77 @@
+package org.dromara.system.domain;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 鎺堟潈绠$悊瀵硅薄 sys_client
+ *
+ * @author Michelle.Chung
+ * @date 2023-05-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_client")
+public class SysClient extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * id
+ */
+ @TableId(value = "id")
+ private Long id;
+
+ /**
+ * 瀹㈡埛绔痠d
+ */
+ private String clientId;
+
+ /**
+ * 瀹㈡埛绔痥ey
+ */
+ private String clientKey;
+
+ /**
+ * 瀹㈡埛绔閽�
+ */
+ private String clientSecret;
+
+ /**
+ * 鎺堟潈绫诲瀷
+ */
+ private String grantType;
+
+ /**
+ * 璁惧绫诲瀷
+ */
+ private String deviceType;
+
+ /**
+ * token娲昏穬瓒呮椂鏃堕棿
+ */
+ private Long activeTimeout;
+
+ /**
+ * token鍥哄畾瓒呮椂鏃堕棿
+ */
+ private Long timeout;
+
+ /**
+ * 鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�
+ */
+ @TableLogic
+ private String delFlag;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysConfig.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysConfig.java
new file mode 100755
index 0000000..6fcb88f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysConfig.java
@@ -0,0 +1,51 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.dromara.common.tenant.core.TenantEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 鍙傛暟閰嶇疆琛� sys_config
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_config")
+public class SysConfig extends TenantEntity {
+
+ /**
+ * 鍙傛暟涓婚敭
+ */
+ @TableId(value = "config_id")
+ private Long configId;
+
+ /**
+ * 鍙傛暟鍚嶇О
+ */
+ private String configName;
+
+ /**
+ * 鍙傛暟閿悕
+ */
+ private String configKey;
+
+ /**
+ * 鍙傛暟閿��
+ */
+ private String configValue;
+
+ /**
+ * 绯荤粺鍐呯疆锛圷鏄� N鍚︼級
+ */
+ private String configType;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDept.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDept.java
new file mode 100755
index 0000000..d436a1c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDept.java
@@ -0,0 +1,92 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 閮ㄩ棬琛� sys_dept
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_dept")
+public class SysDept extends TenantEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ @TableId(value = "dept_id")
+ private Long deptId;
+
+ /**
+ * 鐖堕儴闂↖D
+ */
+ private Long parentId;
+
+ /**
+ * 閮ㄩ棬鍚嶇О
+ */
+ private String deptName;
+
+ /**
+ * 閮ㄩ棬绫诲埆缂栫爜
+ */
+ private String deptCategory;
+
+ /**
+ * 鏄剧ず椤哄簭
+ */
+ private Integer orderNum;
+
+ /**
+ * 璐熻矗浜�
+ */
+ private Long leader;
+
+ /**
+ * 鑱旂郴鐢佃瘽
+ */
+ private String phone;
+
+ /**
+ * 閭
+ */
+ private String email;
+
+ /**
+ * 閮ㄩ棬鐘舵��:0姝e父,1鍋滅敤
+ */
+ private String status;
+
+ /**
+ * 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�
+ */
+ @TableLogic
+ private String delFlag;
+
+ /**
+ * 绁栫骇鍒楄〃
+ */
+ private String ancestors;
+
+ /**
+ * 瀛愰儴闂�
+ */
+ @TableField(exist = false)
+ private List<SysDept> children = new ArrayList<>();
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDictData.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDictData.java
new file mode 100755
index 0000000..9d83736
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDictData.java
@@ -0,0 +1,71 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.tenant.core.TenantEntity;
+
+/**
+ * 瀛楀吀鏁版嵁琛� sys_dict_data
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_dict_data")
+public class SysDictData extends TenantEntity {
+
+ /**
+ * 瀛楀吀缂栫爜
+ */
+ @TableId(value = "dict_code")
+ private Long dictCode;
+
+ /**
+ * 瀛楀吀鎺掑簭
+ */
+ private Integer dictSort;
+
+ /**
+ * 瀛楀吀鏍囩
+ */
+ private String dictLabel;
+
+ /**
+ * 瀛楀吀閿��
+ */
+ private String dictValue;
+
+ /**
+ * 瀛楀吀绫诲瀷
+ */
+ private String dictType;
+
+ /**
+ * 鏍峰紡灞炴�э紙鍏朵粬鏍峰紡鎵╁睍锛�
+ */
+ private String cssClass;
+
+ /**
+ * 琛ㄦ牸瀛楀吀鏍峰紡
+ */
+ private String listClass;
+
+ /**
+ * 鏄惁榛樿锛圷鏄� N鍚︼級
+ */
+ private String isDefault;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ public boolean getDefault() {
+ return SystemConstants.YES.equals(this.isDefault);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDictType.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDictType.java
new file mode 100755
index 0000000..955af85
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDictType.java
@@ -0,0 +1,41 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.dromara.common.tenant.core.TenantEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 瀛楀吀绫诲瀷琛� sys_dict_type
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_dict_type")
+public class SysDictType extends TenantEntity {
+
+ /**
+ * 瀛楀吀涓婚敭
+ */
+ @TableId(value = "dict_id")
+ private Long dictId;
+
+ /**
+ * 瀛楀吀鍚嶇О
+ */
+ private String dictName;
+
+ /**
+ * 瀛楀吀绫诲瀷
+ */
+ private String dictType;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysLogininfor.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysLogininfor.java
new file mode 100755
index 0000000..c57dc0a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysLogininfor.java
@@ -0,0 +1,85 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 绯荤粺璁块棶璁板綍琛� sys_logininfor
+ *
+ * @author Lion Li
+ */
+
+@Data
+@TableName("sys_logininfor")
+public class SysLogininfor implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * ID
+ */
+ @TableId(value = "info_id")
+ private Long infoId;
+
+ /**
+ * 绉熸埛缂栧彿
+ */
+ private String tenantId;
+
+ /**
+ * 鐢ㄦ埛璐﹀彿
+ */
+ private String userName;
+
+ /**
+ * 瀹㈡埛绔�
+ */
+ private String clientKey;
+
+ /**
+ * 璁惧绫诲瀷
+ */
+ private String deviceType;
+
+ /**
+ * 鐧诲綍鐘舵�� 0鎴愬姛 1澶辫触
+ */
+ private String status;
+
+ /**
+ * 鐧诲綍IP鍦板潃
+ */
+ private String ipaddr;
+
+ /**
+ * 鐧诲綍鍦扮偣
+ */
+ private String loginLocation;
+
+ /**
+ * 娴忚鍣ㄧ被鍨�
+ */
+ private String browser;
+
+ /**
+ * 鎿嶄綔绯荤粺
+ */
+ private String os;
+
+ /**
+ * 鎻愮ず娑堟伅
+ */
+ private String msg;
+
+ /**
+ * 璁块棶鏃堕棿
+ */
+ private Date loginTime;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMenu.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMenu.java
new file mode 100755
index 0000000..5fe0de5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMenu.java
@@ -0,0 +1,191 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鑿滃崟鏉冮檺琛� sys_menu
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_menu")
+public class SysMenu extends BaseEntity {
+
+ /**
+ * 鑿滃崟ID
+ */
+ @TableId(value = "menu_id")
+ private Long menuId;
+
+ /**
+ * 鐖惰彍鍗旾D
+ */
+ private Long parentId;
+
+ /**
+ * 鑿滃崟鍚嶇О
+ */
+ private String menuName;
+
+ /**
+ * 鏄剧ず椤哄簭
+ */
+ private Integer orderNum;
+
+ /**
+ * 璺敱鍦板潃
+ */
+ private String path;
+
+ /**
+ * 缁勪欢璺緞
+ */
+ private String component;
+
+ /**
+ * 璺敱鍙傛暟
+ */
+ private String queryParam;
+
+ /**
+ * 鏄惁涓哄閾撅紙0鏄� 1鍚︼級
+ */
+ private String isFrame;
+
+ /**
+ * 鏄惁缂撳瓨锛�0缂撳瓨 1涓嶇紦瀛橈級
+ */
+ private String isCache;
+
+ /**
+ * 绫诲瀷锛圡鐩綍 C鑿滃崟 F鎸夐挳锛�
+ */
+ private String menuType;
+
+ /**
+ * 鏄剧ず鐘舵�侊紙0鏄剧ず 1闅愯棌锛�
+ */
+ private String visible;
+
+ /**
+ * 鑿滃崟鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 鏉冮檺瀛楃涓�
+ */
+ private String perms;
+
+ /**
+ * 鑿滃崟鍥炬爣
+ */
+ private String icon;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鐖惰彍鍗曞悕绉�
+ */
+ @TableField(exist = false)
+ private String parentName;
+
+ /**
+ * 瀛愯彍鍗�
+ */
+ @TableField(exist = false)
+ private List<SysMenu> children = new ArrayList<>();
+
+ /**
+ * 鑾峰彇璺敱鍚嶇О
+ */
+ public String getRouteName() {
+ String routerName = StringUtils.capitalize(path);
+ // 闈炲閾惧苟涓旀槸涓�绾х洰褰曪紙绫诲瀷涓虹洰褰曪級
+ if (isMenuFrame()) {
+ routerName = StringUtils.EMPTY;
+ }
+ return routerName;
+ }
+
+ /**
+ * 鑾峰彇璺敱鍦板潃
+ */
+ public String getRouterPath() {
+ String routerPath = this.path;
+ // 鍐呴摼鎵撳紑澶栫綉鏂瑰紡
+ if (!Constants.TOP_PARENT_ID.equals(getParentId()) && isInnerLink()) {
+ routerPath = innerLinkReplaceEach(routerPath);
+ }
+ // 闈炲閾惧苟涓旀槸涓�绾х洰褰曪紙绫诲瀷涓虹洰褰曪級
+ if (Constants.TOP_PARENT_ID.equals(getParentId()) && SystemConstants.TYPE_DIR.equals(getMenuType())
+ && SystemConstants.NO_FRAME.equals(getIsFrame())) {
+ routerPath = "/" + this.path;
+ }
+ // 闈炲閾惧苟涓旀槸涓�绾х洰褰曪紙绫诲瀷涓鸿彍鍗曪級
+ else if (isMenuFrame()) {
+ routerPath = "/";
+ }
+ return routerPath;
+ }
+
+ /**
+ * 鑾峰彇缁勪欢淇℃伅
+ */
+ public String getComponentInfo() {
+ String component = SystemConstants.LAYOUT;
+ if (StringUtils.isNotEmpty(this.component) && !isMenuFrame()) {
+ component = this.component;
+ } else if (StringUtils.isEmpty(this.component) && !Constants.TOP_PARENT_ID.equals(getParentId()) && isInnerLink()) {
+ component = SystemConstants.INNER_LINK;
+ } else if (StringUtils.isEmpty(this.component) && isParentView()) {
+ component = SystemConstants.PARENT_VIEW;
+ }
+ return component;
+ }
+
+ /**
+ * 鏄惁涓鸿彍鍗曞唴閮ㄨ烦杞�
+ */
+ public boolean isMenuFrame() {
+ return Constants.TOP_PARENT_ID.equals(getParentId()) && SystemConstants.TYPE_MENU.equals(menuType) && isFrame.equals(SystemConstants.NO_FRAME);
+ }
+
+ /**
+ * 鏄惁涓哄唴閾剧粍浠�
+ */
+ public boolean isInnerLink() {
+ return isFrame.equals(SystemConstants.NO_FRAME) && StringUtils.ishttp(path);
+ }
+
+ /**
+ * 鏄惁涓簆arent_view缁勪欢
+ */
+ public boolean isParentView() {
+ return !Constants.TOP_PARENT_ID.equals(getParentId()) && SystemConstants.TYPE_DIR.equals(menuType);
+ }
+
+ /**
+ * 鍐呴摼鍩熷悕鐗规畩瀛楃鏇挎崲
+ */
+ public static String innerLinkReplaceEach(String path) {
+ return StringUtils.replaceEach(path, new String[]{Constants.HTTP, Constants.HTTPS, Constants.WWW, ".", ":"},
+ new String[]{"", "", "", "/", "/"});
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysNotice.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysNotice.java
new file mode 100755
index 0000000..bfcc2bc
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysNotice.java
@@ -0,0 +1,51 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.dromara.common.tenant.core.TenantEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+
+/**
+ * 閫氱煡鍏憡琛� sys_notice
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_notice")
+public class SysNotice extends TenantEntity {
+
+ /**
+ * 鍏憡ID
+ */
+ @TableId(value = "notice_id")
+ private Long noticeId;
+
+ /**
+ * 鍏憡鏍囬
+ */
+ private String noticeTitle;
+
+ /**
+ * 鍏憡绫诲瀷锛�1閫氱煡 2鍏憡锛�
+ */
+ private String noticeType;
+
+ /**
+ * 鍏憡鍐呭
+ */
+ private String noticeContent;
+
+ /**
+ * 鍏憡鐘舵�侊紙0姝e父 1鍏抽棴锛�
+ */
+ private String status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOperLog.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOperLog.java
new file mode 100755
index 0000000..41a8c59
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOperLog.java
@@ -0,0 +1,115 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 鎿嶄綔鏃ュ織璁板綍琛� oper_log
+ *
+ * @author Lion Li
+ */
+
+@Data
+@TableName("sys_oper_log")
+public class SysOperLog implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃ュ織涓婚敭
+ */
+ @TableId(value = "oper_id")
+ private Long operId;
+
+ /**
+ * 绉熸埛缂栧彿
+ */
+ private String tenantId;
+
+ /**
+ * 鎿嶄綔妯″潡
+ */
+ private String title;
+
+ /**
+ * 涓氬姟绫诲瀷锛�0鍏跺畠 1鏂板 2淇敼 3鍒犻櫎锛�
+ */
+ private Integer businessType;
+
+ /**
+ * 璇锋眰鏂规硶
+ */
+ private String method;
+
+ /**
+ * 璇锋眰鏂瑰紡
+ */
+ private String requestMethod;
+
+ /**
+ * 鎿嶄綔绫诲埆锛�0鍏跺畠 1鍚庡彴鐢ㄦ埛 2鎵嬫満绔敤鎴凤級
+ */
+ private Integer operatorType;
+
+ /**
+ * 鎿嶄綔浜哄憳
+ */
+ private String operName;
+
+ /**
+ * 閮ㄩ棬鍚嶇О
+ */
+ private String deptName;
+
+ /**
+ * 璇锋眰url
+ */
+ private String operUrl;
+
+ /**
+ * 鎿嶄綔鍦板潃
+ */
+ private String operIp;
+
+ /**
+ * 鎿嶄綔鍦扮偣
+ */
+ private String operLocation;
+
+ /**
+ * 璇锋眰鍙傛暟
+ */
+ private String operParam;
+
+ /**
+ * 杩斿洖鍙傛暟
+ */
+ private String jsonResult;
+
+ /**
+ * 鎿嶄綔鐘舵�侊紙0姝e父 1寮傚父锛�
+ */
+ private Integer status;
+
+ /**
+ * 閿欒娑堟伅
+ */
+ private String errorMsg;
+
+ /**
+ * 鎿嶄綔鏃堕棿
+ */
+ private Date operTime;
+
+ /**
+ * 娑堣�楁椂闂�
+ */
+ private Long costTime;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOss.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOss.java
new file mode 100755
index 0000000..2285a5d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOss.java
@@ -0,0 +1,55 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.dromara.common.tenant.core.TenantEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * OSS瀵硅薄瀛樺偍瀵硅薄
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_oss")
+public class SysOss extends TenantEntity {
+
+ /**
+ * 瀵硅薄瀛樺偍涓婚敭
+ */
+ @TableId(value = "oss_id")
+ private Long ossId;
+
+ /**
+ * 鏂囦欢鍚�
+ */
+ private String fileName;
+
+ /**
+ * 鍘熷悕
+ */
+ private String originalName;
+
+ /**
+ * 鏂囦欢鍚庣紑鍚�
+ */
+ private String fileSuffix;
+
+ /**
+ * URL鍦板潃
+ */
+ private String url;
+
+ /**
+ * 鎵╁睍瀛楁
+ */
+ private String ext1;
+
+ /**
+ * 鏈嶅姟鍟�
+ */
+ private String service;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOssConfig.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOssConfig.java
new file mode 100755
index 0000000..4b67d63
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOssConfig.java
@@ -0,0 +1,89 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+/**
+ * 瀵硅薄瀛樺偍閰嶇疆瀵硅薄 sys_oss_config
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_oss_config")
+public class SysOssConfig extends BaseEntity {
+
+ /**
+ * 涓婚敭
+ */
+ @TableId(value = "oss_config_id")
+ private Long ossConfigId;
+
+ /**
+ * 閰嶇疆key
+ */
+ private String configKey;
+
+ /**
+ * accessKey
+ */
+ private String accessKey;
+
+ /**
+ * 绉橀挜
+ */
+ private String secretKey;
+
+ /**
+ * 妗跺悕绉�
+ */
+ private String bucketName;
+
+ /**
+ * 鍓嶇紑
+ */
+ private String prefix;
+
+ /**
+ * 璁块棶绔欑偣
+ */
+ private String endpoint;
+
+ /**
+ * 鑷畾涔夊煙鍚�
+ */
+ private String domain;
+
+ /**
+ * 鏄惁https锛�0鍚� 1鏄級
+ */
+ private String isHttps;
+
+ /**
+ * 鍩�
+ */
+ private String region;
+
+ /**
+ * 鏄惁榛樿锛�0=鏄�,1=鍚︼級
+ */
+ private String status;
+
+ /**
+ * 鎵╁睍瀛楁
+ */
+ private String ext1;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 妗舵潈闄愮被鍨�(0private 1public 2custom)
+ */
+ private String accessPolicy;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOssExt.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOssExt.java
new file mode 100755
index 0000000..15a95e7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOssExt.java
@@ -0,0 +1,75 @@
+package org.dromara.system.domain;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 闄勪欢鎵╁睍瀛楁瀵硅薄锛堝瓨鍌ㄥ湪 SysOss.ext1 鐨� JSON 瀛楃涓蹭腑锛�
+ *
+ * @author AprilWind
+ */
+@Data
+public class SysOssExt implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鎵�灞炰笟鍔$被鍨嬶紙濡� avatar銆乺eport銆乧ontract锛�
+ */
+ private String bizType;
+
+ /**
+ * 鏂囦欢澶у皬锛堝崟浣嶏細瀛楄妭锛�
+ */
+ private Long fileSize;
+
+ /**
+ * 鏂囦欢绫诲瀷锛圡IME绫诲瀷锛屽 image/png锛�
+ */
+ private String contentType;
+
+ /**
+ * 鏉ユ簮鏍囪瘑锛堝 userUpload銆乻ystemImport锛�
+ */
+ private String source;
+
+ /**
+ * 涓婁紶 IP 鍦板潃锛屼究浜庡璁″拰杩借釜
+ */
+ private String uploadIp;
+
+ /**
+ * 闄勪欢璇存槑鎴栧娉�
+ */
+ private String remark;
+
+ /**
+ * 闄勪欢鏍囩锛屽 ["鍥剧墖", "璇佷欢"]
+ */
+ private List<String> tags;
+
+ /**
+ * 涓氬姟缁戝畾ID锛堝鏌愪笟鍔¤褰旾D锛�
+ */
+ private String refId;
+
+ /**
+ * 缁戝畾涓氬姟绫诲瀷
+ */
+ private String refType;
+
+ /**
+ * 鏄惁涓轰复鏃舵枃浠讹紝鐢ㄤ簬鍖哄垎姝e紡鎴栧緟娓呯悊
+ */
+ private Boolean isTemp;
+
+ /**
+ * 鏂囦欢MD5鍊硷紙鍙敤浜庡幓閲嶆垨鏍¢獙锛�
+ */
+ private String md5;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysPost.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysPost.java
new file mode 100755
index 0000000..2c985da
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysPost.java
@@ -0,0 +1,61 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.dromara.common.tenant.core.TenantEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 宀椾綅琛� sys_post
+ *
+ * @author Lion Li
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_post")
+public class SysPost extends TenantEntity {
+
+ /**
+ * 宀椾綅搴忓彿
+ */
+ @TableId(value = "post_id")
+ private Long postId;
+
+ /**
+ * 閮ㄩ棬id
+ */
+ private Long deptId;
+
+ /**
+ * 宀椾綅缂栫爜
+ */
+ private String postCode;
+
+ /**
+ * 宀椾綅鍚嶇О
+ */
+ private String postName;
+
+ /**
+ * 宀椾綅绫诲埆缂栫爜
+ */
+ private String postCategory;
+
+ /**
+ * 宀椾綅鎺掑簭
+ */
+ private Integer postSort;
+
+ /**
+ * 鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRole.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRole.java
new file mode 100755
index 0000000..aa1b19a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRole.java
@@ -0,0 +1,79 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.dromara.common.tenant.core.TenantEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 瑙掕壊琛� sys_role
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_role")
+public class SysRole extends TenantEntity {
+
+ /**
+ * 瑙掕壊ID
+ */
+ @TableId(value = "role_id")
+ private Long roleId;
+
+ /**
+ * 瑙掕壊鍚嶇О
+ */
+ private String roleName;
+
+ /**
+ * 瑙掕壊鏉冮檺
+ */
+ private String roleKey;
+
+ /**
+ * 瑙掕壊鎺掑簭
+ */
+ private Integer roleSort;
+
+ /**
+ * 鏁版嵁鑼冨洿锛�1锛氬叏閮ㄦ暟鎹潈闄� 2锛氳嚜瀹氭暟鎹潈闄� 3锛氭湰閮ㄩ棬鏁版嵁鏉冮檺 4锛氭湰閮ㄩ棬鍙婁互涓嬫暟鎹潈闄� 5锛氫粎鏈汉鏁版嵁鏉冮檺 6锛氶儴闂ㄥ強浠ヤ笅鎴栨湰浜烘暟鎹潈闄愶級
+ */
+ private String dataScope;
+
+ /**
+ * 鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀猴紙 0锛氱埗瀛愪笉浜掔浉鍏宠仈鏄剧ず 1锛氱埗瀛愪簰鐩稿叧鑱旀樉绀猴級
+ */
+ private Boolean menuCheckStrictly;
+
+ /**
+ * 閮ㄩ棬鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀猴紙0锛氱埗瀛愪笉浜掔浉鍏宠仈鏄剧ず 1锛氱埗瀛愪簰鐩稿叧鑱旀樉绀� 锛�
+ */
+ private Boolean deptCheckStrictly;
+
+ /**
+ * 瑙掕壊鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�
+ */
+ @TableLogic
+ private String delFlag;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ public SysRole(Long roleId) {
+ this.roleId = roleId;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRoleDept.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRoleDept.java
new file mode 100755
index 0000000..ba77694
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRoleDept.java
@@ -0,0 +1,29 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+/**
+ * 瑙掕壊鍜岄儴闂ㄥ叧鑱� sys_role_dept
+ *
+ * @author Lion Li
+ */
+
+@Data
+@TableName("sys_role_dept")
+public class SysRoleDept {
+
+ /**
+ * 瑙掕壊ID
+ */
+ @TableId(type = IdType.INPUT)
+ private Long roleId;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ private Long deptId;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRoleMenu.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRoleMenu.java
new file mode 100755
index 0000000..ba28f17
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRoleMenu.java
@@ -0,0 +1,29 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+/**
+ * 瑙掕壊鍜岃彍鍗曞叧鑱� sys_role_menu
+ *
+ * @author Lion Li
+ */
+
+@Data
+@TableName("sys_role_menu")
+public class SysRoleMenu {
+
+ /**
+ * 瑙掕壊ID
+ */
+ @TableId(type = IdType.INPUT)
+ private Long roleId;
+
+ /**
+ * 鑿滃崟ID
+ */
+ private Long menuId;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysSocial.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysSocial.java
new file mode 100755
index 0000000..10f2936
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysSocial.java
@@ -0,0 +1,136 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+
+/**
+ * 绀句細鍖栧叧绯诲璞� sys_social
+ *
+ * @author thiszhc
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_social")
+public class SysSocial extends TenantEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭
+ */
+ @TableId(value = "id")
+ private Long id;
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ private Long userId;
+
+ /**
+ * 鐨勫敮涓�ID
+ */
+ private String authId;
+
+ /**
+ * 鐢ㄦ埛鏉ユ簮
+ */
+ private String source;
+
+ /**
+ * 鐢ㄦ埛鐨勬巿鏉冧护鐗�
+ */
+ private String accessToken;
+
+ /**
+ * 鐢ㄦ埛鐨勬巿鏉冧护鐗岀殑鏈夋晥鏈燂紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private int expireIn;
+
+ /**
+ * 鍒锋柊浠ょ墝锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�
+ */
+ private String refreshToken;
+
+ /**
+ * 鐢ㄦ埛鐨� open id
+ */
+ private String openId;
+
+ /**
+ * 鎺堟潈鐨勭涓夋柟璐﹀彿
+ */
+ private String userName;
+
+ /**
+ * 鎺堟潈鐨勭涓夋柟鏄电О
+ */
+ private String nickName;
+
+ /**
+ * 鎺堟潈鐨勭涓夋柟閭
+ */
+ private String email;
+
+ /**
+ * 鎺堟潈鐨勭涓夋柟澶村儚鍦板潃
+ */
+ private String avatar;
+
+ /**
+ * 骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String accessCode;
+
+ /**
+ * 鐢ㄦ埛鐨� unionid
+ */
+ private String unionId;
+
+ /**
+ * 鎺堜簣鐨勬潈闄愶紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String scope;
+
+ /**
+ * 涓埆骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String tokenType;
+
+ /**
+ * id token锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�
+ */
+ private String idToken;
+
+ /**
+ * 灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String macAlgorithm;
+
+ /**
+ * 灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String macKey;
+
+ /**
+ * 鐢ㄦ埛鐨勬巿鏉僣ode锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�
+ */
+ private String code;
+
+ /**
+ * Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String oauthToken;
+
+ /**
+ * Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String oauthTokenSecret;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysTenant.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysTenant.java
new file mode 100755
index 0000000..9800c30
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysTenant.java
@@ -0,0 +1,103 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * 绉熸埛瀵硅薄 sys_tenant
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_tenant")
+public class SysTenant extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * id
+ */
+ @TableId(value = "id")
+ private Long id;
+
+ /**
+ * 绉熸埛缂栧彿
+ */
+ private String tenantId;
+
+ /**
+ * 鑱旂郴浜�
+ */
+ private String contactUserName;
+
+ /**
+ * 鑱旂郴鐢佃瘽
+ */
+ private String contactPhone;
+
+ /**
+ * 浼佷笟鍚嶇О
+ */
+ private String companyName;
+
+ /**
+ * 缁熶竴绀句細淇$敤浠g爜
+ */
+ private String licenseNumber;
+
+ /**
+ * 鍦板潃
+ */
+ private String address;
+
+ /**
+ * 鍩熷悕
+ */
+ private String domain;
+
+ /**
+ * 浼佷笟绠�浠�
+ */
+ private String intro;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 绉熸埛濂楅缂栧彿
+ */
+ private Long packageId;
+
+ /**
+ * 杩囨湡鏃堕棿
+ */
+ private Date expireTime;
+
+ /**
+ * 鐢ㄦ埛鏁伴噺锛�-1涓嶉檺鍒讹級
+ */
+ private Long accountCount;
+
+ /**
+ * 绉熸埛鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�
+ */
+ @TableLogic
+ private String delFlag;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysTenantPackage.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysTenantPackage.java
new file mode 100755
index 0000000..5f58e3e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysTenantPackage.java
@@ -0,0 +1,60 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.io.Serial;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+/**
+ * 绉熸埛濂楅瀵硅薄 sys_tenant_package
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_tenant_package")
+public class SysTenantPackage extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 绉熸埛濂楅id
+ */
+ @TableId(value = "package_id")
+ private Long packageId;
+
+ /**
+ * 濂楅鍚嶇О
+ */
+ private String packageName;
+
+ /**
+ * 鍏宠仈鑿滃崟id
+ */
+ private String menuIds;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀猴紙 0锛氱埗瀛愪笉浜掔浉鍏宠仈鏄剧ず 1锛氱埗瀛愪簰鐩稿叧鑱旀樉绀猴級
+ */
+ private Boolean menuCheckStrictly;
+
+ /**
+ * 鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�
+ */
+ @TableLogic
+ private String delFlag;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUser.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUser.java
new file mode 100755
index 0000000..a06172f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUser.java
@@ -0,0 +1,115 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.util.Date;
+
+/**
+ * 鐢ㄦ埛瀵硅薄 sys_user
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_user")
+public class SysUser extends TenantEntity {
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ @TableId(value = "user_id")
+ private Long userId;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ private Long deptId;
+
+ /**
+ * 鐢ㄦ埛璐﹀彿
+ */
+ private String userName;
+
+ /**
+ * 鐢ㄦ埛鏄电О
+ */
+ private String nickName;
+
+ /**
+ * 鐢ㄦ埛绫诲瀷锛坰ys_user绯荤粺鐢ㄦ埛锛�
+ */
+ private String userType;
+
+ /**
+ * 鐢ㄦ埛閭
+ */
+ private String email;
+
+ /**
+ * 鎵嬫満鍙风爜
+ */
+ private String phonenumber;
+
+ /**
+ * 鐢ㄦ埛鎬у埆
+ */
+ private String sex;
+
+ /**
+ * 鐢ㄦ埛澶村儚
+ */
+ private Long avatar;
+
+ /**
+ * 瀵嗙爜
+ */
+ @TableField(
+ insertStrategy = FieldStrategy.NOT_EMPTY,
+ updateStrategy = FieldStrategy.NOT_EMPTY,
+ whereStrategy = FieldStrategy.NOT_EMPTY
+ )
+ private String password;
+
+ /**
+ * 璐﹀彿鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�
+ */
+ @TableLogic
+ private String delFlag;
+
+ /**
+ * 鏈�鍚庣櫥褰旾P
+ */
+ private String loginIp;
+
+ /**
+ * 鏈�鍚庣櫥褰曟椂闂�
+ */
+ private Date loginDate;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+
+ public SysUser(Long userId) {
+ this.userId = userId;
+ }
+
+ public boolean isSuperAdmin() {
+ return SystemConstants.SUPER_ADMIN_ID.equals(this.userId);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserOnline.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserOnline.java
new file mode 100755
index 0000000..ba30eb6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserOnline.java
@@ -0,0 +1,63 @@
+package org.dromara.system.domain;
+
+import lombok.Data;
+
+/**
+ * 褰撳墠鍦ㄧ嚎浼氳瘽
+ *
+ * @author Lion Li
+ */
+@Data
+public class SysUserOnline {
+
+ /**
+ * 浼氳瘽缂栧彿
+ */
+ private String tokenId;
+
+ /**
+ * 閮ㄩ棬鍚嶇О
+ */
+ private String deptName;
+
+ /**
+ * 鐢ㄦ埛鍚嶇О
+ */
+ private String userName;
+
+ /**
+ * 瀹㈡埛绔�
+ */
+ private String clientKey;
+
+ /**
+ * 璁惧绫诲瀷
+ */
+ private String deviceType;
+
+ /**
+ * 鐧诲綍IP鍦板潃
+ */
+ private String ipaddr;
+
+ /**
+ * 鐧诲綍鍦板潃
+ */
+ private String loginLocation;
+
+ /**
+ * 娴忚鍣ㄧ被鍨�
+ */
+ private String browser;
+
+ /**
+ * 鎿嶄綔绯荤粺
+ */
+ private String os;
+
+ /**
+ * 鐧诲綍鏃堕棿
+ */
+ private Long loginTime;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserPost.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserPost.java
new file mode 100755
index 0000000..119c117
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserPost.java
@@ -0,0 +1,29 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+/**
+ * 鐢ㄦ埛鍜屽矖浣嶅叧鑱� sys_user_post
+ *
+ * @author Lion Li
+ */
+
+@Data
+@TableName("sys_user_post")
+public class SysUserPost {
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ @TableId(type = IdType.INPUT)
+ private Long userId;
+
+ /**
+ * 宀椾綅ID
+ */
+ private Long postId;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserRole.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserRole.java
new file mode 100755
index 0000000..0a50e80
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUserRole.java
@@ -0,0 +1,29 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+/**
+ * 鐢ㄦ埛鍜岃鑹插叧鑱� sys_user_role
+ *
+ * @author Lion Li
+ */
+
+@Data
+@TableName("sys_user_role")
+public class SysUserRole {
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ @TableId(type = IdType.INPUT)
+ private Long userId;
+
+ /**
+ * 瑙掕壊ID
+ */
+ private Long roleId;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysClientBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysClientBo.java
new file mode 100755
index 0000000..e5f5ffa
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysClientBo.java
@@ -0,0 +1,80 @@
+package org.dromara.system.domain.bo;
+
+import org.dromara.system.domain.SysClient;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+import java.util.List;
+
+/**
+ * 鎺堟潈绠$悊涓氬姟瀵硅薄 sys_client
+ *
+ * @author Michelle.Chung
+ * @date 2023-05-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysClient.class, reverseConvertGenerate = false)
+public class SysClientBo extends BaseEntity {
+
+ /**
+ * id
+ */
+ @NotNull(message = "id涓嶈兘涓虹┖", groups = { EditGroup.class })
+ private Long id;
+
+ /**
+ * 瀹㈡埛绔痠d
+ */
+ private String clientId;
+
+ /**
+ * 瀹㈡埛绔痥ey
+ */
+ @NotBlank(message = "瀹㈡埛绔痥ey涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private String clientKey;
+
+ /**
+ * 瀹㈡埛绔閽�
+ */
+ @NotBlank(message = "瀹㈡埛绔閽ヤ笉鑳戒负绌�", groups = { AddGroup.class, EditGroup.class })
+ private String clientSecret;
+
+ /**
+ * 鎺堟潈绫诲瀷
+ */
+ @NotNull(message = "鎺堟潈绫诲瀷涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private List<String> grantTypeList;
+
+ /**
+ * 鎺堟潈绫诲瀷
+ */
+ private String grantType;
+
+ /**
+ * 璁惧绫诲瀷
+ */
+ private String deviceType;
+
+ /**
+ * token娲昏穬瓒呮椂鏃堕棿
+ */
+ private Long activeTimeout;
+
+ /**
+ * token鍥哄畾瓒呮椂鏃堕棿
+ */
+ private Long timeout;
+
+ /**
+ * 鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysConfigBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysConfigBo.java
new file mode 100755
index 0000000..257935d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysConfigBo.java
@@ -0,0 +1,59 @@
+package org.dromara.system.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.system.domain.SysConfig;
+
+/**
+ * 鍙傛暟閰嶇疆涓氬姟瀵硅薄 sys_config
+ *
+ * @author Michelle.Chung
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysConfig.class, reverseConvertGenerate = false)
+public class SysConfigBo extends BaseEntity {
+
+ /**
+ * 鍙傛暟涓婚敭
+ */
+ private Long configId;
+
+ /**
+ * 鍙傛暟鍚嶇О
+ */
+ @NotBlank(message = "鍙傛暟鍚嶇О涓嶈兘涓虹┖")
+ @Size(min = 0, max = 100, message = "鍙傛暟鍚嶇О涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String configName;
+
+ /**
+ * 鍙傛暟閿悕
+ */
+ @NotBlank(message = "鍙傛暟閿悕涓嶈兘涓虹┖")
+ @Size(min = 0, max = 100, message = "鍙傛暟閿悕闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String configKey;
+
+ /**
+ * 鍙傛暟閿��
+ */
+ @NotBlank(message = "鍙傛暟閿�间笉鑳戒负绌�")
+ @Size(min = 0, max = 500, message = "鍙傛暟閿�奸暱搴︿笉鑳借秴杩噞max}涓瓧绗�")
+ private String configValue;
+
+ /**
+ * 绯荤粺鍐呯疆锛圷鏄� N鍚︼級
+ */
+ private String configType;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDeptBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDeptBo.java
new file mode 100755
index 0000000..0d8ac84
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDeptBo.java
@@ -0,0 +1,81 @@
+package org.dromara.system.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.system.domain.SysDept;
+
+/**
+ * 閮ㄩ棬涓氬姟瀵硅薄 sys_dept
+ *
+ * @author Michelle.Chung
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysDept.class, reverseConvertGenerate = false)
+public class SysDeptBo extends BaseEntity {
+
+ /**
+ * 閮ㄩ棬id
+ */
+ private Long deptId;
+
+ /**
+ * 鐖堕儴闂↖D
+ */
+ private Long parentId;
+
+ /**
+ * 閮ㄩ棬鍚嶇О
+ */
+ @NotBlank(message = "閮ㄩ棬鍚嶇О涓嶈兘涓虹┖")
+ @Size(min = 0, max = 30, message = "閮ㄩ棬鍚嶇О闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String deptName;
+
+ /**
+ * 閮ㄩ棬绫诲埆缂栫爜
+ */
+ @Size(min = 0, max = 100, message = "閮ㄩ棬绫诲埆缂栫爜闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String deptCategory;
+
+ /**
+ * 鏄剧ず椤哄簭
+ */
+ @NotNull(message = "鏄剧ず椤哄簭涓嶈兘涓虹┖")
+ private Integer orderNum;
+
+ /**
+ * 璐熻矗浜�
+ */
+ private Long leader;
+
+ /**
+ * 鑱旂郴鐢佃瘽
+ */
+ @Size(min = 0, max = 11, message = "鑱旂郴鐢佃瘽闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String phone;
+
+ /**
+ * 閭
+ */
+ @Email(message = "閭鏍煎紡涓嶆纭�")
+ @Size(min = 0, max = 50, message = "閭闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String email;
+
+ /**
+ * 閮ㄩ棬鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 褰掑睘閮ㄩ棬id锛堥儴闂ㄦ爲锛�
+ */
+ private Long belongDeptId;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDictDataBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDictDataBo.java
new file mode 100755
index 0000000..042946c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDictDataBo.java
@@ -0,0 +1,80 @@
+package org.dromara.system.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.system.domain.SysDictData;
+
+/**
+ * 瀛楀吀鏁版嵁涓氬姟瀵硅薄 sys_dict_data
+ *
+ * @author Michelle.Chung
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysDictData.class, reverseConvertGenerate = false)
+public class SysDictDataBo extends BaseEntity {
+
+ /**
+ * 瀛楀吀缂栫爜
+ */
+ private Long dictCode;
+
+ /**
+ * 瀛楀吀鎺掑簭
+ */
+ private Integer dictSort;
+
+ /**
+ * 瀛楀吀鏍囩
+ */
+ @NotBlank(message = "瀛楀吀鏍囩涓嶈兘涓虹┖")
+ @Size(min = 0, max = 100, message = "瀛楀吀鏍囩闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String dictLabel;
+
+ /**
+ * 瀛楀吀閿��
+ */
+ @NotBlank(message = "瀛楀吀閿�间笉鑳戒负绌�")
+ @Size(min = 0, max = 100, message = "瀛楀吀閿�奸暱搴︿笉鑳借秴杩噞max}涓瓧绗�")
+ private String dictValue;
+
+ /**
+ * 瀛楀吀绫诲瀷
+ */
+ @NotBlank(message = "瀛楀吀绫诲瀷涓嶈兘涓虹┖")
+ @Size(min = 0, max = 100, message = "瀛楀吀绫诲瀷闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String dictType;
+
+ /**
+ * 鏍峰紡灞炴�э紙鍏朵粬鏍峰紡鎵╁睍锛�
+ */
+ @Size(min = 0, max = 100, message = "鏍峰紡灞炴�ч暱搴︿笉鑳借秴杩噞max}涓瓧绗�")
+ private String cssClass;
+
+ /**
+ * 琛ㄦ牸鍥炴樉鏍峰紡
+ */
+ private String listClass;
+
+ /**
+ * 鏄惁榛樿锛圷鏄� N鍚︼級
+ */
+ private String isDefault;
+
+ /**
+ * 鍒涘缓閮ㄩ棬
+ */
+ private Long createDept;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDictTypeBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDictTypeBo.java
new file mode 100755
index 0000000..fcc1ac1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysDictTypeBo.java
@@ -0,0 +1,50 @@
+package org.dromara.system.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.constant.RegexConstants;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.system.domain.SysDictType;
+
+/**
+ * 瀛楀吀绫诲瀷涓氬姟瀵硅薄 sys_dict_type
+ *
+ * @author Michelle.Chung
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysDictType.class, reverseConvertGenerate = false)
+public class SysDictTypeBo extends BaseEntity {
+
+ /**
+ * 瀛楀吀涓婚敭
+ */
+ private Long dictId;
+
+ /**
+ * 瀛楀吀鍚嶇О
+ */
+ @NotBlank(message = "瀛楀吀鍚嶇О涓嶈兘涓虹┖")
+ @Size(min = 0, max = 100, message = "瀛楀吀绫诲瀷鍚嶇О闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String dictName;
+
+ /**
+ * 瀛楀吀绫诲瀷
+ */
+ @NotBlank(message = "瀛楀吀绫诲瀷涓嶈兘涓虹┖")
+ @Size(min = 0, max = 100, message = "瀛楀吀绫诲瀷绫诲瀷闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ @Pattern(regexp = RegexConstants.DICTIONARY_TYPE, message = "瀛楀吀绫诲瀷蹇呴』浠ュ瓧姣嶅紑澶达紝涓斿彧鑳戒负锛堝皬鍐欏瓧姣嶏紝鏁板瓧锛屼笅婊戠嚎锛�")
+ private String dictType;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysLogininforBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysLogininforBo.java
new file mode 100755
index 0000000..4646162
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysLogininforBo.java
@@ -0,0 +1,87 @@
+package org.dromara.system.domain.bo;
+
+import org.dromara.system.domain.SysLogininfor;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 绯荤粺璁块棶璁板綍涓氬姟瀵硅薄 sys_logininfor
+ *
+ * @author Michelle.Chung
+ */
+
+@Data
+@AutoMapper(target = SysLogininfor.class, reverseConvertGenerate = false)
+public class SysLogininforBo {
+
+ /**
+ * 璁块棶ID
+ */
+ private Long infoId;
+
+ /**
+ * 绉熸埛缂栧彿
+ */
+ private String tenantId;
+
+ /**
+ * 鐢ㄦ埛璐﹀彿
+ */
+ private String userName;
+
+ /**
+ * 瀹㈡埛绔�
+ */
+ private String clientKey;
+
+ /**
+ * 璁惧绫诲瀷
+ */
+ private String deviceType;
+
+ /**
+ * 鐧诲綍IP鍦板潃
+ */
+ private String ipaddr;
+
+ /**
+ * 鐧诲綍鍦扮偣
+ */
+ private String loginLocation;
+
+ /**
+ * 娴忚鍣ㄧ被鍨�
+ */
+ private String browser;
+
+ /**
+ * 鎿嶄綔绯荤粺
+ */
+ private String os;
+
+ /**
+ * 鐧诲綍鐘舵�侊紙0鎴愬姛 1澶辫触锛�
+ */
+ private String status;
+
+ /**
+ * 鎻愮ず娑堟伅
+ */
+ private String msg;
+
+ /**
+ * 璁块棶鏃堕棿
+ */
+ private Date loginTime;
+
+ /**
+ * 璇锋眰鍙傛暟
+ */
+ private Map<String, Object> params = new HashMap<>();
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMenuBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMenuBo.java
new file mode 100755
index 0000000..118c954
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMenuBo.java
@@ -0,0 +1,113 @@
+package org.dromara.system.domain.bo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.constant.RegexConstants;
+import org.dromara.common.json.validate.JsonPattern;
+import org.dromara.common.json.validate.JsonType;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.system.domain.SysMenu;
+
+/**
+ * 鑿滃崟鏉冮檺涓氬姟瀵硅薄 sys_menu
+ *
+ * @author Michelle.Chung
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysMenu.class, reverseConvertGenerate = false)
+public class SysMenuBo extends BaseEntity {
+
+ /**
+ * 鑿滃崟ID
+ */
+ private Long menuId;
+
+ /**
+ * 鐖惰彍鍗旾D
+ */
+ private Long parentId;
+
+ /**
+ * 鑿滃崟鍚嶇О
+ */
+ @NotBlank(message = "鑿滃崟鍚嶇О涓嶈兘涓虹┖")
+ @Size(min = 0, max = 50, message = "鑿滃崟鍚嶇О闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String menuName;
+
+ /**
+ * 鏄剧ず椤哄簭
+ */
+ @NotNull(message = "鏄剧ず椤哄簭涓嶈兘涓虹┖")
+ private Integer orderNum;
+
+ /**
+ * 璺敱鍦板潃
+ */
+ @Size(min = 0, max = 200, message = "璺敱鍦板潃涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String path;
+
+ /**
+ * 缁勪欢璺緞
+ */
+ @Size(min = 0, max = 200, message = "缁勪欢璺緞涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String component;
+
+ /**
+ * 璺敱鍙傛暟
+ */
+ @JsonPattern(type = JsonType.OBJECT, message = "璺敱鍙傛暟蹇呴』绗﹀悎JSON鏍煎紡")
+ private String queryParam;
+
+ /**
+ * 鏄惁涓哄閾撅紙0鏄� 1鍚︼級
+ */
+ private String isFrame;
+
+ /**
+ * 鏄惁缂撳瓨锛�0缂撳瓨 1涓嶇紦瀛橈級
+ */
+ private String isCache;
+
+ /**
+ * 鑿滃崟绫诲瀷锛圡鐩綍 C鑿滃崟 F鎸夐挳锛�
+ */
+ @NotBlank(message = "鑿滃崟绫诲瀷涓嶈兘涓虹┖")
+ private String menuType;
+
+ /**
+ * 鏄剧ず鐘舵�侊紙0鏄剧ず 1闅愯棌锛�
+ */
+ private String visible;
+
+ /**
+ * 鑿滃崟鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 鏉冮檺鏍囪瘑
+ */
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ @Size(min = 0, max = 100, message = "鏉冮檺鏍囪瘑闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ @Pattern(regexp = RegexConstants.PERMISSION_STRING, message = "鏉冮檺鏍囪瘑蹇呴』绗﹀悎 tool:build:list 鏍煎紡")
+ private String perms;
+
+ /**
+ * 鑿滃崟鍥炬爣
+ */
+ private String icon;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysNoticeBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysNoticeBo.java
new file mode 100755
index 0000000..cdcc575
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysNoticeBo.java
@@ -0,0 +1,61 @@
+package org.dromara.system.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.xss.Xss;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.system.domain.SysNotice;
+
+/**
+ * 閫氱煡鍏憡涓氬姟瀵硅薄 sys_notice
+ *
+ * @author Michelle.Chung
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysNotice.class, reverseConvertGenerate = false)
+public class SysNoticeBo extends BaseEntity {
+
+ /**
+ * 鍏憡ID
+ */
+ private Long noticeId;
+
+ /**
+ * 鍏憡鏍囬
+ */
+ @Xss(message = "鍏憡鏍囬涓嶈兘鍖呭惈鑴氭湰瀛楃")
+ @NotBlank(message = "鍏憡鏍囬涓嶈兘涓虹┖")
+ @Size(min = 0, max = 50, message = "鍏憡鏍囬涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String noticeTitle;
+
+ /**
+ * 鍏憡绫诲瀷锛�1閫氱煡 2鍏憡锛�
+ */
+ private String noticeType;
+
+ /**
+ * 鍏憡鍐呭
+ */
+ private String noticeContent;
+
+ /**
+ * 鍏憡鐘舵�侊紙0姝e父 1鍏抽棴锛�
+ */
+ private String status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鍒涘缓浜哄悕绉�
+ */
+ private String createByName;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOperLogBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOperLogBo.java
new file mode 100755
index 0000000..f16400a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOperLogBo.java
@@ -0,0 +1,127 @@
+package org.dromara.system.domain.bo;
+
+import org.dromara.common.log.event.OperLogEvent;
+import org.dromara.system.domain.SysOperLog;
+import io.github.linpeilie.annotations.AutoMapper;
+import io.github.linpeilie.annotations.AutoMappers;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 鎿嶄綔鏃ュ織璁板綍涓氬姟瀵硅薄 sys_oper_log
+ *
+ * @author Michelle.Chung
+ * @date 2023-02-07
+ */
+
+@Data
+@AutoMappers({
+ @AutoMapper(target = SysOperLog.class, reverseConvertGenerate = false),
+ @AutoMapper(target = OperLogEvent.class)
+})
+public class SysOperLogBo {
+
+ /**
+ * 鏃ュ織涓婚敭
+ */
+ private Long operId;
+
+ /**
+ * 绉熸埛缂栧彿
+ */
+ private String tenantId;
+
+ /**
+ * 妯″潡鏍囬
+ */
+ private String title;
+
+ /**
+ * 涓氬姟绫诲瀷锛�0鍏跺畠 1鏂板 2淇敼 3鍒犻櫎锛�
+ */
+ private Integer businessType;
+
+ /**
+ * 涓氬姟绫诲瀷鏁扮粍
+ */
+ private Integer[] businessTypes;
+
+ /**
+ * 鏂规硶鍚嶇О
+ */
+ private String method;
+
+ /**
+ * 璇锋眰鏂瑰紡
+ */
+ private String requestMethod;
+
+ /**
+ * 鎿嶄綔绫诲埆锛�0鍏跺畠 1鍚庡彴鐢ㄦ埛 2鎵嬫満绔敤鎴凤級
+ */
+ private Integer operatorType;
+
+ /**
+ * 鎿嶄綔浜哄憳
+ */
+ private String operName;
+
+ /**
+ * 閮ㄩ棬鍚嶇О
+ */
+ private String deptName;
+
+ /**
+ * 璇锋眰URL
+ */
+ private String operUrl;
+
+ /**
+ * 涓绘満鍦板潃
+ */
+ private String operIp;
+
+ /**
+ * 鎿嶄綔鍦扮偣
+ */
+ private String operLocation;
+
+ /**
+ * 璇锋眰鍙傛暟
+ */
+ private String operParam;
+
+ /**
+ * 杩斿洖鍙傛暟
+ */
+ private String jsonResult;
+
+ /**
+ * 鎿嶄綔鐘舵�侊紙0姝e父 1寮傚父锛�
+ */
+ private Integer status;
+
+ /**
+ * 閿欒娑堟伅
+ */
+ private String errorMsg;
+
+ /**
+ * 鎿嶄綔鏃堕棿
+ */
+ private Date operTime;
+
+ /**
+ * 娑堣�楁椂闂�
+ */
+ private Long costTime;
+
+ /**
+ * 璇锋眰鍙傛暟
+ */
+ private Map<String, Object> params = new HashMap<>();
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssBo.java
new file mode 100755
index 0000000..ff624c9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssBo.java
@@ -0,0 +1,54 @@
+package org.dromara.system.domain.bo;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.system.domain.SysOss;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * OSS瀵硅薄瀛樺偍鍒嗛〉鏌ヨ瀵硅薄 sys_oss
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysOss.class, reverseConvertGenerate = false)
+public class SysOssBo extends BaseEntity {
+
+ /**
+ * ossId
+ */
+ private Long ossId;
+
+ /**
+ * 鏂囦欢鍚�
+ */
+ private String fileName;
+
+ /**
+ * 鍘熷悕
+ */
+ private String originalName;
+
+ /**
+ * 鏂囦欢鍚庣紑鍚�
+ */
+ private String fileSuffix;
+
+ /**
+ * URL鍦板潃
+ */
+ private String url;
+
+ /**
+ * 鎵╁睍瀛楁
+ */
+ private String ext1;
+
+ /**
+ * 鏈嶅姟鍟�
+ */
+ private String service;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssConfigBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssConfigBo.java
new file mode 100755
index 0000000..3dc4328
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssConfigBo.java
@@ -0,0 +1,109 @@
+package org.dromara.system.domain.bo;
+
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.system.domain.SysOssConfig;
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 瀵硅薄瀛樺偍閰嶇疆涓氬姟瀵硅薄 sys_oss_config
+ *
+ * @author Lion Li
+ * @author 瀛よ垷鐑熼洦
+ * @date 2021-08-13
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysOssConfig.class, reverseConvertGenerate = false)
+public class SysOssConfigBo extends BaseEntity {
+
+ /**
+ * 涓婚敭
+ */
+ @NotNull(message = "涓婚敭涓嶈兘涓虹┖", groups = {EditGroup.class})
+ private Long ossConfigId;
+
+ /**
+ * 閰嶇疆key
+ */
+ @NotBlank(message = "閰嶇疆key涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+ @Size(min = 2, max = 100, message = "configKey闀垮害蹇呴』浠嬩簬{min}鍜寋max} 涔嬮棿")
+ private String configKey;
+
+ /**
+ * accessKey
+ */
+ @NotBlank(message = "accessKey涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+ @Size(min = 2, max = 100, message = "accessKey闀垮害蹇呴』浠嬩簬{min}鍜寋max} 涔嬮棿")
+ private String accessKey;
+
+ /**
+ * 绉橀挜
+ */
+ @NotBlank(message = "secretKey涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+ @Size(min = 2, max = 100, message = "secretKey闀垮害蹇呴』浠嬩簬{min}鍜寋max} 涔嬮棿")
+ private String secretKey;
+
+ /**
+ * 妗跺悕绉�
+ */
+ @NotBlank(message = "妗跺悕绉颁笉鑳戒负绌�", groups = {AddGroup.class, EditGroup.class})
+ @Size(min = 2, max = 100, message = "bucketName闀垮害蹇呴』浠嬩簬{min}鍜寋max}涔嬮棿")
+ private String bucketName;
+
+ /**
+ * 鍓嶇紑
+ */
+ private String prefix;
+
+ /**
+ * 璁块棶绔欑偣
+ */
+ @NotBlank(message = "璁块棶绔欑偣涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+ @Size(min = 2, max = 100, message = "endpoint闀垮害蹇呴』浠嬩簬{min}鍜寋max}涔嬮棿")
+ private String endpoint;
+
+ /**
+ * 鑷畾涔夊煙鍚�
+ */
+ private String domain;
+
+ /**
+ * 鏄惁https锛圷=鏄�,N=鍚︼級
+ */
+ private String isHttps;
+
+ /**
+ * 鏄惁榛樿锛�0=鏄�,1=鍚︼級
+ */
+ private String status;
+
+ /**
+ * 鍩�
+ */
+ private String region;
+
+ /**
+ * 鎵╁睍瀛楁
+ */
+ private String ext1;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 妗舵潈闄愮被鍨�(0private 1public 2custom)
+ */
+ @NotBlank(message = "妗舵潈闄愮被鍨嬩笉鑳戒负绌�", groups = {AddGroup.class, EditGroup.class})
+ private String accessPolicy;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysPostBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysPostBo.java
new file mode 100755
index 0000000..09805cd
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysPostBo.java
@@ -0,0 +1,75 @@
+package org.dromara.system.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.system.domain.SysPost;
+
+/**
+ * 宀椾綅淇℃伅涓氬姟瀵硅薄 sys_post
+ *
+ * @author Michelle.Chung
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysPost.class, reverseConvertGenerate = false)
+public class SysPostBo extends BaseEntity {
+
+ /**
+ * 宀椾綅ID
+ */
+ private Long postId;
+
+ /**
+ * 閮ㄩ棬id锛堝崟閮ㄩ棬锛�
+ */
+ @NotNull(message = "閮ㄩ棬id涓嶈兘涓虹┖")
+ private Long deptId;
+
+ /**
+ * 褰掑睘閮ㄩ棬id锛堥儴闂ㄦ爲锛�
+ */
+ private Long belongDeptId;
+
+ /**
+ * 宀椾綅缂栫爜
+ */
+ @NotBlank(message = "宀椾綅缂栫爜涓嶈兘涓虹┖")
+ @Size(min = 0, max = 64, message = "宀椾綅缂栫爜闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String postCode;
+
+ /**
+ * 宀椾綅鍚嶇О
+ */
+ @NotBlank(message = "宀椾綅鍚嶇О涓嶈兘涓虹┖")
+ @Size(min = 0, max = 50, message = "宀椾綅鍚嶇О闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String postName;
+
+ /**
+ * 宀椾綅绫诲埆缂栫爜
+ */
+ @Size(min = 0, max = 100, message = "绫诲埆缂栫爜闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String postCategory;
+
+ /**
+ * 鏄剧ず椤哄簭
+ */
+ @NotNull(message = "鏄剧ず椤哄簭涓嶈兘涓虹┖")
+ private Integer postSort;
+
+ /**
+ * 鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysRoleBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysRoleBo.java
new file mode 100755
index 0000000..5e3e602
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysRoleBo.java
@@ -0,0 +1,94 @@
+package org.dromara.system.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.system.domain.SysRole;
+
+/**
+ * 瑙掕壊淇℃伅涓氬姟瀵硅薄 sys_role
+ *
+ * @author Michelle.Chung
+ */
+
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysRole.class, reverseConvertGenerate = false)
+public class SysRoleBo extends BaseEntity {
+
+ /**
+ * 瑙掕壊ID
+ */
+ private Long roleId;
+
+ /**
+ * 瑙掕壊鍚嶇О
+ */
+ @NotBlank(message = "瑙掕壊鍚嶇О涓嶈兘涓虹┖")
+ @Size(min = 0, max = 30, message = "瑙掕壊鍚嶇О闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String roleName;
+
+ /**
+ * 瑙掕壊鏉冮檺瀛楃涓�
+ */
+ @NotBlank(message = "瑙掕壊鏉冮檺瀛楃涓蹭笉鑳戒负绌�")
+ @Size(min = 0, max = 100, message = "鏉冮檺瀛楃闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String roleKey;
+
+ /**
+ * 鏄剧ず椤哄簭
+ */
+ @NotNull(message = "鏄剧ず椤哄簭涓嶈兘涓虹┖")
+ private Integer roleSort;
+
+ /**
+ * 鏁版嵁鑼冨洿锛�1锛氬叏閮ㄦ暟鎹潈闄� 2锛氳嚜瀹氭暟鎹潈闄� 3锛氭湰閮ㄩ棬鏁版嵁鏉冮檺 4锛氭湰閮ㄩ棬鍙婁互涓嬫暟鎹潈闄� 5锛氫粎鏈汉鏁版嵁鏉冮檺 6锛氶儴闂ㄥ強浠ヤ笅鎴栨湰浜烘暟鎹潈闄愶級
+ */
+ private String dataScope;
+
+ /**
+ * 鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�
+ */
+ private Boolean menuCheckStrictly;
+
+ /**
+ * 閮ㄩ棬鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�
+ */
+ private Boolean deptCheckStrictly;
+
+ /**
+ * 瑙掕壊鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鑿滃崟缁�
+ */
+ private Long[] menuIds;
+
+ /**
+ * 閮ㄩ棬缁勶紙鏁版嵁鏉冮檺锛�
+ */
+ private Long[] deptIds;
+
+ public SysRoleBo(Long roleId) {
+ this.roleId = roleId;
+ }
+
+ public boolean isSuperAdmin() {
+ return SystemConstants.SUPER_ADMIN_ID.equals(this.roleId);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysSocialBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysSocialBo.java
new file mode 100755
index 0000000..cede1e9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysSocialBo.java
@@ -0,0 +1,142 @@
+package org.dromara.system.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.tenant.core.TenantEntity;
+import org.dromara.system.domain.SysSocial;
+
+/**
+ * 绀句細鍖栧叧绯讳笟鍔″璞� sys_social
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysSocial.class, reverseConvertGenerate = false)
+public class SysSocialBo extends TenantEntity {
+
+ /**
+ * 涓婚敭
+ */
+ @NotNull(message = "涓婚敭涓嶈兘涓虹┖", groups = { EditGroup.class })
+ private Long id;
+
+ /**
+ * 璁よ瘉鍞竴ID
+ */
+ @NotBlank(message = "璁よ瘉鍞竴ID涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private String authId;
+
+ /**
+ * 鐢ㄦ埛鏉ユ簮
+ */
+ @NotBlank(message = "鐢ㄦ埛鏉ユ簮涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private String source;
+
+ /**
+ * 鐢ㄦ埛鐨勬巿鏉冧护鐗�
+ */
+ @NotBlank(message = "鐢ㄦ埛鐨勬巿鏉冧护鐗屼笉鑳戒负绌�", groups = { AddGroup.class, EditGroup.class })
+ private String accessToken;
+
+ /**
+ * 鐢ㄦ埛鐨勬巿鏉冧护鐗岀殑鏈夋晥鏈燂紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private int expireIn;
+
+ /**
+ * 鍒锋柊浠ょ墝锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�
+ */
+ private String refreshToken;
+
+ /**
+ * 骞冲彴鍞竴id
+ */
+ private String openId;
+
+ /**
+ * 鐢ㄦ埛鐨� ID
+ */
+ @NotBlank(message = "鐢ㄦ埛鐨処D涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private Long userId;
+
+ /**
+ * 骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String accessCode;
+
+ /**
+ * 鐢ㄦ埛鐨� unionid
+ */
+ private String unionId;
+
+ /**
+ * 鎺堜簣鐨勬潈闄愶紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String scope;
+
+ /**
+ * 鎺堟潈鐨勭涓夋柟璐﹀彿
+ */
+ private String userName;
+
+ /**
+ * 鎺堟潈鐨勭涓夋柟鏄电О
+ */
+ private String nickName;
+
+ /**
+ * 鎺堟潈鐨勭涓夋柟閭
+ */
+ private String email;
+
+ /**
+ * 鎺堟潈鐨勭涓夋柟澶村儚鍦板潃
+ */
+ private String avatar;
+
+ /**
+ * 涓埆骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String tokenType;
+
+ /**
+ * id token锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�
+ */
+ private String idToken;
+
+ /**
+ * 灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String macAlgorithm;
+
+ /**
+ * 灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String macKey;
+
+ /**
+ * 鐢ㄦ埛鐨勬巿鏉僣ode锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�
+ */
+ private String code;
+
+ /**
+ * Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String oauthToken;
+
+ /**
+ * Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String oauthTokenSecret;
+
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantBo.java
new file mode 100755
index 0000000..3757f9a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantBo.java
@@ -0,0 +1,115 @@
+package org.dromara.system.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.system.domain.SysTenant;
+
+import java.util.Date;
+
+/**
+ * 绉熸埛涓氬姟瀵硅薄 sys_tenant
+ *
+ * @author Michelle.Chung
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysTenant.class, reverseConvertGenerate = false)
+public class SysTenantBo extends BaseEntity {
+
+ /**
+ * id
+ */
+ @NotNull(message = "id涓嶈兘涓虹┖", groups = { EditGroup.class })
+ private Long id;
+
+ /**
+ * 绉熸埛缂栧彿
+ */
+ private String tenantId;
+
+ /**
+ * 鑱旂郴浜�
+ */
+ @NotBlank(message = "鑱旂郴浜轰笉鑳戒负绌�", groups = { AddGroup.class, EditGroup.class })
+ private String contactUserName;
+
+ /**
+ * 鑱旂郴鐢佃瘽
+ */
+ @NotBlank(message = "鑱旂郴鐢佃瘽涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private String contactPhone;
+
+ /**
+ * 浼佷笟鍚嶇О
+ */
+ @NotBlank(message = "浼佷笟鍚嶇О涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private String companyName;
+
+ /**
+ * 鐢ㄦ埛鍚嶏紙鍒涘缓绯荤粺鐢ㄦ埛锛�
+ */
+ @NotBlank(message = "鐢ㄦ埛鍚嶄笉鑳戒负绌�", groups = { AddGroup.class })
+ private String username;
+
+ /**
+ * 瀵嗙爜锛堝垱寤虹郴缁熺敤鎴凤級
+ */
+ @NotBlank(message = "瀵嗙爜涓嶈兘涓虹┖", groups = { AddGroup.class })
+// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}", groups = { AddGroup.class })
+ private String password;
+
+ /**
+ * 缁熶竴绀句細淇$敤浠g爜
+ */
+ private String licenseNumber;
+
+ /**
+ * 鍦板潃
+ */
+ private String address;
+
+ /**
+ * 鍩熷悕
+ */
+ private String domain;
+
+ /**
+ * 浼佷笟绠�浠�
+ */
+ private String intro;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 绉熸埛濂楅缂栧彿
+ */
+ @NotNull(message = "绉熸埛濂楅涓嶈兘涓虹┖", groups = { AddGroup.class })
+ private Long packageId;
+
+ /**
+ * 杩囨湡鏃堕棿
+ */
+ private Date expireTime;
+
+ /**
+ * 鐢ㄦ埛鏁伴噺锛�-1涓嶉檺鍒讹級
+ */
+ private Long accountCount;
+
+ /**
+ * 绉熸埛鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantPackageBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantPackageBo.java
new file mode 100755
index 0000000..b27a406
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantPackageBo.java
@@ -0,0 +1,59 @@
+package org.dromara.system.domain.bo;
+
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.system.domain.SysTenantPackage;
+import io.github.linpeilie.annotations.AutoMapper;
+import io.github.linpeilie.annotations.AutoMapping;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+/**
+ * 绉熸埛濂楅涓氬姟瀵硅薄 sys_tenant_package
+ *
+ * @author Michelle.Chung
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysTenantPackage.class, reverseConvertGenerate = false)
+public class SysTenantPackageBo extends BaseEntity {
+
+ /**
+ * 绉熸埛濂楅id
+ */
+ @NotNull(message = "绉熸埛濂楅id涓嶈兘涓虹┖", groups = { EditGroup.class })
+ private Long packageId;
+
+ /**
+ * 濂楅鍚嶇О
+ */
+ @NotBlank(message = "濂楅鍚嶇О涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+ private String packageName;
+
+ /**
+ * 鍏宠仈鑿滃崟id
+ */
+ @AutoMapping(target = "menuIds", expression = "java(org.dromara.common.core.utils.StringUtils.joinComma(source.getMenuIds()))")
+ private Long[] menuIds;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�
+ */
+ private Boolean menuCheckStrictly;
+
+ /**
+ * 鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java
new file mode 100755
index 0000000..11c0166
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java
@@ -0,0 +1,124 @@
+package org.dromara.system.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.xss.Xss;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.system.domain.SysUser;
+
+/**
+ * 鐢ㄦ埛淇℃伅涓氬姟瀵硅薄 sys_user
+ *
+ * @author Michelle.Chung
+ */
+
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysUser.class, reverseConvertGenerate = false)
+public class SysUserBo extends BaseEntity {
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ private Long userId;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ private Long deptId;
+
+ /**
+ * 鐢ㄦ埛璐﹀彿
+ */
+ @Xss(message = "鐢ㄦ埛璐﹀彿涓嶈兘鍖呭惈鑴氭湰瀛楃")
+ @NotBlank(message = "鐢ㄦ埛璐﹀彿涓嶈兘涓虹┖")
+ @Size(min = 0, max = 30, message = "鐢ㄦ埛璐﹀彿闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String userName;
+
+ /**
+ * 鐢ㄦ埛鏄电О
+ */
+ @Xss(message = "鐢ㄦ埛鏄电О涓嶈兘鍖呭惈鑴氭湰瀛楃")
+ @NotBlank(message = "鐢ㄦ埛鏄电О涓嶈兘涓虹┖")
+ @Size(min = 0, max = 30, message = "鐢ㄦ埛鏄电О闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String nickName;
+
+ /**
+ * 鐢ㄦ埛绫诲瀷锛坰ys_user绯荤粺鐢ㄦ埛锛�
+ */
+ private String userType;
+
+ /**
+ * 鐢ㄦ埛閭
+ */
+ @Email(message = "閭鏍煎紡涓嶆纭�")
+ @Size(min = 0, max = 50, message = "閭闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String email;
+
+ /**
+ * 鎵嬫満鍙风爜
+ */
+ private String phonenumber;
+
+ /**
+ * 鐢ㄦ埛鎬у埆锛�0鐢� 1濂� 2鏈煡锛�
+ */
+ private String sex;
+
+ /**
+ * 瀵嗙爜
+ */
+ private String password;
+
+ /**
+ * 璐﹀彿鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 瑙掕壊缁�
+ */
+ @Size(min = 1, message = "鐢ㄦ埛瑙掕壊涓嶈兘涓虹┖")
+ private Long[] roleIds;
+
+ /**
+ * 宀椾綅缁�
+ */
+ private Long[] postIds;
+
+ /**
+ * 鏁版嵁鏉冮檺 褰撳墠瑙掕壊ID
+ */
+ private Long roleId;
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ private String userIds;
+
+ /**
+ * 鎺掗櫎涓嶆煡璇㈢殑鐢ㄦ埛(宸ヤ綔娴佺敤)
+ */
+ private String excludeUserIds;
+
+ public SysUserBo(Long userId) {
+ this.userId = userId;
+ }
+
+ public boolean isSuperAdmin() {
+ return SystemConstants.SUPER_ADMIN_ID.equals(this.userId);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserPasswordBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserPasswordBo.java
new file mode 100755
index 0000000..8615fcd
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserPasswordBo.java
@@ -0,0 +1,29 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 鐢ㄦ埛瀵嗙爜淇敼bo
+ */
+@Data
+public class SysUserPasswordBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃у瘑鐮�
+ */
+ @NotBlank(message = "鏃у瘑鐮佷笉鑳戒负绌�")
+ private String oldPassword;
+
+ /**
+ * 鏂板瘑鐮�
+ */
+ @NotBlank(message = "鏂板瘑鐮佷笉鑳戒负绌�")
+ private String newPassword;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserProfileBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserProfileBo.java
new file mode 100755
index 0000000..846dd79
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserProfileBo.java
@@ -0,0 +1,53 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.constant.RegexConstants;
+import org.dromara.common.core.xss.Xss;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.sensitive.annotation.Sensitive;
+import org.dromara.common.sensitive.core.SensitiveStrategy;
+
+/**
+ * 涓汉淇℃伅涓氬姟澶勭悊
+ *
+ * @author Michelle.Chung
+ */
+
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class SysUserProfileBo extends BaseEntity {
+
+ /**
+ * 鐢ㄦ埛鏄电О
+ */
+ @Xss(message = "鐢ㄦ埛鏄电О涓嶈兘鍖呭惈鑴氭湰瀛楃")
+ @Size(min = 0, max = 30, message = "鐢ㄦ埛鏄电О闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String nickName;
+
+ /**
+ * 鐢ㄦ埛閭
+ */
+ @Sensitive(strategy = SensitiveStrategy.EMAIL)
+ @Email(message = "閭鏍煎紡涓嶆纭�")
+ @Size(min = 0, max = 50, message = "閭闀垮害涓嶈兘瓒呰繃{max}涓瓧绗�")
+ private String email;
+
+ /**
+ * 鎵嬫満鍙风爜
+ */
+ @Pattern(regexp = RegexConstants.MOBILE, message = "鎵嬫満鍙锋牸寮忎笉姝g‘")
+ @Sensitive(strategy = SensitiveStrategy.PHONE)
+ private String phonenumber;
+
+ /**
+ * 鐢ㄦ埛鎬у埆锛�0鐢� 1濂� 2鏈煡锛�
+ */
+ private String sex;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/MetaVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/MetaVo.java
new file mode 100755
index 0000000..840ddaf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/MetaVo.java
@@ -0,0 +1,78 @@
+package org.dromara.system.domain.vo;
+
+import org.dromara.common.core.utils.StringUtils;
+import lombok.Data;
+
+/**
+ * 璺敱鏄剧ず淇℃伅
+ *
+ * @author ruoyi
+ */
+
+@Data
+public class MetaVo {
+
+ /**
+ * 璁剧疆璇ヨ矾鐢卞湪渚ц竟鏍忓拰闈㈠寘灞戜腑灞曠ず鐨勫悕瀛�
+ */
+ private String title;
+
+ /**
+ * 璁剧疆璇ヨ矾鐢辩殑鍥炬爣锛屽搴旇矾寰剆rc/assets/icons/svg
+ */
+ private String icon;
+
+ /**
+ * 璁剧疆涓簍rue锛屽垯涓嶄細琚� <keep-alive>缂撳瓨
+ */
+ private Boolean noCache;
+
+ /**
+ * 鍐呴摼鍦板潃锛坔ttp(s)://寮�澶达級
+ */
+ private String link;
+
+ /**
+ * 婵�娲昏彍鍗�
+ */
+ private String activeMenu;
+
+ public MetaVo(String title, String icon) {
+ this.title = title;
+ this.icon = icon;
+ }
+
+ public MetaVo(String title, String icon, Boolean noCache) {
+ this.title = title;
+ this.icon = icon;
+ this.noCache = noCache;
+ }
+
+ public MetaVo(String title, String icon, String link) {
+ this.title = title;
+ this.icon = icon;
+ this.link = link;
+ }
+
+ public MetaVo(String title, String icon, Boolean noCache, String link) {
+ this.title = title;
+ this.icon = icon;
+ this.noCache = noCache;
+ if (StringUtils.ishttp(link)) {
+ this.link = link;
+ }
+ }
+
+ public MetaVo(String title, String icon, Boolean noCache, String link, String activeMenu) {
+ this.title = title;
+ this.icon = icon;
+ this.noCache = noCache;
+ if (StringUtils.ishttp(link)) {
+ this.link = link;
+ }
+ if (StringUtils.startWithAnyIgnoreCase(activeMenu, "/")) {
+ this.activeMenu = activeMenu;
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/ProfileUserVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/ProfileUserVo.java
new file mode 100755
index 0000000..70fc5d3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/ProfileUserVo.java
@@ -0,0 +1,90 @@
+package org.dromara.system.domain.vo;
+
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 鐢ㄦ埛淇℃伅瑙嗗浘瀵硅薄 sys_user
+ *
+ * @author Lion Li
+ */
+@Data
+public class ProfileUserVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ private Long userId;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ private Long deptId;
+
+ /**
+ * 鐢ㄦ埛璐﹀彿
+ */
+ private String userName;
+
+ /**
+ * 鐢ㄦ埛鏄电О
+ */
+ private String nickName;
+
+ /**
+ * 鐢ㄦ埛绫诲瀷锛坰ys_user绯荤粺鐢ㄦ埛锛�
+ */
+ private String userType;
+
+ /**
+ * 鐢ㄦ埛閭
+ */
+ private String email;
+
+ /**
+ * 鎵嬫満鍙风爜
+ */
+ private String phonenumber;
+
+ /**
+ * 鐢ㄦ埛鎬у埆锛�0鐢� 1濂� 2鏈煡锛�
+ */
+ private String sex;
+
+ /**
+ * 澶村儚鍦板潃
+ */
+ @Translation(type = TransConstant.OSS_ID_TO_URL)
+ private Long avatar;
+
+ /**
+ * 鏈�鍚庣櫥褰旾P
+ */
+ private String loginIp;
+
+ /**
+ * 鏈�鍚庣櫥褰曟椂闂�
+ */
+ private Date loginDate;
+
+ /**
+ * 閮ㄩ棬鍚�
+ */
+ @Translation(type = TransConstant.DEPT_ID_TO_NAME, mapper = "deptId")
+ private String deptName;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/RouterVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/RouterVo.java
new file mode 100755
index 0000000..d56e09d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/RouterVo.java
@@ -0,0 +1,62 @@
+package org.dromara.system.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 璺敱閰嶇疆淇℃伅
+ *
+ * @author Lion Li
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public class RouterVo {
+
+ /**
+ * 璺敱鍚嶅瓧
+ */
+ private String name;
+
+ /**
+ * 璺敱鍦板潃
+ */
+ private String path;
+
+ /**
+ * 鏄惁闅愯棌璺敱锛屽綋璁剧疆 true 鐨勬椂鍊欒璺敱涓嶄細鍐嶄晶杈规爮鍑虹幇
+ */
+ private Boolean hidden;
+
+ /**
+ * 閲嶅畾鍚戝湴鍧�锛屽綋璁剧疆 noRedirect 鐨勬椂鍊欒璺敱鍦ㄩ潰鍖呭睉瀵艰埅涓笉鍙鐐瑰嚮
+ */
+ private String redirect;
+
+ /**
+ * 缁勪欢鍦板潃
+ */
+ private String component;
+
+ /**
+ * 璺敱鍙傛暟锛氬 {"id": 1, "name": "ry"}
+ */
+ private String query;
+
+ /**
+ * 褰撲綘涓�涓矾鐢变笅闈㈢殑 children 澹版槑鐨勮矾鐢卞ぇ浜�1涓椂锛岃嚜鍔ㄤ細鍙樻垚宓屽鐨勬ā寮�--濡傜粍浠堕〉闈�
+ */
+ private Boolean alwaysShow;
+
+ /**
+ * 鍏朵粬鍏冪礌
+ */
+ private MetaVo meta;
+
+ /**
+ * 瀛愯矾鐢�
+ */
+ private List<RouterVo> children;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysClientVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysClientVo.java
new file mode 100755
index 0000000..82b0f46
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysClientVo.java
@@ -0,0 +1,90 @@
+package org.dromara.system.domain.vo;
+
+import org.dromara.system.domain.SysClient;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+
+/**
+ * 鎺堟潈绠$悊瑙嗗浘瀵硅薄 sys_client
+ *
+ * @author Michelle.Chung
+ * @date 2023-05-15
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysClient.class)
+public class SysClientVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * id
+ */
+ @ExcelProperty(value = "id")
+ private Long id;
+
+ /**
+ * 瀹㈡埛绔痠d
+ */
+ @ExcelProperty(value = "瀹㈡埛绔痠d")
+ private String clientId;
+
+ /**
+ * 瀹㈡埛绔痥ey
+ */
+ @ExcelProperty(value = "瀹㈡埛绔痥ey")
+ private String clientKey;
+
+ /**
+ * 瀹㈡埛绔閽�
+ */
+ @ExcelProperty(value = "瀹㈡埛绔閽�")
+ private String clientSecret;
+
+ /**
+ * 鎺堟潈绫诲瀷
+ */
+ @ExcelProperty(value = "鎺堟潈绫诲瀷")
+ private List<String> grantTypeList;
+
+ /**
+ * 鎺堟潈绫诲瀷
+ */
+ private String grantType;
+
+ /**
+ * 璁惧绫诲瀷
+ */
+ private String deviceType;
+
+ /**
+ * token娲昏穬瓒呮椂鏃堕棿
+ */
+ @ExcelProperty(value = "token娲昏穬瓒呮椂鏃堕棿")
+ private Long activeTimeout;
+
+ /**
+ * token鍥哄畾瓒呮椂鏃堕棿
+ */
+ @ExcelProperty(value = "token鍥哄畾瓒呮椂鏃堕棿")
+ private Long timeout;
+
+ /**
+ * 鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ @ExcelProperty(value = "鐘舵��", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(readConverterExp = "0=姝e父,1=鍋滅敤")
+ private String status;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysConfigVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysConfigVo.java
new file mode 100755
index 0000000..a35e132
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysConfigVo.java
@@ -0,0 +1,72 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.system.domain.SysConfig;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 鍙傛暟閰嶇疆瑙嗗浘瀵硅薄 sys_config
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysConfig.class)
+public class SysConfigVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鍙傛暟涓婚敭
+ */
+ @ExcelProperty(value = "鍙傛暟涓婚敭")
+ private Long configId;
+
+ /**
+ * 鍙傛暟鍚嶇О
+ */
+ @ExcelProperty(value = "鍙傛暟鍚嶇О")
+ private String configName;
+
+ /**
+ * 鍙傛暟閿悕
+ */
+ @ExcelProperty(value = "鍙傛暟閿悕")
+ private String configKey;
+
+ /**
+ * 鍙傛暟閿��
+ */
+ @ExcelProperty(value = "鍙傛暟閿��")
+ private String configValue;
+
+ /**
+ * 绯荤粺鍐呯疆锛圷鏄� N鍚︼級
+ */
+ @ExcelProperty(value = "绯荤粺鍐呯疆", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_yes_no")
+ private String configType;
+
+ /**
+ * 澶囨敞
+ */
+ @ExcelProperty(value = "澶囨敞")
+ private String remark;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ @ExcelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDeptVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDeptVo.java
new file mode 100755
index 0000000..c9f5a1f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDeptVo.java
@@ -0,0 +1,109 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.system.domain.SysDept;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 閮ㄩ棬瑙嗗浘瀵硅薄 sys_dept
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysDept.class)
+public class SysDeptVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 閮ㄩ棬id
+ */
+ @ExcelProperty(value = "閮ㄩ棬id")
+ private Long deptId;
+
+ /**
+ * 鐖堕儴闂╥d
+ */
+ private Long parentId;
+
+ /**
+ * 鐖堕儴闂ㄥ悕绉�
+ */
+ private String parentName;
+
+ /**
+ * 绁栫骇鍒楄〃
+ */
+ private String ancestors;
+
+ /**
+ * 閮ㄩ棬鍚嶇О
+ */
+ @ExcelProperty(value = "閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ /**
+ * 閮ㄩ棬绫诲埆缂栫爜
+ */
+ @ExcelProperty(value = "閮ㄩ棬绫诲埆缂栫爜")
+ private String deptCategory;
+
+ /**
+ * 鏄剧ず椤哄簭
+ */
+ private Integer orderNum;
+
+ /**
+ * 璐熻矗浜篒D
+ */
+ private Long leader;
+
+ /**
+ * 璐熻矗浜�
+ */
+ @ExcelProperty(value = "璐熻矗浜�")
+ private String leaderName;
+
+ /**
+ * 鑱旂郴鐢佃瘽
+ */
+ @ExcelProperty(value = "鑱旂郴鐢佃瘽")
+ private String phone;
+
+ /**
+ * 閭
+ */
+ @ExcelProperty(value = "閭")
+ private String email;
+
+ /**
+ * 閮ㄩ棬鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ @ExcelProperty(value = "閮ㄩ棬鐘舵��", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_normal_disable")
+ private String status;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ @ExcelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+
+ /**
+ * 瀛愰儴闂�
+ */
+ private List<SysDept> children = new ArrayList<>();
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDictDataVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDictDataVo.java
new file mode 100755
index 0000000..d97c00d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDictDataVo.java
@@ -0,0 +1,88 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.system.domain.SysDictData;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 瀛楀吀鏁版嵁瑙嗗浘瀵硅薄 sys_dict_data
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysDictData.class)
+public class SysDictDataVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 瀛楀吀缂栫爜
+ */
+ @ExcelProperty(value = "瀛楀吀缂栫爜")
+ private Long dictCode;
+
+ /**
+ * 瀛楀吀鎺掑簭
+ */
+ @ExcelProperty(value = "瀛楀吀鎺掑簭")
+ private Integer dictSort;
+
+ /**
+ * 瀛楀吀鏍囩
+ */
+ @ExcelProperty(value = "瀛楀吀鏍囩")
+ private String dictLabel;
+
+ /**
+ * 瀛楀吀閿��
+ */
+ @ExcelProperty(value = "瀛楀吀閿��")
+ private String dictValue;
+
+ /**
+ * 瀛楀吀绫诲瀷
+ */
+ @ExcelProperty(value = "瀛楀吀绫诲瀷")
+ private String dictType;
+
+ /**
+ * 鏍峰紡灞炴�э紙鍏朵粬鏍峰紡鎵╁睍锛�
+ */
+ private String cssClass;
+
+ /**
+ * 琛ㄦ牸鍥炴樉鏍峰紡
+ */
+ private String listClass;
+
+ /**
+ * 鏄惁榛樿锛圷鏄� N鍚︼級
+ */
+ @ExcelProperty(value = "鏄惁榛樿", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_yes_no")
+ private String isDefault;
+
+ /**
+ * 澶囨敞
+ */
+ @ExcelProperty(value = "澶囨敞")
+ private String remark;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ @ExcelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDictTypeVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDictTypeVo.java
new file mode 100755
index 0000000..4b62226
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDictTypeVo.java
@@ -0,0 +1,59 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.system.domain.SysDictType;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 瀛楀吀绫诲瀷瑙嗗浘瀵硅薄 sys_dict_type
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysDictType.class)
+public class SysDictTypeVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 瀛楀吀涓婚敭
+ */
+ @ExcelProperty(value = "瀛楀吀涓婚敭")
+ private Long dictId;
+
+ /**
+ * 瀛楀吀鍚嶇О
+ */
+ @ExcelProperty(value = "瀛楀吀鍚嶇О")
+ private String dictName;
+
+ /**
+ * 瀛楀吀绫诲瀷
+ */
+ @ExcelProperty(value = "瀛楀吀绫诲瀷")
+ private String dictType;
+
+ /**
+ * 澶囨敞
+ */
+ @ExcelProperty(value = "澶囨敞")
+ private String remark;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ @ExcelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysLogininforVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysLogininforVo.java
new file mode 100755
index 0000000..3086aa7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysLogininforVo.java
@@ -0,0 +1,106 @@
+package org.dromara.system.domain.vo;
+
+import java.util.Date;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.system.domain.SysLogininfor;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+
+/**
+ * 绯荤粺璁块棶璁板綍瑙嗗浘瀵硅薄 sys_logininfor
+ *
+ * @author Michelle.Chung
+ * @date 2023-02-07
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysLogininfor.class)
+public class SysLogininforVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 璁块棶ID
+ */
+ @ExcelProperty(value = "搴忓彿")
+ private Long infoId;
+
+ /**
+ * 绉熸埛缂栧彿
+ */
+ private String tenantId;
+
+ /**
+ * 鐢ㄦ埛璐﹀彿
+ */
+ @ExcelProperty(value = "鐢ㄦ埛璐﹀彿")
+ private String userName;
+
+ /**
+ * 瀹㈡埛绔�
+ */
+ @ExcelProperty(value = "瀹㈡埛绔�")
+ private String clientKey;
+
+ /**
+ * 璁惧绫诲瀷
+ */
+ @ExcelProperty(value = "璁惧绫诲瀷", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_device_type")
+ private String deviceType;
+
+ /**
+ * 鐧诲綍鐘舵�侊紙0鎴愬姛 1澶辫触锛�
+ */
+ @ExcelProperty(value = "鐧诲綍鐘舵��", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_common_status")
+ private String status;
+
+ /**
+ * 鐧诲綍IP鍦板潃
+ */
+ @ExcelProperty(value = "鐧诲綍鍦板潃")
+ private String ipaddr;
+
+ /**
+ * 鐧诲綍鍦扮偣
+ */
+ @ExcelProperty(value = "鐧诲綍鍦扮偣")
+ private String loginLocation;
+
+ /**
+ * 娴忚鍣ㄧ被鍨�
+ */
+ @ExcelProperty(value = "娴忚鍣�")
+ private String browser;
+
+ /**
+ * 鎿嶄綔绯荤粺
+ */
+ @ExcelProperty(value = "鎿嶄綔绯荤粺")
+ private String os;
+
+
+ /**
+ * 鎻愮ず娑堟伅
+ */
+ @ExcelProperty(value = "鎻愮ず娑堟伅")
+ private String msg;
+
+ /**
+ * 璁块棶鏃堕棿
+ */
+ @ExcelProperty(value = "璁块棶鏃堕棿")
+ private Date loginTime;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysMenuVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysMenuVo.java
new file mode 100755
index 0000000..5214a33
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysMenuVo.java
@@ -0,0 +1,116 @@
+package org.dromara.system.domain.vo;
+
+import org.dromara.system.domain.SysMenu;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+
+/**
+ * 鑿滃崟鏉冮檺瑙嗗浘瀵硅薄 sys_menu
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@AutoMapper(target = SysMenu.class)
+public class SysMenuVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鑿滃崟ID
+ */
+ private Long menuId;
+
+ /**
+ * 鑿滃崟鍚嶇О
+ */
+ private String menuName;
+
+ /**
+ * 鐖惰彍鍗旾D
+ */
+ private Long parentId;
+
+ /**
+ * 鏄剧ず椤哄簭
+ */
+ private Integer orderNum;
+
+ /**
+ * 璺敱鍦板潃
+ */
+ private String path;
+
+ /**
+ * 缁勪欢璺緞
+ */
+ private String component;
+
+ /**
+ * 璺敱鍙傛暟
+ */
+ private String queryParam;
+
+ /**
+ * 鏄惁涓哄閾撅紙0鏄� 1鍚︼級
+ */
+ private String isFrame;
+
+ /**
+ * 鏄惁缂撳瓨锛�0缂撳瓨 1涓嶇紦瀛橈級
+ */
+ private String isCache;
+
+ /**
+ * 鑿滃崟绫诲瀷锛圡鐩綍 C鑿滃崟 F鎸夐挳锛�
+ */
+ private String menuType;
+
+ /**
+ * 鏄剧ず鐘舵�侊紙0鏄剧ず 1闅愯棌锛�
+ */
+ private String visible;
+
+ /**
+ * 鑿滃崟鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 鏉冮檺鏍囪瘑
+ */
+ private String perms;
+
+ /**
+ * 鑿滃崟鍥炬爣
+ */
+ private String icon;
+
+ /**
+ * 鍒涘缓閮ㄩ棬
+ */
+ private Long createDept;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ private Date createTime;
+
+ /**
+ * 瀛愯彍鍗�
+ */
+ private List<SysMenuVo> children = new ArrayList<>();
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysNoticeVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysNoticeVo.java
new file mode 100755
index 0000000..afe7367
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysNoticeVo.java
@@ -0,0 +1,73 @@
+package org.dromara.system.domain.vo;
+
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.system.domain.SysNotice;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 閫氱煡鍏憡瑙嗗浘瀵硅薄 sys_notice
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@AutoMapper(target = SysNotice.class)
+public class SysNoticeVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鍏憡ID
+ */
+ private Long noticeId;
+
+ /**
+ * 鍏憡鏍囬
+ */
+ private String noticeTitle;
+
+ /**
+ * 鍏憡绫诲瀷锛�1閫氱煡 2鍏憡锛�
+ */
+ private String noticeType;
+
+ /**
+ * 鍏憡鍐呭
+ */
+ private String noticeContent;
+
+ /**
+ * 鍏憡鐘舵�侊紙0姝e父 1鍏抽棴锛�
+ */
+ private String status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鍒涘缓鑰�
+ */
+ private Long createBy;
+
+ /**
+ * 鍒涘缓浜哄悕绉�
+ */
+ @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy")
+ private String createByName;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ private Date createTime;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOperLogVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOperLogVo.java
new file mode 100755
index 0000000..00b3344
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOperLogVo.java
@@ -0,0 +1,144 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.system.domain.SysOperLog;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 鎿嶄綔鏃ュ織璁板綍瑙嗗浘瀵硅薄 sys_oper_log
+ *
+ * @author Michelle.Chung
+ * @date 2023-02-07
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysOperLog.class)
+public class SysOperLogVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏃ュ織涓婚敭
+ */
+ @ExcelProperty(value = "鏃ュ織涓婚敭")
+ private Long operId;
+
+ /**
+ * 绉熸埛缂栧彿
+ */
+ private String tenantId;
+
+ /**
+ * 妯″潡鏍囬
+ */
+ @ExcelProperty(value = "鎿嶄綔妯″潡")
+ private String title;
+
+ /**
+ * 涓氬姟绫诲瀷锛�0鍏跺畠 1鏂板 2淇敼 3鍒犻櫎锛�
+ */
+ @ExcelProperty(value = "涓氬姟绫诲瀷", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_oper_type")
+ private Integer businessType;
+
+ /**
+ * 涓氬姟绫诲瀷鏁扮粍
+ */
+ private Integer[] businessTypes;
+
+ /**
+ * 鏂规硶鍚嶇О
+ */
+ @ExcelProperty(value = "璇锋眰鏂规硶")
+ private String method;
+
+ /**
+ * 璇锋眰鏂瑰紡
+ */
+ @ExcelProperty(value = "璇锋眰鏂瑰紡")
+ private String requestMethod;
+
+ /**
+ * 鎿嶄綔绫诲埆锛�0鍏跺畠 1鍚庡彴鐢ㄦ埛 2鎵嬫満绔敤鎴凤級
+ */
+ @ExcelProperty(value = "鎿嶄綔绫诲埆", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(readConverterExp = "0=鍏跺畠,1=鍚庡彴鐢ㄦ埛,2=鎵嬫満绔敤鎴�")
+ private Integer operatorType;
+
+ /**
+ * 鎿嶄綔浜哄憳
+ */
+ @ExcelProperty(value = "鎿嶄綔浜哄憳")
+ private String operName;
+
+ /**
+ * 閮ㄩ棬鍚嶇О
+ */
+ @ExcelProperty(value = "閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ /**
+ * 璇锋眰URL
+ */
+ @ExcelProperty(value = "璇锋眰鍦板潃")
+ private String operUrl;
+
+ /**
+ * 涓绘満鍦板潃
+ */
+ @ExcelProperty(value = "鎿嶄綔鍦板潃")
+ private String operIp;
+
+ /**
+ * 鎿嶄綔鍦扮偣
+ */
+ @ExcelProperty(value = "鎿嶄綔鍦扮偣")
+ private String operLocation;
+
+ /**
+ * 璇锋眰鍙傛暟
+ */
+ @ExcelProperty(value = "璇锋眰鍙傛暟")
+ private String operParam;
+
+ /**
+ * 杩斿洖鍙傛暟
+ */
+ @ExcelProperty(value = "杩斿洖鍙傛暟")
+ private String jsonResult;
+
+ /**
+ * 鎿嶄綔鐘舵�侊紙0姝e父 1寮傚父锛�
+ */
+ @ExcelProperty(value = "鐘舵��", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_common_status")
+ private Integer status;
+
+ /**
+ * 閿欒娑堟伅
+ */
+ @ExcelProperty(value = "閿欒娑堟伅")
+ private String errorMsg;
+
+ /**
+ * 鎿嶄綔鏃堕棿
+ */
+ @ExcelProperty(value = "鎿嶄綔鏃堕棿")
+ private Date operTime;
+
+ /**
+ * 娑堣�楁椂闂�
+ */
+ @ExcelProperty(value = "娑堣�楁椂闂�")
+ private Long costTime;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssConfigVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssConfigVo.java
new file mode 100755
index 0000000..a030722
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssConfigVo.java
@@ -0,0 +1,97 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import org.dromara.system.domain.SysOssConfig;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 瀵硅薄瀛樺偍閰嶇疆瑙嗗浘瀵硅薄 sys_oss_config
+ *
+ * @author Lion Li
+ * @author 瀛よ垷鐑熼洦
+ * @date 2021-08-13
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysOssConfig.class)
+public class SysOssConfigVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭
+ */
+ private Long ossConfigId;
+
+ /**
+ * 閰嶇疆key
+ */
+ private String configKey;
+
+ /**
+ * accessKey
+ */
+ private String accessKey;
+
+ /**
+ * 绉橀挜
+ */
+ private String secretKey;
+
+ /**
+ * 妗跺悕绉�
+ */
+ private String bucketName;
+
+ /**
+ * 鍓嶇紑
+ */
+ private String prefix;
+
+ /**
+ * 璁块棶绔欑偣
+ */
+ private String endpoint;
+
+ /**
+ * 鑷畾涔夊煙鍚�
+ */
+ private String domain;
+
+ /**
+ * 鏄惁https锛圷=鏄�,N=鍚︼級
+ */
+ private String isHttps;
+
+ /**
+ * 鍩�
+ */
+ private String region;
+
+ /**
+ * 鏄惁榛樿锛�0=鏄�,1=鍚︼級
+ */
+ private String status;
+
+ /**
+ * 鎵╁睍瀛楁
+ */
+ private String ext1;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 妗舵潈闄愮被鍨�(0private 1public 2custom)
+ */
+ private String accessPolicy;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssUploadVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssUploadVo.java
new file mode 100755
index 0000000..11e0ff8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssUploadVo.java
@@ -0,0 +1,28 @@
+package org.dromara.system.domain.vo;
+
+import lombok.Data;
+
+/**
+ * 涓婁紶瀵硅薄淇℃伅
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class SysOssUploadVo {
+
+ /**
+ * URL鍦板潃
+ */
+ private String url;
+
+ /**
+ * 鏂囦欢鍚�
+ */
+ private String fileName;
+
+ /**
+ * 瀵硅薄瀛樺偍涓婚敭
+ */
+ private String ossId;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssVo.java
new file mode 100755
index 0000000..fe05651
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssVo.java
@@ -0,0 +1,77 @@
+package org.dromara.system.domain.vo;
+
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.system.domain.SysOss;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * OSS瀵硅薄瀛樺偍瑙嗗浘瀵硅薄 sys_oss
+ *
+ * @author Lion Li
+ */
+@Data
+@AutoMapper(target = SysOss.class)
+public class SysOssVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 瀵硅薄瀛樺偍涓婚敭
+ */
+ private Long ossId;
+
+ /**
+ * 鏂囦欢鍚�
+ */
+ private String fileName;
+
+ /**
+ * 鍘熷悕
+ */
+ private String originalName;
+
+ /**
+ * 鏂囦欢鍚庣紑鍚�
+ */
+ private String fileSuffix;
+
+ /**
+ * URL鍦板潃
+ */
+ private String url;
+
+ /**
+ * 鎵╁睍瀛楁
+ */
+ private String ext1;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ private Date createTime;
+
+ /**
+ * 涓婁紶浜�
+ */
+ private Long createBy;
+
+ /**
+ * 涓婁紶浜哄悕绉�
+ */
+ @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy")
+ private String createByName;
+
+ /**
+ * 鏈嶅姟鍟�
+ */
+ private String service;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysPostVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysPostVo.java
new file mode 100755
index 0000000..50140b6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysPostVo.java
@@ -0,0 +1,91 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.system.domain.SysPost;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 宀椾綅淇℃伅瑙嗗浘瀵硅薄 sys_post
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysPost.class)
+public class SysPostVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 宀椾綅ID
+ */
+ @ExcelProperty(value = "宀椾綅搴忓彿")
+ private Long postId;
+
+ /**
+ * 閮ㄩ棬id
+ */
+ @ExcelProperty(value = "閮ㄩ棬id")
+ private Long deptId;
+
+ /**
+ * 宀椾綅缂栫爜
+ */
+ @ExcelProperty(value = "宀椾綅缂栫爜")
+ private String postCode;
+
+ /**
+ * 宀椾綅鍚嶇О
+ */
+ @ExcelProperty(value = "宀椾綅鍚嶇О")
+ private String postName;
+
+ /**
+ * 宀椾綅绫诲埆缂栫爜
+ */
+ @ExcelProperty(value = "绫诲埆缂栫爜")
+ private String postCategory;
+
+ /**
+ * 鏄剧ず椤哄簭
+ */
+ @ExcelProperty(value = "宀椾綅鎺掑簭")
+ private Integer postSort;
+
+ /**
+ * 鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ @ExcelProperty(value = "鐘舵��", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_normal_disable")
+ private String status;
+
+ /**
+ * 澶囨敞
+ */
+ @ExcelProperty(value = "澶囨敞")
+ private String remark;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ @ExcelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+
+ /**
+ * 閮ㄩ棬鍚�
+ */
+ @Translation(type = TransConstant.DEPT_ID_TO_NAME, mapper = "deptId")
+ private String deptName;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysRoleVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysRoleVo.java
new file mode 100755
index 0000000..1a205cc
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysRoleVo.java
@@ -0,0 +1,100 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.system.domain.SysRole;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 瑙掕壊淇℃伅瑙嗗浘瀵硅薄 sys_role
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysRole.class)
+public class SysRoleVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 瑙掕壊ID
+ */
+ @ExcelProperty(value = "瑙掕壊搴忓彿")
+ private Long roleId;
+
+ /**
+ * 瑙掕壊鍚嶇О
+ */
+ @ExcelProperty(value = "瑙掕壊鍚嶇О")
+ private String roleName;
+
+ /**
+ * 瑙掕壊鏉冮檺瀛楃涓�
+ */
+ @ExcelProperty(value = "瑙掕壊鏉冮檺")
+ private String roleKey;
+
+ /**
+ * 鏄剧ず椤哄簭
+ */
+ @ExcelProperty(value = "瑙掕壊鎺掑簭")
+ private Integer roleSort;
+
+ /**
+ * 鏁版嵁鑼冨洿锛�1锛氬叏閮ㄦ暟鎹潈闄� 2锛氳嚜瀹氭暟鎹潈闄� 3锛氭湰閮ㄩ棬鏁版嵁鏉冮檺 4锛氭湰閮ㄩ棬鍙婁互涓嬫暟鎹潈闄� 5锛氫粎鏈汉鏁版嵁鏉冮檺 6锛氶儴闂ㄥ強浠ヤ笅鎴栨湰浜烘暟鎹潈闄愶級
+ */
+ @ExcelProperty(value = "鏁版嵁鑼冨洿", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(readConverterExp = "1=鍏ㄩ儴鏁版嵁鏉冮檺,2=鑷畾涔夋暟鎹潈闄�,3=鏈儴闂ㄦ暟鎹潈闄�,4=鏈儴闂ㄥ強浠ヤ笅鏁版嵁鏉冮檺,5=浠呮湰浜烘暟鎹潈闄�,6=閮ㄩ棬鍙婁互涓嬫垨鏈汉鏁版嵁鏉冮檺")
+ private String dataScope;
+
+ /**
+ * 鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�
+ */
+ @ExcelProperty(value = "鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�")
+ private Boolean menuCheckStrictly;
+
+ /**
+ * 閮ㄩ棬鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�
+ */
+ @ExcelProperty(value = "閮ㄩ棬鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�")
+ private Boolean deptCheckStrictly;
+
+ /**
+ * 瑙掕壊鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ @ExcelProperty(value = "瑙掕壊鐘舵��", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_normal_disable")
+ private String status;
+
+ /**
+ * 澶囨敞
+ */
+ @ExcelProperty(value = "澶囨敞")
+ private String remark;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ @ExcelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+
+ /**
+ * 鐢ㄦ埛鏄惁瀛樺湪姝よ鑹叉爣璇� 榛樿涓嶅瓨鍦�
+ */
+ private boolean flag = false;
+
+ public boolean isSuperAdmin() {
+ return SystemConstants.SUPER_ADMIN_ID.equals(this.roleId);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysSocialVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysSocialVo.java
new file mode 100755
index 0000000..948dbcc
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysSocialVo.java
@@ -0,0 +1,144 @@
+package org.dromara.system.domain.vo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.system.domain.SysSocial;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 绀句細鍖栧叧绯昏鍥惧璞� sys_social
+ *
+ * @author thiszhc
+ */
+@Data
+@AutoMapper(target = SysSocial.class)
+public class SysSocialVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭
+ */
+ private Long id;
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ private Long userId;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 鐨勫敮涓�ID
+ */
+ private String authId;
+
+ /**
+ * 鐢ㄦ埛鏉ユ簮
+ */
+ private String source;
+
+ /**
+ * 鐢ㄦ埛鐨勬巿鏉冧护鐗�
+ */
+ private String accessToken;
+
+ /**
+ * 鐢ㄦ埛鐨勬巿鏉冧护鐗岀殑鏈夋晥鏈燂紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private int expireIn;
+
+ /**
+ * 鍒锋柊浠ょ墝锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�
+ */
+ private String refreshToken;
+
+ /**
+ * 鐢ㄦ埛鐨� open id
+ */
+ private String openId;
+
+ /**
+ * 鎺堟潈鐨勭涓夋柟璐﹀彿
+ */
+ private String userName;
+
+ /**
+ * 鎺堟潈鐨勭涓夋柟鏄电О
+ */
+ private String nickName;
+
+ /**
+ * 鎺堟潈鐨勭涓夋柟閭
+ */
+ private String email;
+
+ /**
+ * 鎺堟潈鐨勭涓夋柟澶村儚鍦板潃
+ */
+ private String avatar;
+
+
+ /**
+ * 骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String accessCode;
+
+ /**
+ * 鐢ㄦ埛鐨� unionid
+ */
+ private String unionId;
+
+ /**
+ * 鎺堜簣鐨勬潈闄愶紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String scope;
+
+ /**
+ * 涓埆骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String tokenType;
+
+ /**
+ * id token锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�
+ */
+ private String idToken;
+
+ /**
+ * 灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String macAlgorithm;
+
+ /**
+ * 灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String macKey;
+
+ /**
+ * 鐢ㄦ埛鐨勬巿鏉僣ode锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�
+ */
+ private String code;
+
+ /**
+ * Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String oauthToken;
+
+ /**
+ * Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁
+ */
+ private String oauthTokenSecret;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ private Date createTime;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysTenantPackageVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysTenantPackageVo.java
new file mode 100755
index 0000000..ead9229
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysTenantPackageVo.java
@@ -0,0 +1,66 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.system.domain.SysTenantPackage;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 绉熸埛濂楅瑙嗗浘瀵硅薄 sys_tenant_package
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysTenantPackage.class)
+public class SysTenantPackageVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 绉熸埛濂楅id
+ */
+ @ExcelProperty(value = "绉熸埛濂楅id")
+ private Long packageId;
+
+ /**
+ * 濂楅鍚嶇О
+ */
+ @ExcelProperty(value = "濂楅鍚嶇О")
+ private String packageName;
+
+ /**
+ * 鍏宠仈鑿滃崟id
+ */
+ @ExcelProperty(value = "鍏宠仈鑿滃崟id")
+ private String menuIds;
+
+ /**
+ * 澶囨敞
+ */
+ @ExcelProperty(value = "澶囨敞")
+ private String remark;
+
+ /**
+ * 鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�
+ */
+ @ExcelProperty(value = "鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�")
+ private Boolean menuCheckStrictly;
+
+ /**
+ * 鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ @ExcelProperty(value = "鐘舵��", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(readConverterExp = "0=姝e父,1=鍋滅敤")
+ private String status;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysTenantVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysTenantVo.java
new file mode 100755
index 0000000..021c664
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysTenantVo.java
@@ -0,0 +1,115 @@
+package org.dromara.system.domain.vo;
+
+import java.util.Date;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.system.domain.SysTenant;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 绉熸埛瑙嗗浘瀵硅薄 sys_tenant
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysTenant.class)
+public class SysTenantVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * id
+ */
+ @ExcelProperty(value = "id")
+ private Long id;
+
+ /**
+ * 绉熸埛缂栧彿
+ */
+ @ExcelProperty(value = "绉熸埛缂栧彿")
+ private String tenantId;
+
+ /**
+ * 鑱旂郴浜�
+ */
+ @ExcelProperty(value = "鑱旂郴浜�")
+ private String contactUserName;
+
+ /**
+ * 鑱旂郴鐢佃瘽
+ */
+ @ExcelProperty(value = "鑱旂郴鐢佃瘽")
+ private String contactPhone;
+
+ /**
+ * 浼佷笟鍚嶇О
+ */
+ @ExcelProperty(value = "浼佷笟鍚嶇О")
+ private String companyName;
+
+ /**
+ * 缁熶竴绀句細淇$敤浠g爜
+ */
+ @ExcelProperty(value = "缁熶竴绀句細淇$敤浠g爜")
+ private String licenseNumber;
+
+ /**
+ * 鍦板潃
+ */
+ @ExcelProperty(value = "鍦板潃")
+ private String address;
+
+ /**
+ * 鍩熷悕
+ */
+ @ExcelProperty(value = "鍩熷悕")
+ private String domain;
+
+ /**
+ * 浼佷笟绠�浠�
+ */
+ @ExcelProperty(value = "浼佷笟绠�浠�")
+ private String intro;
+
+ /**
+ * 澶囨敞
+ */
+ @ExcelProperty(value = "澶囨敞")
+ private String remark;
+
+ /**
+ * 绉熸埛濂楅缂栧彿
+ */
+ @ExcelProperty(value = "绉熸埛濂楅缂栧彿")
+ private Long packageId;
+
+ /**
+ * 杩囨湡鏃堕棿
+ */
+ @ExcelProperty(value = "杩囨湡鏃堕棿")
+ private Date expireTime;
+
+ /**
+ * 鐢ㄦ埛鏁伴噺锛�-1涓嶉檺鍒讹級
+ */
+ @ExcelProperty(value = "鐢ㄦ埛鏁伴噺")
+ private Long accountCount;
+
+ /**
+ * 绉熸埛鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ @ExcelProperty(value = "绉熸埛鐘舵��", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(readConverterExp = "0=姝e父,1=鍋滅敤")
+ private String status;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserExportVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserExportVo.java
new file mode 100755
index 0000000..c600872
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserExportVo.java
@@ -0,0 +1,96 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import io.github.linpeilie.annotations.ReverseAutoMapping;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 鐢ㄦ埛瀵硅薄瀵煎嚭VO
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+public class SysUserExportVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ @ExcelProperty(value = "鐢ㄦ埛搴忓彿")
+ private Long userId;
+
+ /**
+ * 鐢ㄦ埛璐﹀彿
+ */
+ @ExcelProperty(value = "鐧诲綍鍚嶇О")
+ private String userName;
+
+ /**
+ * 鐢ㄦ埛鏄电О
+ */
+ @ExcelProperty(value = "鐢ㄦ埛鍚嶇О")
+ private String nickName;
+
+ /**
+ * 鐢ㄦ埛閭
+ */
+ @ExcelProperty(value = "鐢ㄦ埛閭")
+ private String email;
+
+ /**
+ * 鎵嬫満鍙风爜
+ */
+ @ExcelProperty(value = "鎵嬫満鍙风爜")
+ private String phonenumber;
+
+ /**
+ * 鐢ㄦ埛鎬у埆
+ */
+ @ExcelProperty(value = "鐢ㄦ埛鎬у埆", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_user_sex")
+ private String sex;
+
+ /**
+ * 璐﹀彿鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ @ExcelProperty(value = "璐﹀彿鐘舵��", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_normal_disable")
+ private String status;
+
+ /**
+ * 鏈�鍚庣櫥褰旾P
+ */
+ @ExcelProperty(value = "鏈�鍚庣櫥褰旾P")
+ private String loginIp;
+
+ /**
+ * 鏈�鍚庣櫥褰曟椂闂�
+ */
+ @ExcelProperty(value = "鏈�鍚庣櫥褰曟椂闂�")
+ private Date loginDate;
+
+ /**
+ * 閮ㄩ棬鍚嶇О
+ */
+ @ExcelProperty(value = "閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ /**
+ * 璐熻矗浜�
+ */
+ @ExcelProperty(value = "閮ㄩ棬璐熻矗浜�")
+ private String leaderName;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserImportVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserImportVo.java
new file mode 100755
index 0000000..4507f63
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserImportVo.java
@@ -0,0 +1,76 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 鐢ㄦ埛瀵硅薄瀵煎叆VO
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+// @Accessors(chain = true) // 瀵煎叆涓嶅厑璁镐娇鐢� 浼氭壘涓嶅埌set鏂规硶
+public class SysUserImportVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ @ExcelProperty(value = "鐢ㄦ埛搴忓彿")
+ private Long userId;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ @ExcelProperty(value = "閮ㄩ棬缂栧彿")
+ private Long deptId;
+
+ /**
+ * 鐢ㄦ埛璐﹀彿
+ */
+ @ExcelProperty(value = "鐧诲綍鍚嶇О")
+ private String userName;
+
+ /**
+ * 鐢ㄦ埛鏄电О
+ */
+ @ExcelProperty(value = "鐢ㄦ埛鍚嶇О")
+ private String nickName;
+
+ /**
+ * 鐢ㄦ埛閭
+ */
+ @ExcelProperty(value = "鐢ㄦ埛閭")
+ private String email;
+
+ /**
+ * 鎵嬫満鍙风爜
+ */
+ @ExcelProperty(value = "鎵嬫満鍙风爜")
+ private String phonenumber;
+
+ /**
+ * 鐢ㄦ埛鎬у埆
+ */
+ @ExcelProperty(value = "鐢ㄦ埛鎬у埆", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_user_sex")
+ private String sex;
+
+ /**
+ * 璐﹀彿鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ @ExcelProperty(value = "璐﹀彿鐘舵��", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(dictType = "sys_normal_disable")
+ private String status;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserInfoVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserInfoVo.java
new file mode 100755
index 0000000..e41355d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserInfoVo.java
@@ -0,0 +1,40 @@
+package org.dromara.system.domain.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 鐢ㄦ埛淇℃伅
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class SysUserInfoVo {
+
+ /**
+ * 鐢ㄦ埛淇℃伅
+ */
+ private SysUserVo user;
+
+ /**
+ * 瑙掕壊ID鍒楄〃
+ */
+ private List<Long> roleIds;
+
+ /**
+ * 瑙掕壊鍒楄〃
+ */
+ private List<SysRoleVo> roles;
+
+ /**
+ * 宀椾綅ID鍒楄〃
+ */
+ private List<Long> postIds;
+
+ /**
+ * 宀椾綅鍒楄〃
+ */
+ private List<SysPostVo> posts;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java
new file mode 100755
index 0000000..755dcf8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java
@@ -0,0 +1,142 @@
+package org.dromara.system.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.dromara.common.sensitive.annotation.Sensitive;
+import org.dromara.common.sensitive.core.SensitiveStrategy;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.system.domain.SysUser;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+
+/**
+ * 鐢ㄦ埛淇℃伅瑙嗗浘瀵硅薄 sys_user
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@AutoMapper(target = SysUser.class)
+public class SysUserVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ private Long userId;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ private Long deptId;
+
+ /**
+ * 鐢ㄦ埛璐﹀彿
+ */
+ private String userName;
+
+ /**
+ * 鐢ㄦ埛鏄电О
+ */
+ private String nickName;
+
+ /**
+ * 鐢ㄦ埛绫诲瀷锛坰ys_user绯荤粺鐢ㄦ埛锛�
+ */
+ private String userType;
+
+ /**
+ * 鐢ㄦ埛閭
+ */
+ @Sensitive(strategy = SensitiveStrategy.EMAIL, perms = "system:user:edit")
+ private String email;
+
+ /**
+ * 鎵嬫満鍙风爜
+ */
+ @Sensitive(strategy = SensitiveStrategy.PHONE, perms = "system:user:edit")
+ private String phonenumber;
+
+ /**
+ * 鐢ㄦ埛鎬у埆锛�0鐢� 1濂� 2鏈煡锛�
+ */
+ private String sex;
+
+ /**
+ * 澶村儚鍦板潃
+ */
+ @Translation(type = TransConstant.OSS_ID_TO_URL)
+ private Long avatar;
+
+ /**
+ * 瀵嗙爜
+ */
+ @JsonIgnore
+ @JsonProperty
+ private String password;
+
+ /**
+ * 璐﹀彿鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 鏈�鍚庣櫥褰旾P
+ */
+ private String loginIp;
+
+ /**
+ * 鏈�鍚庣櫥褰曟椂闂�
+ */
+ private Date loginDate;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ private Date createTime;
+
+ /**
+ * 閮ㄩ棬鍚�
+ */
+ @Translation(type = TransConstant.DEPT_ID_TO_NAME, mapper = "deptId")
+ private String deptName;
+
+ /**
+ * 瑙掕壊瀵硅薄
+ */
+ private List<SysRoleVo> roles;
+
+ /**
+ * 瑙掕壊缁�
+ */
+ private Long[] roleIds;
+
+ /**
+ * 宀椾綅缁�
+ */
+ private Long[] postIds;
+
+ /**
+ * 鏁版嵁鏉冮檺 褰撳墠瑙掕壊ID
+ */
+ private Long roleId;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/UserInfoVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/UserInfoVo.java
new file mode 100755
index 0000000..48fa92a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/UserInfoVo.java
@@ -0,0 +1,30 @@
+package org.dromara.system.domain.vo;
+
+import lombok.Data;
+
+import java.util.Set;
+
+/**
+ * 鐧诲綍鐢ㄦ埛淇℃伅
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class UserInfoVo {
+
+ /**
+ * 鐢ㄦ埛鍩烘湰淇℃伅
+ */
+ private SysUserVo user;
+
+ /**
+ * 鑿滃崟鏉冮檺
+ */
+ private Set<String> permissions;
+
+ /**
+ * 瑙掕壊鏉冮檺
+ */
+ private Set<String> roles;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/SysUserImportListener.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/SysUserImportListener.java
new file mode 100755
index 0000000..66db5f5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/SysUserImportListener.java
@@ -0,0 +1,127 @@
+package org.dromara.system.listener;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.crypto.digest.BCrypt;
+import cn.hutool.http.HtmlUtil;
+import cn.idev.excel.context.AnalysisContext;
+import cn.idev.excel.event.AnalysisEventListener;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.excel.core.ExcelListener;
+import org.dromara.common.excel.core.ExcelResult;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.bo.SysUserBo;
+import org.dromara.system.domain.vo.SysUserImportVo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.service.ISysConfigService;
+import org.dromara.system.service.ISysUserService;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+
+/**
+ * 绯荤粺鐢ㄦ埛鑷畾涔夊鍏�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo> implements ExcelListener<SysUserImportVo> {
+
+ private final ISysUserService userService;
+
+ private final String password;
+
+ private final Boolean isUpdateSupport;
+
+ private final Long operUserId;
+
+ private int successNum = 0;
+ private int failureNum = 0;
+ private final StringBuilder successMsg = new StringBuilder();
+ private final StringBuilder failureMsg = new StringBuilder();
+
+ public SysUserImportListener(Boolean isUpdateSupport) {
+ String initPassword = SpringUtils.getBean(ISysConfigService.class).selectConfigByKey("sys.user.initPassword");
+ this.userService = SpringUtils.getBean(ISysUserService.class);
+ this.password = BCrypt.hashpw(initPassword);
+ this.isUpdateSupport = isUpdateSupport;
+ this.operUserId = LoginHelper.getUserId();
+ }
+
+ @Override
+ public void invoke(SysUserImportVo userVo, AnalysisContext context) {
+ SysUserVo sysUser = this.userService.selectUserByUserName(userVo.getUserName());
+ try {
+ // 楠岃瘉鏄惁瀛樺湪杩欎釜鐢ㄦ埛
+ if (ObjectUtil.isNull(sysUser)) {
+ SysUserBo user = BeanUtil.toBean(userVo, SysUserBo.class);
+ ValidatorUtils.validate(user);
+ user.setPassword(password);
+ user.setCreateBy(operUserId);
+ userService.insertUser(user);
+ successNum++;
+ successMsg.append("<br/>").append(successNum).append("銆佽处鍙� ").append(user.getUserName()).append(" 瀵煎叆鎴愬姛");
+ } else if (isUpdateSupport) {
+ Long userId = sysUser.getUserId();
+ SysUserBo user = BeanUtil.toBean(userVo, SysUserBo.class);
+ user.setUserId(userId);
+ ValidatorUtils.validate(user);
+ userService.checkUserAllowed(user.getUserId());
+ userService.checkUserDataScope(user.getUserId());
+ user.setUpdateBy(operUserId);
+ userService.updateUser(user);
+ successNum++;
+ successMsg.append("<br/>").append(successNum).append("銆佽处鍙� ").append(user.getUserName()).append(" 鏇存柊鎴愬姛");
+ } else {
+ failureNum++;
+ failureMsg.append("<br/>").append(failureNum).append("銆佽处鍙� ").append(sysUser.getUserName()).append(" 宸插瓨鍦�");
+ }
+ } catch (Exception e) {
+ failureNum++;
+ String msg = "<br/>" + failureNum + "銆佽处鍙� " + HtmlUtil.cleanHtmlTag(userVo.getUserName()) + " 瀵煎叆澶辫触锛�";
+ String message = e.getMessage();
+ if (e instanceof ConstraintViolationException cvException) {
+ message = StreamUtils.join(cvException.getConstraintViolations(), ConstraintViolation::getMessage, ", ");
+ }
+ failureMsg.append(msg).append(message);
+ log.error(msg, e);
+ }
+ }
+
+ @Override
+ public void doAfterAllAnalysed(AnalysisContext context) {
+
+ }
+
+ @Override
+ public ExcelResult<SysUserImportVo> getExcelResult() {
+ return new ExcelResult<>() {
+
+ @Override
+ public String getAnalysis() {
+ if (failureNum > 0) {
+ failureMsg.insert(0, "寰堟姳姝夛紝瀵煎叆澶辫触锛佸叡 " + failureNum + " 鏉℃暟鎹牸寮忎笉姝g‘锛岄敊璇涓嬶細");
+ throw new ServiceException(failureMsg.toString());
+ } else {
+ successMsg.insert(0, "鎭枩鎮紝鏁版嵁宸插叏閮ㄥ鍏ユ垚鍔燂紒鍏� " + successNum + " 鏉★紝鏁版嵁濡備笅锛�");
+ }
+ return successMsg.toString();
+ }
+
+ @Override
+ public List<SysUserImportVo> getList() {
+ return null;
+ }
+
+ @Override
+ public List<String> getErrorList() {
+ return null;
+ }
+ };
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysClientMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysClientMapper.java
new file mode 100755
index 0000000..15bcfb4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysClientMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.system.mapper;
+
+import org.dromara.system.domain.SysClient;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 鎺堟潈绠$悊Mapper鎺ュ彛
+ *
+ * @author Michelle.Chung
+ * @date 2023-05-15
+ */
+public interface SysClientMapper extends BaseMapperPlus<SysClient, SysClientVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysConfigMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysConfigMapper.java
new file mode 100755
index 0000000..0eaaee8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysConfigMapper.java
@@ -0,0 +1,14 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysConfig;
+import org.dromara.system.domain.vo.SysConfigVo;
+
+/**
+ * 鍙傛暟閰嶇疆 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysConfigMapper extends BaseMapperPlus<SysConfig, SysConfigVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDeptMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDeptMapper.java
new file mode 100755
index 0000000..80a99cf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDeptMapper.java
@@ -0,0 +1,143 @@
+package org.dromara.system.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.mybatis.annotation.DataColumn;
+import org.dromara.common.mybatis.annotation.DataPermission;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.common.mybatis.helper.DataBaseHelper;
+import org.dromara.system.domain.SysDept;
+import org.dromara.system.domain.vo.SysDeptVo;
+
+import java.util.List;
+
+/**
+ * 閮ㄩ棬绠$悊 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysDeptMapper extends BaseMapperPlus<SysDept, SysDeptVo> {
+
+ /**
+ * 鏋勫缓瑙掕壊瀵瑰簲鐨勯儴闂� SQL 鏌ヨ璇彞
+ *
+ * <p>璇� SQL 鐢ㄤ簬鏌ヨ鏌愪釜瑙掕壊鍏宠仈鐨勬墍鏈夐儴闂� ID锛屽父鐢ㄤ簬鏁版嵁鏉冮檺鎺у埗</p>
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 鏌ヨ閮ㄩ棬ID鐨� SQL 璇彞瀛楃涓�
+ */
+ default String buildDeptByRoleSql(Long roleId) {
+ return """
+ select srd.dept_id from sys_role_dept srd
+ left join sys_role sr on sr.role_id = srd.role_id
+ where srd.role_id = %d and sr.status = '0'
+ """.formatted(roleId);
+ }
+
+ /**
+ * 鏋勫缓 SQL 鏌ヨ锛岀敤浜庤幏鍙栧綋鍓嶈鑹叉嫢鏈夌殑閮ㄩ棬涓墍鏈夌殑鐖堕儴闂↖D
+ *
+ * <p>
+ * 璇� SQL 鐢ㄤ簬 deptCheckStrictly 鍦烘櫙涓嬶紝鎺掗櫎闈炲彾瀛愯妭鐐癸紙鐖惰妭鐐癸級鐢ㄣ��
+ * </p>
+ *
+ * @param roleId 瑙掕壊ID
+ * @return SQL 璇彞瀛楃涓诧紝鏌ヨ瑙掕壊涓嬮儴闂ㄧ殑鎵�鏈夌埗閮ㄩ棬ID
+ */
+ default String buildParentDeptByRoleSql(Long roleId) {
+ return """
+ select parent_id from sys_dept where dept_id in (
+ select srd.dept_id from sys_role_dept srd
+ left join sys_role sr on sr.role_id = srd.role_id
+ where srd.role_id = %d and sr.status = '0'
+ )
+ """.formatted(roleId);
+ }
+
+ /**
+ * 鏌ヨ閮ㄩ棬绠$悊鏁版嵁
+ *
+ * @param queryWrapper 鏌ヨ鏉′欢
+ * @return 閮ㄩ棬淇℃伅闆嗗悎
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id")
+ })
+ default List<SysDeptVo> selectDeptList(Wrapper<SysDept> queryWrapper) {
+ return this.selectVoList(queryWrapper);
+ }
+
+ /**
+ * 鍒嗛〉鏌ヨ閮ㄩ棬绠$悊鏁版嵁
+ *
+ * @param page 鍒嗛〉淇℃伅
+ * @param queryWrapper 鏌ヨ鏉′欢
+ * @return 閮ㄩ棬淇℃伅闆嗗悎
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ })
+ default Page<SysDeptVo> selectPageDeptList(Page<SysDept> page, Wrapper<SysDept> queryWrapper) {
+ return this.selectVoPage(page, queryWrapper);
+ }
+
+ /**
+ * 缁熻鎸囧畾閮ㄩ棬ID鐨勯儴闂ㄦ暟閲�
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @return 璇ラ儴闂↖D鐨勯儴闂ㄦ暟閲�
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id")
+ })
+ default long countDeptById(Long deptId) {
+ return this.selectCount(new LambdaQueryWrapper<SysDept>().eq(SysDept::getDeptId, deptId));
+ }
+
+ /**
+ * 鏍规嵁鐖堕儴闂↖D鏌ヨ鍏舵墍鏈夊瓙閮ㄩ棬鐨勫垪琛�
+ *
+ * @param parentId 鐖堕儴闂↖D
+ * @return 鍖呭惈瀛愰儴闂ㄧ殑鍒楄〃
+ */
+ default List<SysDept> selectListByParentId(Long parentId) {
+ return this.selectList(new LambdaQueryWrapper<SysDept>()
+ .select(SysDept::getDeptId)
+ .apply(DataBaseHelper.findInSet(parentId, "ancestors")));
+ }
+
+ /**
+ * 鏌ヨ鏌愪釜閮ㄩ棬鍙婂叾鎵�鏈夊瓙閮ㄩ棬ID锛堝惈鑷韩锛�
+ *
+ * @param parentId 鐖堕儴闂↖D
+ * @return 閮ㄩ棬ID闆嗗悎
+ */
+ default List<Long> selectDeptAndChildById(Long parentId) {
+ List<SysDept> deptList = this.selectListByParentId(parentId);
+ List<Long> deptIds = StreamUtils.toList(deptList, SysDept::getDeptId);
+ deptIds.add(parentId);
+ return deptIds;
+ }
+
+ /**
+ * 鏍规嵁瑙掕壊ID鏌ヨ閮ㄩ棬鏍戜俊鎭�
+ *
+ * @param roleId 瑙掕壊ID
+ * @param deptCheckStrictly 閮ㄩ棬鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�
+ * @return 閫変腑閮ㄩ棬鍒楄〃
+ */
+ default List<Long> selectDeptListByRoleId(Long roleId, boolean deptCheckStrictly) {
+ LambdaQueryWrapper<SysDept> wrapper = new LambdaQueryWrapper<>();
+ wrapper.select(SysDept::getDeptId)
+ .inSql(SysDept::getDeptId, this.buildDeptByRoleSql(roleId))
+ .orderByAsc(SysDept::getParentId)
+ .orderByAsc(SysDept::getOrderNum);
+ if (deptCheckStrictly) {
+ wrapper.notInSql(SysDept::getDeptId, this.buildParentDeptByRoleSql(roleId));
+ }
+ return this.selectObjs(wrapper);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDictDataMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDictDataMapper.java
new file mode 100755
index 0000000..7298db3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDictDataMapper.java
@@ -0,0 +1,29 @@
+package org.dromara.system.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysDictData;
+import org.dromara.system.domain.vo.SysDictDataVo;
+
+import java.util.List;
+
+/**
+ * 瀛楀吀琛� 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysDictDataMapper extends BaseMapperPlus<SysDictData, SysDictDataVo> {
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鏌ヨ瀛楀吀鏁版嵁鍒楄〃
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return 绗﹀悎鏉′欢鐨勫瓧鍏告暟鎹垪琛�
+ */
+ default List<SysDictDataVo> selectDictDataByType(String dictType) {
+ return selectVoList(
+ new LambdaQueryWrapper<SysDictData>()
+ .eq(SysDictData::getDictType, dictType)
+ .orderByAsc(SysDictData::getDictSort));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDictTypeMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDictTypeMapper.java
new file mode 100755
index 0000000..9a9bdd5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDictTypeMapper.java
@@ -0,0 +1,14 @@
+package org.dromara.system.mapper;
+
+import org.dromara.system.domain.SysDictType;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.vo.SysDictTypeVo;
+
+/**
+ * 瀛楀吀琛� 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysDictTypeMapper extends BaseMapperPlus<SysDictType, SysDictTypeVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysLogininforMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysLogininforMapper.java
new file mode 100755
index 0000000..85edd1d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysLogininforMapper.java
@@ -0,0 +1,14 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysLogininfor;
+import org.dromara.system.domain.vo.SysLogininforVo;
+
+/**
+ * 绯荤粺璁块棶鏃ュ織鎯呭喌淇℃伅 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysLogininforMapper extends BaseMapperPlus<SysLogininfor, SysLogininforVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysMenuMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysMenuMapper.java
new file mode 100755
index 0000000..05e2546
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysMenuMapper.java
@@ -0,0 +1,147 @@
+package org.dromara.system.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysMenu;
+import org.dromara.system.domain.vo.SysMenuVo;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 鑿滃崟琛� 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
+
+ /**
+ * 鏋勫缓鐢ㄦ埛鏉冮檺鑿滃崟 SQL
+ *
+ * <p>
+ * 鏌ヨ鐢ㄦ埛鎵�灞炶鑹叉墍鎷ユ湁鐨勮彍鍗曟潈闄愶紝鐢ㄤ簬鏉冮檺鍒ゆ柇銆佽彍鍗曞姞杞界瓑鍦烘櫙
+ * </p>
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return SQL 瀛楃涓诧紝鐢ㄤ簬 inSql 鏉′欢
+ */
+ default String buildMenuByUserSql(Long userId) {
+ return """
+ select menu_id from sys_role_menu where role_id in (
+ select sur.role_id from sys_user_role sur
+ left join sys_role sr on sr.role_id = sur.role_id
+ where sur.user_id = %d and sr.status = '0'
+ )
+ """.formatted(userId);
+ }
+
+ /**
+ * 鏋勫缓瑙掕壊瀵瑰簲鐨勮彍鍗旾D SQL 瀛愭煡璇�
+ *
+ * <p>
+ * 鐢ㄤ簬鏍规嵁瑙掕壊ID鏌ヨ鍏舵墍鎷ユ湁鐨勮彍鍗曟潈闄愶紙鐢ㄤ簬鏉冮檺鏍囪瘑銆佽彍鍗曟樉绀虹瓑鍦烘櫙锛�
+ * 閫氬父閰嶅悎 inSql 浣跨敤
+ * </p>
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 鏌ヨ鑿滃崟ID鐨� SQL 瀛愭煡璇㈠瓧绗︿覆
+ */
+ default String buildMenuByRoleSql(Long roleId) {
+ return """
+ select srm.menu_id from sys_role_menu srm
+ left join sys_role sr on sr.role_id = srm.role_id
+ where srm.role_id = %d and sr.status = '0'
+ """.formatted(roleId);
+ }
+
+ /**
+ * 鏋勫缓瑙掕壊鎵�鍏宠仈鑿滃崟鐨勭埗鑿滃崟ID鏌ヨ SQL
+ *
+ * <p>
+ * 鐢ㄤ簬閰嶅悎鑿滃崟鍕鹃�夋爲缁撴瀯鐨� {@code menuCheckStrictly} 妯″紡锛岃繃婊ゆ帀闈炲彾瀛愯妭鐐癸紙鐖惰彍鍗曪級锛�
+ * 鍙繑鍥炶鑹插疄闄呭嬀閫夌殑鏈骇鑿滃崟
+ * </p>
+ *
+ * @param roleId 瑙掕壊ID
+ * @return SQL 璇彞瀛楃涓诧紙鏌ヨ鑿滃崟鐨勭埗鑿滃崟ID锛�
+ */
+ default String buildParentMenuByRoleSql(Long roleId) {
+ return """
+ select parent_id from sys_menu where menu_id in (
+ select srm.menu_id from sys_role_menu srm
+ left join sys_role sr on sr.role_id = srm.role_id
+ where srm.role_id = %d and sr.status = '0'
+ )
+ """.formatted(roleId);
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ鏉冮檺
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鏉冮檺鍒楄〃
+ */
+ default Set<String> selectMenuPermsByUserId(Long userId) {
+ List<String> list = this.selectObjs(
+ new LambdaQueryWrapper<SysMenu>()
+ .select(SysMenu::getPerms)
+ .inSql(SysMenu::getMenuId, this.buildMenuByUserSql(userId))
+ .isNotNull(SysMenu::getPerms)
+ );
+ return new HashSet<>(StreamUtils.filter(list, StringUtils::isNotBlank));
+ }
+
+ /**
+ * 鏍规嵁瑙掕壊ID鏌ヨ鏉冮檺
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 鏉冮檺鍒楄〃
+ */
+ default Set<String> selectMenuPermsByRoleId(Long roleId) {
+ List<String> list = this.selectObjs(
+ new LambdaQueryWrapper<SysMenu>()
+ .select(SysMenu::getPerms)
+ .inSql(SysMenu::getMenuId, this.buildMenuByRoleSql(roleId))
+ .isNotNull(SysMenu::getPerms)
+ );
+ return new HashSet<>(StreamUtils.filter(list, StringUtils::isNotBlank));
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ鑿滃崟
+ *
+ * @return 鑿滃崟鍒楄〃
+ */
+ default List<SysMenu> selectMenuTreeAll() {
+ LambdaQueryWrapper<SysMenu> lqw = new LambdaQueryWrapper<SysMenu>()
+ .in(SysMenu::getMenuType, SystemConstants.TYPE_DIR, SystemConstants.TYPE_MENU)
+ .eq(SysMenu::getStatus, SystemConstants.NORMAL)
+ .orderByAsc(SysMenu::getParentId)
+ .orderByAsc(SysMenu::getOrderNum);
+ return this.selectList(lqw);
+ }
+
+ /**
+ * 鏍规嵁瑙掕壊ID鏌ヨ鑿滃崟鏍戜俊鎭�
+ *
+ * @param roleId 瑙掕壊ID
+ * @param menuCheckStrictly 鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�
+ * @return 閫変腑鑿滃崟鍒楄〃
+ */
+ default List<Long> selectMenuListByRoleId(Long roleId, boolean menuCheckStrictly) {
+ LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
+ wrapper.select(SysMenu::getMenuId)
+ .inSql(SysMenu::getMenuId, buildMenuByRoleSql(roleId))
+ .orderByAsc(SysMenu::getParentId)
+ .orderByAsc(SysMenu::getOrderNum);
+ if (menuCheckStrictly) {
+ wrapper.notInSql(SysMenu::getMenuId, this.buildParentMenuByRoleSql(roleId));
+ }
+ return this.selectObjs(wrapper);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysNoticeMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysNoticeMapper.java
new file mode 100755
index 0000000..1e27b77
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysNoticeMapper.java
@@ -0,0 +1,14 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysNotice;
+import org.dromara.system.domain.vo.SysNoticeVo;
+
+/**
+ * 閫氱煡鍏憡琛� 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysNoticeMapper extends BaseMapperPlus<SysNotice, SysNoticeVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysOperLogMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysOperLogMapper.java
new file mode 100755
index 0000000..5d20404
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysOperLogMapper.java
@@ -0,0 +1,14 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysOperLog;
+import org.dromara.system.domain.vo.SysOperLogVo;
+
+/**
+ * 鎿嶄綔鏃ュ織 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysOperLogMapper extends BaseMapperPlus<SysOperLog, SysOperLogVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysOssConfigMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysOssConfigMapper.java
new file mode 100755
index 0000000..f93d34d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysOssConfigMapper.java
@@ -0,0 +1,16 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysOssConfig;
+import org.dromara.system.domain.vo.SysOssConfigVo;
+
+/**
+ * 瀵硅薄瀛樺偍閰嶇疆Mapper鎺ュ彛
+ *
+ * @author Lion Li
+ * @author 瀛よ垷鐑熼洦
+ * @date 2021-08-13
+ */
+public interface SysOssConfigMapper extends BaseMapperPlus<SysOssConfig, SysOssConfigVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysOssMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysOssMapper.java
new file mode 100755
index 0000000..3da621d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysOssMapper.java
@@ -0,0 +1,13 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysOss;
+import org.dromara.system.domain.vo.SysOssVo;
+
+/**
+ * 鏂囦欢涓婁紶 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysOssMapper extends BaseMapperPlus<SysOss, SysOssVo> {
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysPostMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysPostMapper.java
new file mode 100755
index 0000000..d8d0315
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysPostMapper.java
@@ -0,0 +1,75 @@
+package org.dromara.system.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.dromara.common.mybatis.annotation.DataColumn;
+import org.dromara.common.mybatis.annotation.DataPermission;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysPost;
+import org.dromara.system.domain.vo.SysPostVo;
+
+import java.util.List;
+
+/**
+ * 宀椾綅淇℃伅 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysPostMapper extends BaseMapperPlus<SysPost, SysPostVo> {
+
+ /**
+ * 鍒嗛〉鏌ヨ宀椾綅鍒楄〃
+ *
+ * @param page 鍒嗛〉瀵硅薄
+ * @param queryWrapper 鏌ヨ鏉′欢
+ * @return 鍖呭惈宀椾綅淇℃伅鐨勫垎椤电粨鏋�
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "create_by")
+ })
+ default Page<SysPostVo> selectPagePostList(Page<SysPost> page, Wrapper<SysPost> queryWrapper) {
+ return this.selectVoPage(page, queryWrapper);
+ }
+
+ /**
+ * 鏌ヨ宀椾綅鍒楄〃
+ *
+ * @param queryWrapper 鏌ヨ鏉′欢
+ * @return 宀椾綅淇℃伅鍒楄〃
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "create_by")
+ })
+ default List<SysPostVo> selectPostList(Wrapper<SysPost> queryWrapper) {
+ return this.selectVoList(queryWrapper);
+ }
+
+ /**
+ * 鏍规嵁宀椾綅ID闆嗗悎鏌ヨ宀椾綅鏁伴噺
+ *
+ * @param postIds 宀椾綅ID鍒楄〃
+ * @return 鍖归厤鐨勫矖浣嶆暟閲�
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "create_by")
+ })
+ default long selectPostCount(List<Long> postIds) {
+ return this.selectCount(new LambdaQueryWrapper<SysPost>().in(SysPost::getPostId, postIds));
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ鍏跺叧鑱旂殑宀椾綅鍒楄〃
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 宀椾綅淇℃伅鍒楄〃
+ */
+ default List<SysPostVo> selectPostsByUserId(Long userId) {
+ return this.selectVoList(new LambdaQueryWrapper<SysPost>()
+ .inSql(SysPost::getPostId, "select post_id from sys_user_post where user_id = " + userId));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleDeptMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleDeptMapper.java
new file mode 100755
index 0000000..3de0bb6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleDeptMapper.java
@@ -0,0 +1,13 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysRoleDept;
+
+/**
+ * 瑙掕壊涓庨儴闂ㄥ叧鑱旇〃 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysRoleDeptMapper extends BaseMapperPlus<SysRoleDept, SysRoleDept> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMapper.java
new file mode 100755
index 0000000..9207805
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMapper.java
@@ -0,0 +1,105 @@
+package org.dromara.system.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+import org.dromara.common.mybatis.annotation.DataColumn;
+import org.dromara.common.mybatis.annotation.DataPermission;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysRole;
+import org.dromara.system.domain.vo.SysRoleVo;
+
+import java.util.List;
+
+/**
+ * 瑙掕壊琛� 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysRoleMapper extends BaseMapperPlus<SysRole, SysRoleVo> {
+
+ /**
+ * 鏋勫缓鏍规嵁鐢ㄦ埛ID鏌ヨ瑙掕壊ID鐨凷QL瀛愭煡璇�
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鏌ヨ鐢ㄦ埛瀵瑰簲瑙掕壊ID鐨凷QL璇彞瀛楃涓�
+ */
+ default String buildRoleByUserSql(Long userId) {
+ return """
+ select role_id from sys_user_role where user_id = %d
+ """.formatted(userId);
+ }
+
+ /**
+ * 鍒嗛〉鏌ヨ瑙掕壊鍒楄〃
+ *
+ * @param page 鍒嗛〉瀵硅薄
+ * @param queryWrapper 鏌ヨ鏉′欢
+ * @return 鍖呭惈瑙掕壊淇℃伅鐨勫垎椤电粨鏋�
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "create_dept"),
+ @DataColumn(key = "userName", value = "create_by")
+ })
+ default Page<SysRoleVo> selectPageRoleList(@Param("page") Page<SysRole> page, @Param(Constants.WRAPPER) Wrapper<SysRole> queryWrapper) {
+ return this.selectVoPage(page, queryWrapper);
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鏌ヨ瑙掕壊鏁版嵁
+ *
+ * @param queryWrapper 鏌ヨ鏉′欢
+ * @return 瑙掕壊鏁版嵁闆嗗悎淇℃伅
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "create_dept"),
+ @DataColumn(key = "userName", value = "create_by")
+ })
+ default List<SysRoleVo> selectRoleList(@Param(Constants.WRAPPER) Wrapper<SysRole> queryWrapper) {
+ return this.selectVoList(queryWrapper);
+ }
+
+ /**
+ * 鏍规嵁瑙掕壊ID闆嗗悎鏌ヨ瑙掕壊鏁伴噺
+ *
+ * @param roleIds 瑙掕壊ID鍒楄〃
+ * @return 鍖归厤鐨勮鑹叉暟閲�
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "create_dept"),
+ @DataColumn(key = "userName", value = "create_by")
+ })
+ default long selectRoleCount(List<Long> roleIds) {
+ return this.selectCount(new LambdaQueryWrapper<SysRole>().in(SysRole::getRoleId, roleIds));
+ }
+
+ /**
+ * 鏍规嵁瑙掕壊ID鏌ヨ瑙掕壊淇℃伅
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 瀵瑰簲鐨勮鑹蹭俊鎭�
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "create_dept"),
+ @DataColumn(key = "userName", value = "create_by")
+ })
+ default SysRoleVo selectRoleById(Long roleId) {
+ return this.selectVoById(roleId);
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ瑙掕壊
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 瑙掕壊鍒楄〃
+ */
+ default List<SysRoleVo> selectRolesByUserId(Long userId) {
+ return this.selectVoList(new LambdaQueryWrapper<SysRole>()
+ .select(SysRole::getRoleId, SysRole::getRoleName, SysRole::getRoleKey,
+ SysRole::getRoleSort, SysRole::getDataScope, SysRole::getStatus)
+ .inSql(SysRole::getRoleId, this.buildRoleByUserSql(userId)));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMenuMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMenuMapper.java
new file mode 100755
index 0000000..8aa9dd3
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMenuMapper.java
@@ -0,0 +1,26 @@
+package org.dromara.system.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysRoleMenu;
+
+import java.util.List;
+
+/**
+ * 瑙掕壊涓庤彍鍗曞叧鑱旇〃 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysRoleMenuMapper extends BaseMapperPlus<SysRoleMenu, SysRoleMenu> {
+
+ /**
+ * 鏍规嵁鑿滃崟ID涓插垹闄ゅ叧鑱斿叧绯�
+ *
+ * @param menuIds 鑿滃崟ID涓�
+ * @return 缁撴灉
+ */
+ default int deleteByMenuIds(List<Long> menuIds) {
+ return this.delete(new LambdaUpdateWrapper<SysRoleMenu>().in(SysRoleMenu::getMenuId, menuIds));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysSocialMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysSocialMapper.java
new file mode 100755
index 0000000..b942061
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysSocialMapper.java
@@ -0,0 +1,14 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysSocial;
+import org.dromara.system.domain.vo.SysSocialVo;
+
+/**
+ * 绀句細鍖栧叧绯籑apper鎺ュ彛
+ *
+ * @author thiszhc
+ */
+public interface SysSocialMapper extends BaseMapperPlus<SysSocial, SysSocialVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysTenantMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysTenantMapper.java
new file mode 100755
index 0000000..7e1167a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysTenantMapper.java
@@ -0,0 +1,14 @@
+package org.dromara.system.mapper;
+
+import org.dromara.system.domain.SysTenant;
+import org.dromara.system.domain.vo.SysTenantVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 绉熸埛Mapper鎺ュ彛
+ *
+ * @author Michelle.Chung
+ */
+public interface SysTenantMapper extends BaseMapperPlus<SysTenant, SysTenantVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysTenantPackageMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysTenantPackageMapper.java
new file mode 100755
index 0000000..10ca170
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysTenantPackageMapper.java
@@ -0,0 +1,14 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysTenantPackage;
+import org.dromara.system.domain.vo.SysTenantPackageVo;
+
+/**
+ * 绉熸埛濂楅Mapper鎺ュ彛
+ *
+ * @author Michelle.Chung
+ */
+public interface SysTenantPackageMapper extends BaseMapperPlus<SysTenantPackage, SysTenantPackageVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java
new file mode 100755
index 0000000..2cfd3db
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java
@@ -0,0 +1,131 @@
+package org.dromara.system.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+import org.dromara.common.mybatis.annotation.DataColumn;
+import org.dromara.common.mybatis.annotation.DataPermission;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.vo.SysUserExportVo;
+import org.dromara.system.domain.vo.SysUserVo;
+
+import java.util.List;
+
+/**
+ * 鐢ㄦ埛琛� 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
+
+ /**
+ * 鍒嗛〉鏌ヨ鐢ㄦ埛鍒楄〃锛屽苟杩涜鏁版嵁鏉冮檺鎺у埗
+ *
+ * @param page 鍒嗛〉鍙傛暟
+ * @param queryWrapper 鏌ヨ鏉′欢
+ * @return 鍒嗛〉鐨勭敤鎴蜂俊鎭�
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "create_by")
+ })
+ default Page<SysUserVo> selectPageUserList(Page<SysUser> page, Wrapper<SysUser> queryWrapper) {
+ return this.selectVoPage(page, queryWrapper);
+ }
+
+ /**
+ * 鏌ヨ鐢ㄦ埛鍒楄〃锛屽苟杩涜鏁版嵁鏉冮檺鎺у埗
+ *
+ * @param queryWrapper 鏌ヨ鏉′欢
+ * @return 鐢ㄦ埛淇℃伅闆嗗悎
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "create_by")
+ })
+ default List<SysUserVo> selectUserList(Wrapper<SysUser> queryWrapper) {
+ return this.selectVoList(queryWrapper);
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ鐢ㄦ埛鍒楄〃
+ *
+ * @param queryWrapper 鏌ヨ鏉′欢
+ * @return 鐢ㄦ埛淇℃伅闆嗗悎淇℃伅
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "d.dept_id"),
+ @DataColumn(key = "userName", value = "u.create_by")
+ })
+ List<SysUserExportVo> selectUserExportList(@Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ宸查厤鐢ㄦ埛瑙掕壊鍒楄〃
+ *
+ * @param page 鍒嗛〉淇℃伅
+ * @param queryWrapper 鏌ヨ鏉′欢
+ * @return 鐢ㄦ埛淇℃伅闆嗗悎淇℃伅
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "d.dept_id"),
+ @DataColumn(key = "userName", value = "u.create_by")
+ })
+ Page<SysUserVo> selectAllocatedList(@Param("page") Page<SysUser> page, @Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ鏈垎閰嶇敤鎴疯鑹插垪琛�
+ *
+ * @param queryWrapper 鏌ヨ鏉′欢
+ * @return 鐢ㄦ埛淇℃伅闆嗗悎淇℃伅
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "d.dept_id"),
+ @DataColumn(key = "userName", value = "u.create_by")
+ })
+ Page<SysUserVo> selectUnallocatedList(@Param("page") Page<SysUser> page, @Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID缁熻鐢ㄦ埛鏁伴噺
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鐢ㄦ埛鏁伴噺
+ */
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "create_by")
+ })
+ default long countUserById(Long userId) {
+ return this.selectCount(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserId, userId));
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鏇存柊鐢ㄦ埛鏁版嵁
+ *
+ * @param user 瑕佹洿鏂扮殑鐢ㄦ埛瀹炰綋
+ * @param updateWrapper 鏇存柊鏉′欢灏佽鍣�
+ * @return 鏇存柊鎿嶄綔褰卞搷鐨勮鏁�
+ */
+ @Override
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "create_by")
+ })
+ int update(@Param(Constants.ENTITY) SysUser user, @Param(Constants.WRAPPER) Wrapper<SysUser> updateWrapper);
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏇存柊鐢ㄦ埛鏁版嵁
+ *
+ * @param user 瑕佹洿鏂扮殑鐢ㄦ埛瀹炰綋
+ * @return 鏇存柊鎿嶄綔褰卞搷鐨勮鏁�
+ */
+ @Override
+ @DataPermission({
+ @DataColumn(key = "deptName", value = "dept_id"),
+ @DataColumn(key = "userName", value = "create_by")
+ })
+ int updateById(@Param(Constants.ENTITY) SysUser user);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserPostMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserPostMapper.java
new file mode 100755
index 0000000..07c1371
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserPostMapper.java
@@ -0,0 +1,13 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysUserPost;
+
+/**
+ * 鐢ㄦ埛涓庡矖浣嶅叧鑱旇〃 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysUserPostMapper extends BaseMapperPlus<SysUserPost, SysUserPost> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserRoleMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserRoleMapper.java
new file mode 100755
index 0000000..4c1c8b9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserRoleMapper.java
@@ -0,0 +1,28 @@
+package org.dromara.system.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.SysUserRole;
+
+import java.util.List;
+
+/**
+ * 鐢ㄦ埛涓庤鑹插叧鑱旇〃 鏁版嵁灞�
+ *
+ * @author Lion Li
+ */
+public interface SysUserRoleMapper extends BaseMapperPlus<SysUserRole, SysUserRole> {
+
+ /**
+ * 鏍规嵁瑙掕壊ID鏌ヨ鍏宠仈鐨勭敤鎴稩D鍒楄〃
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 鍏宠仈鍒版寚瀹氳鑹茬殑鐢ㄦ埛ID鍒楄〃
+ */
+ default List<Long> selectUserIdsByRoleId(Long roleId) {
+ return this.selectObjs(new LambdaQueryWrapper<SysUserRole>()
+ .select(SysUserRole::getUserId).eq(SysUserRole::getRoleId, roleId)
+ );
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/runner/SystemApplicationRunner.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/runner/SystemApplicationRunner.java
new file mode 100755
index 0000000..27dad7d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/runner/SystemApplicationRunner.java
@@ -0,0 +1,28 @@
+package org.dromara.system.runner;
+
+import org.dromara.system.service.ISysOssConfigService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鍒濆鍖� system 妯″潡瀵瑰簲涓氬姟鏁版嵁
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Component
+public class SystemApplicationRunner implements ApplicationRunner {
+
+ private final ISysOssConfigService ossConfigService;
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ ossConfigService.init();
+ log.info("鍒濆鍖朞SS閰嶇疆鎴愬姛");
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysClientService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysClientService.java
new file mode 100755
index 0000000..9e742fd
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysClientService.java
@@ -0,0 +1,66 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysClientBo;
+import org.dromara.system.domain.vo.SysClientVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 瀹㈡埛绔鐞哠ervice鎺ュ彛
+ *
+ * @author Michelle.Chung
+ * @date 2023-06-18
+ */
+public interface ISysClientService {
+
+ /**
+ * 鏌ヨ瀹㈡埛绔鐞�
+ */
+ SysClientVo queryById(Long id);
+
+ /**
+ * 鏌ヨ瀹㈡埛绔俊鎭熀浜庡鎴风id
+ */
+ SysClientVo queryByClientId(String clientId);
+
+ /**
+ * 鏌ヨ瀹㈡埛绔鐞嗗垪琛�
+ */
+ TableDataInfo<SysClientVo> queryPageList(SysClientBo bo, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ瀹㈡埛绔鐞嗗垪琛�
+ */
+ List<SysClientVo> queryList(SysClientBo bo);
+
+ /**
+ * 鏂板瀹㈡埛绔鐞�
+ */
+ Boolean insertByBo(SysClientBo bo);
+
+ /**
+ * 淇敼瀹㈡埛绔鐞�
+ */
+ Boolean updateByBo(SysClientBo bo);
+
+ /**
+ * 淇敼鐘舵��
+ */
+ int updateClientStatus(String clientId, String status);
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄ゅ鎴风绠$悊淇℃伅
+ */
+ Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+ /**
+ * 鏍¢獙瀹㈡埛绔痥ey鏄惁鍞竴
+ *
+ * @param client 瀹㈡埛绔俊鎭�
+ * @return 缁撴灉
+ */
+ boolean checkClickKeyUnique(SysClientBo client);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysConfigService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysConfigService.java
new file mode 100755
index 0000000..a101643
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysConfigService.java
@@ -0,0 +1,93 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysConfigBo;
+import org.dromara.system.domain.vo.SysConfigVo;
+
+import java.util.List;
+
+/**
+ * 鍙傛暟閰嶇疆 鏈嶅姟灞�
+ *
+ * @author Lion Li
+ */
+public interface ISysConfigService {
+
+ /**
+ * 鍒嗛〉鏌ヨ鍙傛暟閰嶇疆鍒楄〃
+ *
+ * @param config 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 鍙傛暟閰嶇疆鍒嗛〉鍒楄〃
+ */
+ TableDataInfo<SysConfigVo> selectPageConfigList(SysConfigBo config, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ鍙傛暟閰嶇疆淇℃伅
+ *
+ * @param configId 鍙傛暟閰嶇疆ID
+ * @return 鍙傛暟閰嶇疆淇℃伅
+ */
+ SysConfigVo selectConfigById(Long configId);
+
+ /**
+ * 鏍规嵁閿悕鏌ヨ鍙傛暟閰嶇疆淇℃伅
+ *
+ * @param configKey 鍙傛暟閿悕
+ * @return 鍙傛暟閿��
+ */
+ String selectConfigByKey(String configKey);
+
+ /**
+ * 鑾峰彇娉ㄥ唽寮�鍏�
+ * @param tenantId 绉熸埛id
+ * @return true寮�鍚紝false鍏抽棴
+ */
+ boolean selectRegisterEnabled(String tenantId);
+
+ /**
+ * 鏌ヨ鍙傛暟閰嶇疆鍒楄〃
+ *
+ * @param config 鍙傛暟閰嶇疆淇℃伅
+ * @return 鍙傛暟閰嶇疆闆嗗悎
+ */
+ List<SysConfigVo> selectConfigList(SysConfigBo config);
+
+ /**
+ * 鏂板鍙傛暟閰嶇疆
+ *
+ * @param bo 鍙傛暟閰嶇疆淇℃伅
+ * @return 缁撴灉
+ */
+ String insertConfig(SysConfigBo bo);
+
+ /**
+ * 淇敼鍙傛暟閰嶇疆
+ *
+ * @param bo 鍙傛暟閰嶇疆淇℃伅
+ * @return 缁撴灉
+ */
+ String updateConfig(SysConfigBo bo);
+
+ /**
+ * 鎵归噺鍒犻櫎鍙傛暟淇℃伅
+ *
+ * @param configIds 闇�瑕佸垹闄ょ殑鍙傛暟ID
+ */
+ void deleteConfigByIds(List<Long> configIds);
+
+ /**
+ * 閲嶇疆鍙傛暟缂撳瓨鏁版嵁
+ */
+ void resetConfigCache();
+
+ /**
+ * 鏍¢獙鍙傛暟閿悕鏄惁鍞竴
+ *
+ * @param config 鍙傛暟淇℃伅
+ * @return 缁撴灉
+ */
+ boolean checkConfigKeyUnique(SysConfigBo config);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDataScopeService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDataScopeService.java
new file mode 100755
index 0000000..3f252f7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDataScopeService.java
@@ -0,0 +1,26 @@
+package org.dromara.system.service;
+
+/**
+ * 閫氱敤 鏁版嵁鏉冮檺 鏈嶅姟
+ *
+ * @author Lion Li
+ */
+public interface ISysDataScopeService {
+
+ /**
+ * 鑾峰彇瑙掕壊鑷畾涔夋潈闄�
+ *
+ * @param roleId 瑙掕壊id
+ * @return 閮ㄩ棬id缁�
+ */
+ String getRoleCustom(Long roleId);
+
+ /**
+ * 鑾峰彇閮ㄩ棬鍙婁互涓嬫潈闄�
+ *
+ * @param deptId 閮ㄩ棬id
+ * @return 閮ㄩ棬id缁�
+ */
+ String getDeptAndChild(Long deptId);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDeptService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDeptService.java
new file mode 100755
index 0000000..1397443
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDeptService.java
@@ -0,0 +1,137 @@
+package org.dromara.system.service;
+
+import cn.hutool.core.lang.tree.Tree;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysDeptBo;
+import org.dromara.system.domain.vo.SysDeptVo;
+
+import java.util.List;
+
+/**
+ * 閮ㄩ棬绠$悊 鏈嶅姟灞�
+ *
+ * @author Lion Li
+ */
+public interface ISysDeptService {
+
+ /**
+ * 鍒嗛〉鏌ヨ閮ㄩ棬绠$悊鏁版嵁
+ *
+ * @param dept 閮ㄩ棬淇℃伅
+ * @param pageQuery 鍒嗛〉瀵硅薄
+ * @return 閮ㄩ棬淇℃伅闆嗗悎
+ */
+ TableDataInfo<SysDeptVo> selectPageDeptList(SysDeptBo dept, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ閮ㄩ棬绠$悊鏁版嵁
+ *
+ * @param dept 閮ㄩ棬淇℃伅
+ * @return 閮ㄩ棬淇℃伅闆嗗悎
+ */
+ List<SysDeptVo> selectDeptList(SysDeptBo dept);
+
+ /**
+ * 鏌ヨ閮ㄩ棬鏍戠粨鏋勪俊鎭�
+ *
+ * @param dept 閮ㄩ棬淇℃伅
+ * @return 閮ㄩ棬鏍戜俊鎭泦鍚�
+ */
+ List<Tree<Long>> selectDeptTreeList(SysDeptBo dept);
+
+ /**
+ * 鏋勫缓鍓嶇鎵�闇�瑕佷笅鎷夋爲缁撴瀯
+ *
+ * @param depts 閮ㄩ棬鍒楄〃
+ * @return 涓嬫媺鏍戠粨鏋勫垪琛�
+ */
+ List<Tree<Long>> buildDeptTreeSelect(List<SysDeptVo> depts);
+
+ /**
+ * 鏍规嵁瑙掕壊ID鏌ヨ閮ㄩ棬鏍戜俊鎭�
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 閫変腑閮ㄩ棬鍒楄〃
+ */
+ List<Long> selectDeptListByRoleId(Long roleId);
+
+ /**
+ * 鏍规嵁閮ㄩ棬ID鏌ヨ淇℃伅
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @return 閮ㄩ棬淇℃伅
+ */
+ SysDeptVo selectDeptById(Long deptId);
+
+ /**
+ * 閫氳繃閮ㄩ棬ID涓叉煡璇㈤儴闂�
+ *
+ * @param deptIds 閮ㄩ棬id涓�
+ * @return 閮ㄩ棬鍒楄〃淇℃伅
+ */
+ List<SysDeptVo> selectDeptByIds(List<Long> deptIds);
+
+ /**
+ * 鏍规嵁ID鏌ヨ鎵�鏈夊瓙閮ㄩ棬鏁帮紙姝e父鐘舵�侊級
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @return 瀛愰儴闂ㄦ暟
+ */
+ long selectNormalChildrenDeptById(Long deptId);
+
+ /**
+ * 鏄惁瀛樺湪閮ㄩ棬瀛愯妭鐐�
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @return 缁撴灉
+ */
+ boolean hasChildByDeptId(Long deptId);
+
+ /**
+ * 鏌ヨ閮ㄩ棬鏄惁瀛樺湪鐢ㄦ埛
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @return 缁撴灉 true 瀛樺湪 false 涓嶅瓨鍦�
+ */
+ boolean checkDeptExistUser(Long deptId);
+
+ /**
+ * 鏍¢獙閮ㄩ棬鍚嶇О鏄惁鍞竴
+ *
+ * @param dept 閮ㄩ棬淇℃伅
+ * @return 缁撴灉
+ */
+ boolean checkDeptNameUnique(SysDeptBo dept);
+
+ /**
+ * 鏍¢獙閮ㄩ棬鏄惁鏈夋暟鎹潈闄�
+ *
+ * @param deptId 閮ㄩ棬id
+ */
+ void checkDeptDataScope(Long deptId);
+
+ /**
+ * 鏂板淇濆瓨閮ㄩ棬淇℃伅
+ *
+ * @param bo 閮ㄩ棬淇℃伅
+ * @return 缁撴灉
+ */
+ int insertDept(SysDeptBo bo);
+
+ /**
+ * 淇敼淇濆瓨閮ㄩ棬淇℃伅
+ *
+ * @param bo 閮ㄩ棬淇℃伅
+ * @return 缁撴灉
+ */
+ int updateDept(SysDeptBo bo);
+
+ /**
+ * 鍒犻櫎閮ㄩ棬绠$悊淇℃伅
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @return 缁撴灉
+ */
+ int deleteDeptById(Long deptId);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictDataService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictDataService.java
new file mode 100755
index 0000000..fdabd31
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictDataService.java
@@ -0,0 +1,82 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysDictDataBo;
+import org.dromara.system.domain.vo.SysDictDataVo;
+
+import java.util.List;
+
+/**
+ * 瀛楀吀 涓氬姟灞�
+ *
+ * @author Lion Li
+ */
+public interface ISysDictDataService {
+
+ /**
+ * 鍒嗛〉鏌ヨ瀛楀吀鏁版嵁鍒楄〃
+ *
+ * @param dictData 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 瀛楀吀鏁版嵁鍒嗛〉鍒楄〃
+ */
+ TableDataInfo<SysDictDataVo> selectPageDictDataList(SysDictDataBo dictData, PageQuery pageQuery);
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ瀛楀吀鏁版嵁
+ *
+ * @param dictData 瀛楀吀鏁版嵁淇℃伅
+ * @return 瀛楀吀鏁版嵁闆嗗悎淇℃伅
+ */
+ List<SysDictDataVo> selectDictDataList(SysDictDataBo dictData);
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏搁敭鍊兼煡璇㈠瓧鍏告暟鎹俊鎭�
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @param dictValue 瀛楀吀閿��
+ * @return 瀛楀吀鏍囩
+ */
+ String selectDictLabel(String dictType, String dictValue);
+
+ /**
+ * 鏍规嵁瀛楀吀鏁版嵁ID鏌ヨ淇℃伅
+ *
+ * @param dictCode 瀛楀吀鏁版嵁ID
+ * @return 瀛楀吀鏁版嵁
+ */
+ SysDictDataVo selectDictDataById(Long dictCode);
+
+ /**
+ * 鎵归噺鍒犻櫎瀛楀吀鏁版嵁淇℃伅
+ *
+ * @param dictCodes 闇�瑕佸垹闄ょ殑瀛楀吀鏁版嵁ID
+ */
+ void deleteDictDataByIds(List<Long> dictCodes);
+
+ /**
+ * 鏂板淇濆瓨瀛楀吀鏁版嵁淇℃伅
+ *
+ * @param bo 瀛楀吀鏁版嵁淇℃伅
+ * @return 缁撴灉
+ */
+ List<SysDictDataVo> insertDictData(SysDictDataBo bo);
+
+ /**
+ * 淇敼淇濆瓨瀛楀吀鏁版嵁淇℃伅
+ *
+ * @param bo 瀛楀吀鏁版嵁淇℃伅
+ * @return 缁撴灉
+ */
+ List<SysDictDataVo> updateDictData(SysDictDataBo bo);
+
+ /**
+ * 鏍¢獙瀛楀吀閿�兼槸鍚﹀敮涓�
+ *
+ * @param dict 瀛楀吀鏁版嵁
+ * @return 缁撴灉
+ */
+ boolean checkDictDataUnique(SysDictDataBo dict);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictTypeService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictTypeService.java
new file mode 100755
index 0000000..e3a04de
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictTypeService.java
@@ -0,0 +1,101 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysDictTypeBo;
+import org.dromara.system.domain.vo.SysDictDataVo;
+import org.dromara.system.domain.vo.SysDictTypeVo;
+
+import java.util.List;
+
+/**
+ * 瀛楀吀 涓氬姟灞�
+ *
+ * @author Lion Li
+ */
+public interface ISysDictTypeService {
+
+ /**
+ * 鍒嗛〉鏌ヨ瀛楀吀绫诲瀷鍒楄〃
+ *
+ * @param dictType 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 瀛楀吀绫诲瀷鍒嗛〉鍒楄〃
+ */
+ TableDataInfo<SysDictTypeVo> selectPageDictTypeList(SysDictTypeBo dictType, PageQuery pageQuery);
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ瀛楀吀绫诲瀷
+ *
+ * @param dictType 瀛楀吀绫诲瀷淇℃伅
+ * @return 瀛楀吀绫诲瀷闆嗗悎淇℃伅
+ */
+ List<SysDictTypeVo> selectDictTypeList(SysDictTypeBo dictType);
+
+ /**
+ * 鏍规嵁鎵�鏈夊瓧鍏哥被鍨�
+ *
+ * @return 瀛楀吀绫诲瀷闆嗗悎淇℃伅
+ */
+ List<SysDictTypeVo> selectDictTypeAll();
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鏌ヨ瀛楀吀鏁版嵁
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return 瀛楀吀鏁版嵁闆嗗悎淇℃伅
+ */
+ List<SysDictDataVo> selectDictDataByType(String dictType);
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷ID鏌ヨ淇℃伅
+ *
+ * @param dictId 瀛楀吀绫诲瀷ID
+ * @return 瀛楀吀绫诲瀷
+ */
+ SysDictTypeVo selectDictTypeById(Long dictId);
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鏌ヨ淇℃伅
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return 瀛楀吀绫诲瀷
+ */
+ SysDictTypeVo selectDictTypeByType(String dictType);
+
+ /**
+ * 鎵归噺鍒犻櫎瀛楀吀淇℃伅
+ *
+ * @param dictIds 闇�瑕佸垹闄ょ殑瀛楀吀ID
+ */
+ void deleteDictTypeByIds(List<Long> dictIds);
+
+ /**
+ * 閲嶇疆瀛楀吀缂撳瓨鏁版嵁
+ */
+ void resetDictCache();
+
+ /**
+ * 鏂板淇濆瓨瀛楀吀绫诲瀷淇℃伅
+ *
+ * @param bo 瀛楀吀绫诲瀷淇℃伅
+ * @return 缁撴灉
+ */
+ List<SysDictDataVo> insertDictType(SysDictTypeBo bo);
+
+ /**
+ * 淇敼淇濆瓨瀛楀吀绫诲瀷淇℃伅
+ *
+ * @param bo 瀛楀吀绫诲瀷淇℃伅
+ * @return 缁撴灉
+ */
+ List<SysDictDataVo> updateDictType(SysDictTypeBo bo);
+
+ /**
+ * 鏍¢獙瀛楀吀绫诲瀷绉版槸鍚﹀敮涓�
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return 缁撴灉
+ */
+ boolean checkDictTypeUnique(SysDictTypeBo dictType);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysLogininforService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysLogininforService.java
new file mode 100755
index 0000000..1bc1ea9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysLogininforService.java
@@ -0,0 +1,53 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysLogininforBo;
+import org.dromara.system.domain.vo.SysLogininforVo;
+
+import java.util.List;
+
+/**
+ * 绯荤粺璁块棶鏃ュ織鎯呭喌淇℃伅 鏈嶅姟灞�
+ *
+ * @author Lion Li
+ */
+public interface ISysLogininforService {
+
+ /**
+ * 鍒嗛〉鏌ヨ鐧诲綍鏃ュ織鍒楄〃
+ *
+ * @param logininfor 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 鐧诲綍鏃ュ織鍒嗛〉鍒楄〃
+ */
+ TableDataInfo<SysLogininforVo> selectPageLogininforList(SysLogininforBo logininfor, PageQuery pageQuery);
+
+ /**
+ * 鏂板绯荤粺鐧诲綍鏃ュ織
+ *
+ * @param bo 璁块棶鏃ュ織瀵硅薄
+ */
+ void insertLogininfor(SysLogininforBo bo);
+
+ /**
+ * 鏌ヨ绯荤粺鐧诲綍鏃ュ織闆嗗悎
+ *
+ * @param logininfor 璁块棶鏃ュ織瀵硅薄
+ * @return 鐧诲綍璁板綍闆嗗悎
+ */
+ List<SysLogininforVo> selectLogininforList(SysLogininforBo logininfor);
+
+ /**
+ * 鎵归噺鍒犻櫎绯荤粺鐧诲綍鏃ュ織
+ *
+ * @param infoIds 闇�瑕佸垹闄ょ殑鐧诲綍鏃ュ織ID
+ * @return 缁撴灉
+ */
+ int deleteLogininforByIds(Long[] infoIds);
+
+ /**
+ * 娓呯┖绯荤粺鐧诲綍鏃ュ織
+ */
+ void cleanLogininfor();
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMenuService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMenuService.java
new file mode 100755
index 0000000..8888c3c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMenuService.java
@@ -0,0 +1,172 @@
+package org.dromara.system.service;
+
+import cn.hutool.core.lang.tree.Tree;
+import org.dromara.system.domain.SysMenu;
+import org.dromara.system.domain.bo.SysMenuBo;
+import org.dromara.system.domain.vo.RouterVo;
+import org.dromara.system.domain.vo.SysMenuVo;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 鑿滃崟 涓氬姟灞�
+ *
+ * @author Lion Li
+ */
+public interface ISysMenuService {
+
+ /**
+ * 鏍规嵁鐢ㄦ埛鏌ヨ绯荤粺鑿滃崟鍒楄〃
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鑿滃崟鍒楄〃
+ */
+ List<SysMenuVo> selectMenuList(Long userId);
+
+ /**
+ * 鏍规嵁鐢ㄦ埛鏌ヨ绯荤粺鑿滃崟鍒楄〃
+ *
+ * @param menu 鑿滃崟淇℃伅
+ * @param userId 鐢ㄦ埛ID
+ * @return 鑿滃崟鍒楄〃
+ */
+ List<SysMenuVo> selectMenuList(SysMenuBo menu, Long userId);
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ鏉冮檺
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鏉冮檺鍒楄〃
+ */
+ Set<String> selectMenuPermsByUserId(Long userId);
+
+ /**
+ * 鏍规嵁瑙掕壊ID鏌ヨ鏉冮檺
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 鏉冮檺鍒楄〃
+ */
+ Set<String> selectMenuPermsByRoleId(Long roleId);
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ鑿滃崟鏍戜俊鎭�
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鑿滃崟鍒楄〃
+ */
+ List<SysMenu> selectMenuTreeByUserId(Long userId);
+
+ /**
+ * 鏍规嵁瑙掕壊ID鏌ヨ鑿滃崟鏍戜俊鎭�
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 閫変腑鑿滃崟鍒楄〃
+ */
+ List<Long> selectMenuListByRoleId(Long roleId);
+
+ /**
+ * 鏍规嵁绉熸埛濂楅ID鏌ヨ鑿滃崟鏍戜俊鎭�
+ *
+ * @param packageId 绉熸埛濂楅ID
+ * @return 閫変腑鑿滃崟鍒楄〃
+ */
+ List<Long> selectMenuListByPackageId(Long packageId);
+
+ /**
+ * 鏋勫缓鍓嶇璺敱鎵�闇�瑕佺殑鑿滃崟
+ *
+ * @param menus 鑿滃崟鍒楄〃
+ * @return 璺敱鍒楄〃
+ */
+ List<RouterVo> buildMenus(List<SysMenu> menus);
+
+ /**
+ * 鏋勫缓鍓嶇鎵�闇�瑕佷笅鎷夋爲缁撴瀯
+ *
+ * @param menus 鑿滃崟鍒楄〃
+ * @return 涓嬫媺鏍戠粨鏋勫垪琛�
+ */
+ List<Tree<Long>> buildMenuTreeSelect(List<SysMenuVo> menus);
+
+ /**
+ * 鏍规嵁鑿滃崟ID鏌ヨ淇℃伅
+ *
+ * @param menuId 鑿滃崟ID
+ * @return 鑿滃崟淇℃伅
+ */
+ SysMenuVo selectMenuById(Long menuId);
+
+ /**
+ * 鏄惁瀛樺湪鑿滃崟瀛愯妭鐐�
+ *
+ * @param menuId 鑿滃崟ID
+ * @return 缁撴灉 true 瀛樺湪 false 涓嶅瓨鍦�
+ */
+ boolean hasChildByMenuId(Long menuId);
+
+ /**
+ * 鏄惁瀛樺湪鑿滃崟瀛愯妭鐐�
+ *
+ * @param menuIds 鑿滃崟ID涓�
+ * @return 缁撴灉 true 瀛樺湪 false 涓嶅瓨鍦�
+ */
+ boolean hasChildByMenuId(List<Long> menuIds);
+
+ /**
+ * 鏌ヨ鑿滃崟鏄惁瀛樺湪瑙掕壊
+ *
+ * @param menuId 鑿滃崟ID
+ * @return 缁撴灉 true 瀛樺湪 false 涓嶅瓨鍦�
+ */
+ boolean checkMenuExistRole(Long menuId);
+
+ /**
+ * 鏂板淇濆瓨鑿滃崟淇℃伅
+ *
+ * @param bo 鑿滃崟淇℃伅
+ * @return 缁撴灉
+ */
+ int insertMenu(SysMenuBo bo);
+
+ /**
+ * 淇敼淇濆瓨鑿滃崟淇℃伅
+ *
+ * @param bo 鑿滃崟淇℃伅
+ * @return 缁撴灉
+ */
+ int updateMenu(SysMenuBo bo);
+
+ /**
+ * 鍒犻櫎鑿滃崟绠$悊淇℃伅
+ *
+ * @param menuId 鑿滃崟ID
+ * @return 缁撴灉
+ */
+ int deleteMenuById(Long menuId);
+
+ /**
+ * 鎵归噺鍒犻櫎鑿滃崟绠$悊淇℃伅
+ *
+ * @param menuIds 鑿滃崟ID涓�
+ * @return 缁撴灉
+ */
+ void deleteMenuById(List<Long> menuIds);
+
+ /**
+ * 鏍¢獙鑿滃崟鍚嶇О鏄惁鍞竴
+ *
+ * @param menu 鑿滃崟淇℃伅
+ * @return 缁撴灉
+ */
+ boolean checkMenuNameUnique(SysMenuBo menu);
+
+ /**
+ * 鏍¢獙璺敱缁勫悎鏄惁鍞竴
+ *
+ * @param menu 鑿滃崟淇℃伅
+ * @return 缁撴灉
+ */
+ boolean checkRouteConfigUnique(SysMenuBo menu);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysNoticeService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysNoticeService.java
new file mode 100755
index 0000000..8482cd0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysNoticeService.java
@@ -0,0 +1,73 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysNoticeBo;
+import org.dromara.system.domain.vo.SysNoticeVo;
+
+import java.util.List;
+
+/**
+ * 鍏憡 鏈嶅姟灞�
+ *
+ * @author Lion Li
+ */
+public interface ISysNoticeService {
+
+ /**
+ * 鍒嗛〉鏌ヨ閫氱煡鍏憡鍒楄〃
+ *
+ * @param notice 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 閫氱煡鍏憡鍒嗛〉鍒楄〃
+ */
+ TableDataInfo<SysNoticeVo> selectPageNoticeList(SysNoticeBo notice, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ鍏憡淇℃伅
+ *
+ * @param noticeId 鍏憡ID
+ * @return 鍏憡淇℃伅
+ */
+ SysNoticeVo selectNoticeById(Long noticeId);
+
+ /**
+ * 鏌ヨ鍏憡鍒楄〃
+ *
+ * @param notice 鍏憡淇℃伅
+ * @return 鍏憡闆嗗悎
+ */
+ List<SysNoticeVo> selectNoticeList(SysNoticeBo notice);
+
+ /**
+ * 鏂板鍏憡
+ *
+ * @param bo 鍏憡淇℃伅
+ * @return 缁撴灉
+ */
+ int insertNotice(SysNoticeBo bo);
+
+ /**
+ * 淇敼鍏憡
+ *
+ * @param bo 鍏憡淇℃伅
+ * @return 缁撴灉
+ */
+ int updateNotice(SysNoticeBo bo);
+
+ /**
+ * 鍒犻櫎鍏憡淇℃伅
+ *
+ * @param noticeId 鍏憡ID
+ * @return 缁撴灉
+ */
+ int deleteNoticeById(Long noticeId);
+
+ /**
+ * 鎵归噺鍒犻櫎鍏憡淇℃伅
+ *
+ * @param noticeIds 闇�瑕佸垹闄ょ殑鍏憡ID
+ * @return 缁撴灉
+ */
+ int deleteNoticeByIds(Long[] noticeIds);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOperLogService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOperLogService.java
new file mode 100755
index 0000000..e8b340c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOperLogService.java
@@ -0,0 +1,61 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysOperLogBo;
+import org.dromara.system.domain.vo.SysOperLogVo;
+
+import java.util.List;
+
+/**
+ * 鎿嶄綔鏃ュ織 鏈嶅姟灞�
+ *
+ * @author Lion Li
+ */
+public interface ISysOperLogService {
+
+ /**
+ * 鍒嗛〉鏌ヨ鎿嶄綔鏃ュ織鍒楄〃
+ *
+ * @param operLog 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 鎿嶄綔鏃ュ織鍒嗛〉鍒楄〃
+ */
+ TableDataInfo<SysOperLogVo> selectPageOperLogList(SysOperLogBo operLog, PageQuery pageQuery);
+
+ /**
+ * 鏂板鎿嶄綔鏃ュ織
+ *
+ * @param bo 鎿嶄綔鏃ュ織瀵硅薄
+ */
+ void insertOperlog(SysOperLogBo bo);
+
+ /**
+ * 鏌ヨ绯荤粺鎿嶄綔鏃ュ織闆嗗悎
+ *
+ * @param operLog 鎿嶄綔鏃ュ織瀵硅薄
+ * @return 鎿嶄綔鏃ュ織闆嗗悎
+ */
+ List<SysOperLogVo> selectOperLogList(SysOperLogBo operLog);
+
+ /**
+ * 鎵归噺鍒犻櫎绯荤粺鎿嶄綔鏃ュ織
+ *
+ * @param operIds 闇�瑕佸垹闄ょ殑鎿嶄綔鏃ュ織ID
+ * @return 缁撴灉
+ */
+ int deleteOperLogByIds(Long[] operIds);
+
+ /**
+ * 鏌ヨ鎿嶄綔鏃ュ織璇︾粏
+ *
+ * @param operId 鎿嶄綔ID
+ * @return 鎿嶄綔鏃ュ織瀵硅薄
+ */
+ SysOperLogVo selectOperLogById(Long operId);
+
+ /**
+ * 娓呯┖鎿嶄綔鏃ュ織
+ */
+ void cleanOperLog();
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssConfigService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssConfigService.java
new file mode 100755
index 0000000..2f6dfc9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssConfigService.java
@@ -0,0 +1,64 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysOssConfigBo;
+import org.dromara.system.domain.vo.SysOssConfigVo;
+
+import java.util.Collection;
+
+/**
+ * 瀵硅薄瀛樺偍閰嶇疆Service鎺ュ彛
+ *
+ * @author Lion Li
+ * @author 瀛よ垷鐑熼洦
+ * @date 2021-08-13
+ */
+public interface ISysOssConfigService {
+
+ /**
+ * 鍒濆鍖朞SS閰嶇疆
+ */
+ void init();
+
+ /**
+ * 鏌ヨ鍗曚釜
+ */
+ SysOssConfigVo queryById(Long ossConfigId);
+
+ /**
+ * 鏌ヨ鍒楄〃
+ */
+ TableDataInfo<SysOssConfigVo> queryPageList(SysOssConfigBo bo, PageQuery pageQuery);
+
+ /**
+ * 鏍规嵁鏂板涓氬姟瀵硅薄鎻掑叆瀵硅薄瀛樺偍閰嶇疆
+ *
+ * @param bo 瀵硅薄瀛樺偍閰嶇疆鏂板涓氬姟瀵硅薄
+ * @return 缁撴灉
+ */
+ Boolean insertByBo(SysOssConfigBo bo);
+
+ /**
+ * 鏍规嵁缂栬緫涓氬姟瀵硅薄淇敼瀵硅薄瀛樺偍閰嶇疆
+ *
+ * @param bo 瀵硅薄瀛樺偍閰嶇疆缂栬緫涓氬姟瀵硅薄
+ * @return 缁撴灉
+ */
+ Boolean updateByBo(SysOssConfigBo bo);
+
+ /**
+ * 鏍¢獙骞跺垹闄ゆ暟鎹�
+ *
+ * @param ids 涓婚敭闆嗗悎
+ * @param isValid 鏄惁鏍¢獙,true-鍒犻櫎鍓嶆牎楠�,false-涓嶆牎楠�
+ * @return 缁撴灉
+ */
+ Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+ /**
+ * 鍚敤鍋滅敤鐘舵��
+ */
+ int updateOssConfigStatus(SysOssConfigBo bo);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssService.java
new file mode 100755
index 0000000..057c068
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOssService.java
@@ -0,0 +1,80 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysOssBo;
+import org.dromara.system.domain.vo.SysOssVo;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 鏂囦欢涓婁紶 鏈嶅姟灞�
+ *
+ * @author Lion Li
+ */
+public interface ISysOssService {
+
+ /**
+ * 鏌ヨOSS瀵硅薄瀛樺偍鍒楄〃
+ *
+ * @param sysOss OSS瀵硅薄瀛樺偍鍒嗛〉鏌ヨ瀵硅薄
+ * @param pageQuery 鍒嗛〉鏌ヨ瀹炰綋绫�
+ * @return 缁撴灉
+ */
+ TableDataInfo<SysOssVo> queryPageList(SysOssBo sysOss, PageQuery pageQuery);
+
+ /**
+ * 鏍规嵁涓�缁� ossIds 鑾峰彇瀵瑰簲鐨� SysOssVo 鍒楄〃
+ *
+ * @param ossIds 涓�缁勬枃浠跺湪鏁版嵁搴撲腑鐨勫敮涓�鏍囪瘑闆嗗悎
+ * @return 鍖呭惈 SysOssVo 瀵硅薄鐨勫垪琛�
+ */
+ List<SysOssVo> listByIds(Collection<Long> ossIds);
+
+ /**
+ * 鏍规嵁 ossId 浠庣紦瀛樻垨鏁版嵁搴撲腑鑾峰彇 SysOssVo 瀵硅薄
+ *
+ * @param ossId 鏂囦欢鍦ㄦ暟鎹簱涓殑鍞竴鏍囪瘑
+ * @return SysOssVo 瀵硅薄锛屽寘鍚枃浠朵俊鎭�
+ */
+ SysOssVo getById(Long ossId);
+
+ /**
+ * 涓婁紶 MultipartFile 鍒板璞″瓨鍌ㄦ湇鍔★紝骞朵繚瀛樻枃浠朵俊鎭埌鏁版嵁搴�
+ *
+ * @param file 瑕佷笂浼犵殑 MultipartFile 瀵硅薄
+ * @return 涓婁紶鎴愬姛鍚庣殑 SysOssVo 瀵硅薄锛屽寘鍚枃浠朵俊鎭�
+ */
+ SysOssVo upload(MultipartFile file);
+
+ /**
+ * 涓婁紶鏂囦欢鍒板璞″瓨鍌ㄦ湇鍔★紝骞朵繚瀛樻枃浠朵俊鎭埌鏁版嵁搴�
+ *
+ * @param file 瑕佷笂浼犵殑鏂囦欢瀵硅薄
+ * @return 涓婁紶鎴愬姛鍚庣殑 SysOssVo 瀵硅薄锛屽寘鍚枃浠朵俊鎭�
+ */
+ SysOssVo upload(File file);
+
+ /**
+ * 鏂囦欢涓嬭浇鏂规硶锛屾敮鎸佷竴娆℃�т笅杞藉畬鏁存枃浠�
+ *
+ * @param ossId OSS瀵硅薄ID
+ * @param response HttpServletResponse瀵硅薄锛岀敤浜庤缃搷搴斿ご鍜屽悜瀹㈡埛绔彂閫佹枃浠跺唴瀹�
+ */
+ void download(Long ossId, HttpServletResponse response) throws IOException;
+
+ /**
+ * 鍒犻櫎OSS瀵硅薄瀛樺偍
+ *
+ * @param ids OSS瀵硅薄ID涓�
+ * @param isValid 鍒ゆ柇鏄惁闇�瑕佹牎楠�
+ * @return 缁撴灉
+ */
+ Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPermissionService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPermissionService.java
new file mode 100755
index 0000000..0116df5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPermissionService.java
@@ -0,0 +1,28 @@
+package org.dromara.system.service;
+
+import java.util.Set;
+
+/**
+ * 鐢ㄦ埛鏉冮檺澶勭悊
+ *
+ * @author Lion Li
+ */
+public interface ISysPermissionService {
+
+ /**
+ * 鑾峰彇瑙掕壊鏁版嵁鏉冮檺
+ *
+ * @param userId 鐢ㄦ埛id
+ * @return 瑙掕壊鏉冮檺淇℃伅
+ */
+ Set<String> getRolePermission(Long userId);
+
+ /**
+ * 鑾峰彇鑿滃崟鏁版嵁鏉冮檺
+ *
+ * @param userId 鐢ㄦ埛id
+ * @return 鑿滃崟鏉冮檺淇℃伅
+ */
+ Set<String> getMenuPermission(Long userId);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPostService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPostService.java
new file mode 100755
index 0000000..1caaab2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPostService.java
@@ -0,0 +1,136 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysPostBo;
+import org.dromara.system.domain.vo.SysPostVo;
+
+import java.util.List;
+
+/**
+ * 宀椾綅淇℃伅 鏈嶅姟灞�
+ *
+ * @author Lion Li
+ */
+public interface ISysPostService {
+
+ /**
+ * 鍒嗛〉鏌ヨ宀椾綅鍒楄〃
+ *
+ * @param post 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 宀椾綅鍒嗛〉鍒楄〃
+ */
+ TableDataInfo<SysPostVo> selectPagePostList(SysPostBo post, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ宀椾綅淇℃伅闆嗗悎
+ *
+ * @param post 宀椾綅淇℃伅
+ * @return 宀椾綅鍒楄〃
+ */
+ List<SysPostVo> selectPostList(SysPostBo post);
+
+ /**
+ * 鏌ヨ鐢ㄦ埛鎵�灞炲矖浣嶇粍
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 宀椾綅ID
+ */
+ List<SysPostVo> selectPostsByUserId(Long userId);
+
+ /**
+ * 鏌ヨ鎵�鏈夊矖浣�
+ *
+ * @return 宀椾綅鍒楄〃
+ */
+ List<SysPostVo> selectPostAll();
+
+ /**
+ * 閫氳繃宀椾綅ID鏌ヨ宀椾綅淇℃伅
+ *
+ * @param postId 宀椾綅ID
+ * @return 瑙掕壊瀵硅薄淇℃伅
+ */
+ SysPostVo selectPostById(Long postId);
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鑾峰彇宀椾綅閫夋嫨妗嗗垪琛�
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 閫変腑宀椾綅ID鍒楄〃
+ */
+ List<Long> selectPostListByUserId(Long userId);
+
+ /**
+ * 閫氳繃宀椾綅ID涓叉煡璇㈠矖浣�
+ *
+ * @param postIds 宀椾綅id涓�
+ * @return 宀椾綅鍒楄〃淇℃伅
+ */
+ List<SysPostVo> selectPostByIds(List<Long> postIds);
+
+ /**
+ * 鏍¢獙宀椾綅鍚嶇О
+ *
+ * @param post 宀椾綅淇℃伅
+ * @return 缁撴灉
+ */
+ boolean checkPostNameUnique(SysPostBo post);
+
+ /**
+ * 鏍¢獙宀椾綅缂栫爜
+ *
+ * @param post 宀椾綅淇℃伅
+ * @return 缁撴灉
+ */
+ boolean checkPostCodeUnique(SysPostBo post);
+
+ /**
+ * 閫氳繃宀椾綅ID鏌ヨ宀椾綅浣跨敤鏁伴噺
+ *
+ * @param postId 宀椾綅ID
+ * @return 缁撴灉
+ */
+ long countUserPostById(Long postId);
+
+ /**
+ * 閫氳繃閮ㄩ棬ID鏌ヨ宀椾綅浣跨敤鏁伴噺
+ *
+ * @param deptId 閮ㄩ棬id
+ * @return 缁撴灉
+ */
+ long countPostByDeptId(Long deptId);
+
+ /**
+ * 鍒犻櫎宀椾綅淇℃伅
+ *
+ * @param postId 宀椾綅ID
+ * @return 缁撴灉
+ */
+ int deletePostById(Long postId);
+
+ /**
+ * 鎵归噺鍒犻櫎宀椾綅淇℃伅
+ *
+ * @param postIds 闇�瑕佸垹闄ょ殑宀椾綅ID
+ * @return 缁撴灉
+ */
+ int deletePostByIds(List<Long> postIds);
+
+ /**
+ * 鏂板淇濆瓨宀椾綅淇℃伅
+ *
+ * @param bo 宀椾綅淇℃伅
+ * @return 缁撴灉
+ */
+ int insertPost(SysPostBo bo);
+
+ /**
+ * 淇敼淇濆瓨宀椾綅淇℃伅
+ *
+ * @param bo 宀椾綅淇℃伅
+ * @return 缁撴灉
+ */
+ int updatePost(SysPostBo bo);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysRoleService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysRoleService.java
new file mode 100755
index 0000000..abbfc79
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysRoleService.java
@@ -0,0 +1,236 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.SysUserRole;
+import org.dromara.system.domain.bo.SysRoleBo;
+import org.dromara.system.domain.vo.SysRoleVo;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 瑙掕壊涓氬姟灞�
+ *
+ * @author Lion Li
+ */
+public interface ISysRoleService {
+
+ /**
+ * 鍒嗛〉鏌ヨ瑙掕壊鍒楄〃
+ *
+ * @param role 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 瑙掕壊鍒嗛〉鍒楄〃
+ */
+ TableDataInfo<SysRoleVo> selectPageRoleList(SysRoleBo role, PageQuery pageQuery);
+
+ /**
+ * 鏍规嵁鏉′欢鏌ヨ瑙掕壊鏁版嵁
+ *
+ * @param role 瑙掕壊淇℃伅
+ * @return 瑙掕壊鏁版嵁闆嗗悎淇℃伅
+ */
+ List<SysRoleVo> selectRoleList(SysRoleBo role);
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ瑙掕壊鍒楄〃
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 瑙掕壊鍒楄〃
+ */
+ List<SysRoleVo> selectRolesByUserId(Long userId);
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ瑙掕壊鍒楄〃(鍖呭惈琚巿鏉冪姸鎬�)
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 瑙掕壊鍒楄〃
+ */
+ List<SysRoleVo> selectRolesAuthByUserId(Long userId);
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ瑙掕壊鏉冮檺
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鏉冮檺鍒楄〃
+ */
+ Set<String> selectRolePermissionByUserId(Long userId);
+
+ /**
+ * 鏌ヨ鎵�鏈夎鑹�
+ *
+ * @return 瑙掕壊鍒楄〃
+ */
+ List<SysRoleVo> selectRoleAll();
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鑾峰彇瑙掕壊閫夋嫨妗嗗垪琛�
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 閫変腑瑙掕壊ID鍒楄〃
+ */
+ List<Long> selectRoleListByUserId(Long userId);
+
+ /**
+ * 閫氳繃瑙掕壊ID鏌ヨ瑙掕壊
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 瑙掕壊瀵硅薄淇℃伅
+ */
+ SysRoleVo selectRoleById(Long roleId);
+
+ /**
+ * 閫氳繃瑙掕壊ID涓叉煡璇㈣鑹�
+ *
+ * @param roleIds 瑙掕壊ID涓�
+ * @return 瑙掕壊鍒楄〃淇℃伅
+ */
+ List<SysRoleVo> selectRoleByIds(List<Long> roleIds);
+
+ /**
+ * 鏍¢獙瑙掕壊鍚嶇О鏄惁鍞竴
+ *
+ * @param role 瑙掕壊淇℃伅
+ * @return 缁撴灉
+ */
+ boolean checkRoleNameUnique(SysRoleBo role);
+
+ /**
+ * 鏍¢獙瑙掕壊鏉冮檺鏄惁鍞竴
+ *
+ * @param role 瑙掕壊淇℃伅
+ * @return 缁撴灉
+ */
+ boolean checkRoleKeyUnique(SysRoleBo role);
+
+ /**
+ * 鏍¢獙瑙掕壊鏄惁鍏佽鎿嶄綔
+ *
+ * @param role 瑙掕壊淇℃伅
+ */
+ void checkRoleAllowed(SysRoleBo role);
+
+ /**
+ * 鏍¢獙瑙掕壊鏄惁鏈夋暟鎹潈闄�
+ *
+ * @param roleId 瑙掕壊id
+ */
+ void checkRoleDataScope(Long roleId);
+
+ /**
+ * 鏍¢獙瑙掕壊鏄惁鏈夋暟鎹潈闄�
+ *
+ * @param roleIds 瑙掕壊ID鍒楄〃锛堟敮鎸佷紶鍗曚釜ID锛�
+ */
+ void checkRoleDataScope(List<Long> roleIds);
+
+ /**
+ * 閫氳繃瑙掕壊ID鏌ヨ瑙掕壊浣跨敤鏁伴噺
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 缁撴灉
+ */
+ long countUserRoleByRoleId(Long roleId);
+
+ /**
+ * 鏂板淇濆瓨瑙掕壊淇℃伅
+ *
+ * @param bo 瑙掕壊淇℃伅
+ * @return 缁撴灉
+ */
+ int insertRole(SysRoleBo bo);
+
+ /**
+ * 淇敼淇濆瓨瑙掕壊淇℃伅
+ *
+ * @param bo 瑙掕壊淇℃伅
+ * @return 缁撴灉
+ */
+ int updateRole(SysRoleBo bo);
+
+ /**
+ * 淇敼瑙掕壊鐘舵��
+ *
+ * @param roleId 瑙掕壊ID
+ * @param status 瑙掕壊鐘舵��
+ * @return 缁撴灉
+ */
+ int updateRoleStatus(Long roleId, String status);
+
+ /**
+ * 淇敼鏁版嵁鏉冮檺淇℃伅
+ *
+ * @param bo 瑙掕壊淇℃伅
+ * @return 缁撴灉
+ */
+ int authDataScope(SysRoleBo bo);
+
+ /**
+ * 閫氳繃瑙掕壊ID鍒犻櫎瑙掕壊
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 缁撴灉
+ */
+ int deleteRoleById(Long roleId);
+
+ /**
+ * 鎵归噺鍒犻櫎瑙掕壊淇℃伅
+ *
+ * @param roleIds 闇�瑕佸垹闄ょ殑瑙掕壊ID
+ * @return 缁撴灉
+ */
+ int deleteRoleByIds(List<Long> roleIds);
+
+ /**
+ * 鍙栨秷鎺堟潈鐢ㄦ埛瑙掕壊
+ *
+ * @param userRole 鐢ㄦ埛鍜岃鑹插叧鑱斾俊鎭�
+ * @return 缁撴灉
+ */
+ int deleteAuthUser(SysUserRole userRole);
+
+ /**
+ * 鎵归噺鍙栨秷鎺堟潈鐢ㄦ埛瑙掕壊
+ *
+ * @param roleId 瑙掕壊ID
+ * @param userIds 闇�瑕佸彇娑堟巿鏉冪殑鐢ㄦ埛鏁版嵁ID
+ * @return 缁撴灉
+ */
+ int deleteAuthUsers(Long roleId, Long[] userIds);
+
+ /**
+ * 鎵归噺閫夋嫨鎺堟潈鐢ㄦ埛瑙掕壊
+ *
+ * @param roleId 瑙掕壊ID
+ * @param userIds 闇�瑕佸垹闄ょ殑鐢ㄦ埛鏁版嵁ID
+ * @return 缁撴灉
+ */
+ int insertAuthUsers(Long roleId, Long[] userIds);
+
+ /**
+ * 鏍规嵁瑙掕壊ID娓呴櫎璇ヨ鑹插叧鑱旂殑鎵�鏈夊湪绾跨敤鎴风殑鐧诲綍鐘舵�侊紙韪㈠嚭鍦ㄧ嚎鐢ㄦ埛锛�
+ *
+ * <p>
+ * 鍏堝垽鏂鑹叉槸鍚︾粦瀹氱敤鎴凤紝鑻ユ棤缁戝畾鍒欑洿鎺ヨ繑鍥�
+ * 鐒跺悗閬嶅巻褰撳墠鎵�鏈夊湪绾縏oken锛屾煡鎵炬嫢鏈夎瑙掕壊鐨勭敤鎴峰苟寮哄埗鐧诲嚭
+ * 娉ㄦ剰锛氬湪绾跨敤鎴烽噺杩囧ぇ鏃讹紝鎿嶄綔鍙兘瀵艰嚧 Redis 闃诲锛岄渶璋ㄦ厧璋冪敤
+ * </p>
+ *
+ * @param roleId 瑙掕壊ID
+ */
+ void cleanOnlineUserByRole(Long roleId);
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鍒楄〃娓呴櫎瀵瑰簲鍦ㄧ嚎鐢ㄦ埛鐨勭櫥褰曠姸鎬侊紙韪㈠嚭鎸囧畾鐢ㄦ埛锛�
+ *
+ * <p>
+ * 閬嶅巻褰撳墠鎵�鏈夊湪绾縏oken锛屽尮閰嶇敤鎴稩D鍒楄〃涓殑鐢ㄦ埛锛屽己鍒剁櫥鍑�
+ * 娉ㄦ剰锛氬湪绾跨敤鎴烽噺杩囧ぇ鏃讹紝鎿嶄綔鍙兘瀵艰嚧 Redis 闃诲锛岄渶璋ㄦ厧璋冪敤
+ * </p>
+ *
+ * @param userIds 闇�瑕佹竻闄ょ殑鐢ㄦ埛ID鍒楄〃
+ */
+ void cleanOnlineUser(List<Long> userIds);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysSocialService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysSocialService.java
new file mode 100755
index 0000000..cc7016e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysSocialService.java
@@ -0,0 +1,53 @@
+package org.dromara.system.service;
+
+import org.dromara.system.domain.bo.SysSocialBo;
+import org.dromara.system.domain.vo.SysSocialVo;
+
+import java.util.List;
+
+/**
+ * 绀句細鍖栧叧绯籗ervice鎺ュ彛
+ *
+ * @author thiszhc
+ */
+public interface ISysSocialService {
+
+
+ /**
+ * 鏌ヨ绀句細鍖栧叧绯�
+ */
+ SysSocialVo queryById(String id);
+
+ /**
+ * 鏌ヨ绀句細鍖栧叧绯诲垪琛�
+ */
+ List<SysSocialVo> queryList(SysSocialBo bo);
+
+ /**
+ * 鏌ヨ绀句細鍖栧叧绯诲垪琛�
+ */
+ List<SysSocialVo> queryListByUserId(Long userId);
+
+ /**
+ * 鏂板鎺堟潈鍏崇郴
+ */
+ Boolean insertByBo(SysSocialBo bo);
+
+ /**
+ * 鏇存柊绀句細鍖栧叧绯�
+ */
+ Boolean updateByBo(SysSocialBo bo);
+
+ /**
+ * 鍒犻櫎绀句細鍖栧叧绯讳俊鎭�
+ */
+ Boolean deleteWithValidById(Long id);
+
+
+ /**
+ * 鏍规嵁 authId 鏌ヨ
+ */
+ List<SysSocialVo> selectByAuthId(String authId);
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysTenantPackageService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysTenantPackageService.java
new file mode 100755
index 0000000..d060b68
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysTenantPackageService.java
@@ -0,0 +1,62 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysTenantPackageBo;
+import org.dromara.system.domain.vo.SysTenantPackageVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 绉熸埛濂楅Service鎺ュ彛
+ *
+ * @author Michelle.Chung
+ */
+public interface ISysTenantPackageService {
+
+ /**
+ * 鏌ヨ绉熸埛濂楅
+ */
+ SysTenantPackageVo queryById(Long packageId);
+
+ /**
+ * 鏌ヨ绉熸埛濂楅鍒楄〃
+ */
+ TableDataInfo<SysTenantPackageVo> queryPageList(SysTenantPackageBo bo, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ绉熸埛濂楅宸插惎鐢ㄥ垪琛�
+ */
+ List<SysTenantPackageVo> selectList();
+
+ /**
+ * 鏌ヨ绉熸埛濂楅鍒楄〃
+ */
+ List<SysTenantPackageVo> queryList(SysTenantPackageBo bo);
+
+ /**
+ * 鏂板绉熸埛濂楅
+ */
+ Boolean insertByBo(SysTenantPackageBo bo);
+
+ /**
+ * 淇敼绉熸埛濂楅
+ */
+ Boolean updateByBo(SysTenantPackageBo bo);
+
+ /**
+ * 鏍¢獙濂楅鍚嶇О鏄惁鍞竴
+ */
+ boolean checkPackageNameUnique(SysTenantPackageBo bo);
+
+ /**
+ * 淇敼濂楅鐘舵��
+ */
+ int updatePackageStatus(SysTenantPackageBo bo);
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄ょ鎴峰椁愪俊鎭�
+ */
+ Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysTenantService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysTenantService.java
new file mode 100755
index 0000000..1c763e0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysTenantService.java
@@ -0,0 +1,92 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysTenantBo;
+import org.dromara.system.domain.vo.SysTenantVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 绉熸埛Service鎺ュ彛
+ *
+ * @author Michelle.Chung
+ */
+public interface ISysTenantService {
+
+ /**
+ * 鏌ヨ绉熸埛
+ */
+ SysTenantVo queryById(Long id);
+
+ /**
+ * 鍩轰簬绉熸埛ID鏌ヨ绉熸埛
+ */
+ SysTenantVo queryByTenantId(String tenantId);
+
+ /**
+ * 鏌ヨ绉熸埛鍒楄〃
+ */
+ TableDataInfo<SysTenantVo> queryPageList(SysTenantBo bo, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ绉熸埛鍒楄〃
+ */
+ List<SysTenantVo> queryList(SysTenantBo bo);
+
+ /**
+ * 鏂板绉熸埛
+ */
+ Boolean insertByBo(SysTenantBo bo);
+
+ /**
+ * 淇敼绉熸埛
+ */
+ Boolean updateByBo(SysTenantBo bo);
+
+ /**
+ * 淇敼绉熸埛鐘舵��
+ */
+ int updateTenantStatus(SysTenantBo bo);
+
+ /**
+ * 鏍¢獙绉熸埛鏄惁鍏佽鎿嶄綔
+ */
+ void checkTenantAllowed(String tenantId);
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄ょ鎴蜂俊鎭�
+ */
+ Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+ /**
+ * 鏍¢獙浼佷笟鍚嶇О鏄惁鍞竴
+ */
+ boolean checkCompanyNameUnique(SysTenantBo bo);
+
+ /**
+ * 鏍¢獙璐﹀彿浣欓
+ */
+ boolean checkAccountBalance(String tenantId);
+
+ /**
+ * 鏍¢獙鏈夋晥鏈�
+ */
+ boolean checkExpireTime(String tenantId);
+
+ /**
+ * 鍚屾绉熸埛濂楅
+ */
+ Boolean syncTenantPackage(String tenantId, Long packageId);
+
+ /**
+ * 鍚屾绉熸埛瀛楀吀
+ */
+ void syncTenantDict();
+
+ /**
+ * 鍚屾绉熸埛鍙傛暟閰嶇疆
+ */
+ void syncTenantConfig();
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java
new file mode 100755
index 0000000..9e255f9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java
@@ -0,0 +1,231 @@
+package org.dromara.system.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysUserBo;
+import org.dromara.system.domain.vo.SysUserExportVo;
+import org.dromara.system.domain.vo.SysUserVo;
+
+import java.util.List;
+
+/**
+ * 鐢ㄦ埛 涓氬姟灞�
+ *
+ * @author Lion Li
+ */
+public interface ISysUserService {
+
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ鐢ㄦ埛鍒楄〃
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @param pageQuery 鍙戝憿涔�
+ * @return 鐢ㄦ埛淇℃伅
+ */
+ TableDataInfo<SysUserVo> selectPageUserList(SysUserBo user, PageQuery pageQuery);
+
+ /**
+ * 瀵煎嚭鐢ㄦ埛鍒楄〃
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 鐢ㄦ埛淇℃伅闆嗗悎淇℃伅
+ */
+ List<SysUserExportVo> selectUserExportList(SysUserBo user);
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ宸插垎閰嶇敤鎴疯鑹插垪琛�
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @param pageQuery 鍒嗛〉
+ * @return 鐢ㄦ埛淇℃伅闆嗗悎淇℃伅
+ */
+ TableDataInfo<SysUserVo> selectAllocatedList(SysUserBo user, PageQuery pageQuery);
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ鏈垎閰嶇敤鎴疯鑹插垪琛�
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @param pageQuery 鍒嗛〉
+ * @return 鐢ㄦ埛淇℃伅闆嗗悎淇℃伅
+ */
+ TableDataInfo<SysUserVo> selectUnallocatedList(SysUserBo user, PageQuery pageQuery);
+
+ /**
+ * 閫氳繃鐢ㄦ埛鍚嶆煡璇㈢敤鎴�
+ *
+ * @param userName 鐢ㄦ埛鍚�
+ * @return 鐢ㄦ埛瀵硅薄淇℃伅
+ */
+ SysUserVo selectUserByUserName(String userName);
+
+ /**
+ * 閫氳繃鎵嬫満鍙锋煡璇㈢敤鎴�
+ *
+ * @param phonenumber 鎵嬫満鍙�
+ * @return 鐢ㄦ埛瀵硅薄淇℃伅
+ */
+ SysUserVo selectUserByPhonenumber(String phonenumber);
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鐢ㄦ埛瀵硅薄淇℃伅
+ */
+ SysUserVo selectUserById(Long userId);
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID涓叉煡璇㈢敤鎴�
+ *
+ * @param userIds 鐢ㄦ埛ID涓�
+ * @param deptId 閮ㄩ棬id
+ * @return 鐢ㄦ埛鍒楄〃淇℃伅
+ */
+ List<SysUserVo> selectUserByIds(List<Long> userIds, Long deptId);
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ鐢ㄦ埛鎵�灞炶鑹茬粍
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 缁撴灉
+ */
+ String selectUserRoleGroup(Long userId);
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ鐢ㄦ埛鎵�灞炲矖浣嶇粍
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 缁撴灉
+ */
+ String selectUserPostGroup(Long userId);
+
+ /**
+ * 鏍¢獙鐢ㄦ埛鍚嶇О鏄惁鍞竴
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 缁撴灉
+ */
+ boolean checkUserNameUnique(SysUserBo user);
+
+ /**
+ * 鏍¢獙鎵嬫満鍙风爜鏄惁鍞竴
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 缁撴灉
+ */
+ boolean checkPhoneUnique(SysUserBo user);
+
+ /**
+ * 鏍¢獙email鏄惁鍞竴
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 缁撴灉
+ */
+ boolean checkEmailUnique(SysUserBo user);
+
+ /**
+ * 鏍¢獙鐢ㄦ埛鏄惁鍏佽鎿嶄綔
+ *
+ * @param userId 鐢ㄦ埛ID
+ */
+ void checkUserAllowed(Long userId);
+
+ /**
+ * 鏍¢獙鐢ㄦ埛鏄惁鏈夋暟鎹潈闄�
+ *
+ * @param userId 鐢ㄦ埛id
+ */
+ void checkUserDataScope(Long userId);
+
+ /**
+ * 鏂板鐢ㄦ埛淇℃伅
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 缁撴灉
+ */
+ int insertUser(SysUserBo user);
+
+ /**
+ * 娉ㄥ唽鐢ㄦ埛淇℃伅
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 缁撴灉
+ */
+ boolean registerUser(SysUserBo user, String tenantId);
+
+ /**
+ * 淇敼鐢ㄦ埛淇℃伅
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 缁撴灉
+ */
+ int updateUser(SysUserBo user);
+
+ /**
+ * 鐢ㄦ埛鎺堟潈瑙掕壊
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @param roleIds 瑙掕壊缁�
+ */
+ void insertUserAuth(Long userId, Long[] roleIds);
+
+ /**
+ * 淇敼鐢ㄦ埛鐘舵��
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @param status 璐﹀彿鐘舵��
+ * @return 缁撴灉
+ */
+ int updateUserStatus(Long userId, String status);
+
+ /**
+ * 淇敼鐢ㄦ埛鍩烘湰淇℃伅
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 缁撴灉
+ */
+ int updateUserProfile(SysUserBo user);
+
+ /**
+ * 淇敼鐢ㄦ埛澶村儚
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @param avatar 澶村儚鍦板潃
+ * @return 缁撴灉
+ */
+ boolean updateUserAvatar(Long userId, Long avatar);
+
+ /**
+ * 閲嶇疆鐢ㄦ埛瀵嗙爜
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @param password 瀵嗙爜
+ * @return 缁撴灉
+ */
+ int resetUserPwd(Long userId, String password);
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鍒犻櫎鐢ㄦ埛
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 缁撴灉
+ */
+ int deleteUserById(Long userId);
+
+ /**
+ * 鎵归噺鍒犻櫎鐢ㄦ埛淇℃伅
+ *
+ * @param userIds 闇�瑕佸垹闄ょ殑鐢ㄦ埛ID
+ * @return 缁撴灉
+ */
+ int deleteUserByIds(Long[] userIds);
+
+ /**
+ * 閫氳繃閮ㄩ棬id鏌ヨ褰撳墠閮ㄩ棬鎵�鏈夌敤鎴�
+ *
+ * @param deptId 閮ㄩ棬id
+ * @return 缁撴灉
+ */
+ List<SysUserVo> selectUserListByDept(Long deptId);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java
new file mode 100755
index 0000000..fc469ad
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java
@@ -0,0 +1,155 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.crypto.SecureUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.SysClient;
+import org.dromara.system.domain.bo.SysClientBo;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.system.mapper.SysClientMapper;
+import org.dromara.system.service.ISysClientService;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 瀹㈡埛绔鐞哠ervice涓氬姟灞傚鐞�
+ *
+ * @author Michelle.Chung
+ * @date 2023-06-18
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class SysClientServiceImpl implements ISysClientService {
+
+ private final SysClientMapper baseMapper;
+
+ /**
+ * 鏌ヨ瀹㈡埛绔鐞�
+ */
+ @Override
+ public SysClientVo queryById(Long id) {
+ SysClientVo vo = baseMapper.selectVoById(id);
+ vo.setGrantTypeList(StringUtils.splitList(vo.getGrantType()));
+ return vo;
+ }
+
+ /**
+ * 鏌ヨ瀹㈡埛绔鐞�
+ */
+ @Cacheable(cacheNames = CacheNames.SYS_CLIENT, key = "#clientId")
+ @Override
+ public SysClientVo queryByClientId(String clientId) {
+ return baseMapper.selectVoOne(new LambdaQueryWrapper<SysClient>().eq(SysClient::getClientId, clientId));
+ }
+
+ /**
+ * 鏌ヨ瀹㈡埛绔鐞嗗垪琛�
+ */
+ @Override
+ public TableDataInfo<SysClientVo> queryPageList(SysClientBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper<SysClient> lqw = buildQueryWrapper(bo);
+ Page<SysClientVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ result.getRecords().forEach(r -> r.setGrantTypeList(StringUtils.splitList(r.getGrantType())));
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 鏌ヨ瀹㈡埛绔鐞嗗垪琛�
+ */
+ @Override
+ public List<SysClientVo> queryList(SysClientBo bo) {
+ LambdaQueryWrapper<SysClient> lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper<SysClient> buildQueryWrapper(SysClientBo bo) {
+ LambdaQueryWrapper<SysClient> lqw = Wrappers.lambdaQuery();
+ lqw.eq(StringUtils.isNotBlank(bo.getClientId()), SysClient::getClientId, bo.getClientId());
+ lqw.eq(StringUtils.isNotBlank(bo.getClientKey()), SysClient::getClientKey, bo.getClientKey());
+ lqw.eq(StringUtils.isNotBlank(bo.getClientSecret()), SysClient::getClientSecret, bo.getClientSecret());
+ lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysClient::getStatus, bo.getStatus());
+ lqw.orderByAsc(SysClient::getId);
+ return lqw;
+ }
+
+ /**
+ * 鏂板瀹㈡埛绔鐞�
+ */
+ @Override
+ public Boolean insertByBo(SysClientBo bo) {
+ SysClient add = MapstructUtils.convert(bo, SysClient.class);
+ add.setGrantType(CollUtil.join(bo.getGrantTypeList(), StringUtils.SEPARATOR));
+ // 鐢熸垚clientid
+ String clientKey = bo.getClientKey();
+ String clientSecret = bo.getClientSecret();
+ add.setClientId(SecureUtil.md5(clientKey + clientSecret));
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setId(add.getId());
+ }
+ return flag;
+ }
+
+ /**
+ * 淇敼瀹㈡埛绔鐞�
+ */
+ @CacheEvict(cacheNames = CacheNames.SYS_CLIENT, key = "#bo.clientId")
+ @Override
+ public Boolean updateByBo(SysClientBo bo) {
+ SysClient update = MapstructUtils.convert(bo, SysClient.class);
+ update.setGrantType(StringUtils.joinComma(bo.getGrantTypeList()));
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 淇敼鐘舵��
+ */
+ @CacheEvict(cacheNames = CacheNames.SYS_CLIENT, key = "#clientId")
+ @Override
+ public int updateClientStatus(String clientId, String status) {
+ return baseMapper.update(null,
+ new LambdaUpdateWrapper<SysClient>()
+ .set(SysClient::getStatus, status)
+ .eq(SysClient::getClientId, clientId));
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎瀹㈡埛绔鐞�
+ */
+ @CacheEvict(cacheNames = CacheNames.SYS_CLIENT, allEntries = true)
+ @Override
+ public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+
+ /**
+ * 鏍¢獙瀹㈡埛绔痥ey鏄惁鍞竴
+ *
+ * @param client 瀹㈡埛绔俊鎭�
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkClickKeyUnique(SysClientBo client) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysClient>()
+ .eq(SysClient::getClientKey, client.getClientKey())
+ .ne(ObjectUtil.isNotNull(client.getId()), SysClient::getId, client.getId()));
+ return !exist;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java
new file mode 100755
index 0000000..6b0b2a2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java
@@ -0,0 +1,268 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.ConfigService;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.ObjectUtils;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.redis.utils.CacheUtils;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.domain.SysConfig;
+import org.dromara.system.domain.bo.SysConfigBo;
+import org.dromara.system.domain.vo.SysConfigVo;
+import org.dromara.system.mapper.SysConfigMapper;
+import org.dromara.system.service.ISysConfigService;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鍙傛暟閰嶇疆 鏈嶅姟灞傚疄鐜�
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Service
+public class SysConfigServiceImpl implements ISysConfigService, ConfigService {
+
+ private final SysConfigMapper baseMapper;
+
+ /**
+ * 鍒嗛〉鏌ヨ鍙傛暟閰嶇疆鍒楄〃
+ *
+ * @param config 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 鍙傛暟閰嶇疆鍒嗛〉鍒楄〃
+ */
+ @Override
+ public TableDataInfo<SysConfigVo> selectPageConfigList(SysConfigBo config, PageQuery pageQuery) {
+ LambdaQueryWrapper<SysConfig> lqw = buildQueryWrapper(config);
+ Page<SysConfigVo> page = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏌ヨ鍙傛暟閰嶇疆淇℃伅
+ *
+ * @param configId 鍙傛暟閰嶇疆ID
+ * @return 鍙傛暟閰嶇疆淇℃伅
+ */
+ @Override
+ public SysConfigVo selectConfigById(Long configId) {
+ return baseMapper.selectVoById(configId);
+ }
+
+ /**
+ * 鏍规嵁閿悕鏌ヨ鍙傛暟閰嶇疆淇℃伅
+ *
+ * @param configKey 鍙傛暟key
+ * @return 鍙傛暟閿��
+ */
+ @Cacheable(cacheNames = CacheNames.SYS_CONFIG, key = "#configKey")
+ @Override
+ public String selectConfigByKey(String configKey) {
+ SysConfig retConfig = baseMapper.selectOne(new LambdaQueryWrapper<SysConfig>()
+ .eq(SysConfig::getConfigKey, configKey));
+ return ObjectUtils.notNullGetter(retConfig, SysConfig::getConfigValue, StringUtils.EMPTY);
+ }
+
+ /**
+ * 鑾峰彇娉ㄥ唽寮�鍏�
+ *
+ * @param tenantId 绉熸埛id
+ * @return true寮�鍚紝false鍏抽棴
+ */
+ @Override
+ public boolean selectRegisterEnabled(String tenantId) {
+ String configValue = TenantHelper.dynamic(tenantId, () ->
+ this.selectConfigByKey("sys.account.registerUser")
+ );
+ return Convert.toBool(configValue);
+ }
+
+ /**
+ * 鏌ヨ鍙傛暟閰嶇疆鍒楄〃
+ *
+ * @param config 鍙傛暟閰嶇疆淇℃伅
+ * @return 鍙傛暟閰嶇疆闆嗗悎
+ */
+ @Override
+ public List<SysConfigVo> selectConfigList(SysConfigBo config) {
+ LambdaQueryWrapper<SysConfig> lqw = buildQueryWrapper(config);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper<SysConfig> buildQueryWrapper(SysConfigBo bo) {
+ Map<String, Object> params = bo.getParams();
+ LambdaQueryWrapper<SysConfig> lqw = Wrappers.lambdaQuery();
+ lqw.like(StringUtils.isNotBlank(bo.getConfigName()), SysConfig::getConfigName, bo.getConfigName());
+ lqw.eq(StringUtils.isNotBlank(bo.getConfigType()), SysConfig::getConfigType, bo.getConfigType());
+ lqw.like(StringUtils.isNotBlank(bo.getConfigKey()), SysConfig::getConfigKey, bo.getConfigKey());
+ lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
+ SysConfig::getCreateTime, params.get("beginTime"), params.get("endTime"));
+ lqw.orderByAsc(SysConfig::getConfigId);
+ return lqw;
+ }
+
+ /**
+ * 鏂板鍙傛暟閰嶇疆
+ *
+ * @param bo 鍙傛暟閰嶇疆淇℃伅
+ * @return 缁撴灉
+ */
+ @CachePut(cacheNames = CacheNames.SYS_CONFIG, key = "#bo.configKey")
+ @Override
+ public String insertConfig(SysConfigBo bo) {
+ SysConfig config = MapstructUtils.convert(bo, SysConfig.class);
+ int row = baseMapper.insert(config);
+ if (row > 0) {
+ return config.getConfigValue();
+ }
+ throw new ServiceException("鎿嶄綔澶辫触");
+ }
+
+ /**
+ * 淇敼鍙傛暟閰嶇疆
+ *
+ * @param bo 鍙傛暟閰嶇疆淇℃伅
+ * @return 缁撴灉
+ */
+ @CachePut(cacheNames = CacheNames.SYS_CONFIG, key = "#bo.configKey")
+ @Override
+ public String updateConfig(SysConfigBo bo) {
+ int row = 0;
+ SysConfig config = MapstructUtils.convert(bo, SysConfig.class);
+ if (config.getConfigId() != null) {
+ SysConfig temp = baseMapper.selectById(config.getConfigId());
+ if (!StringUtils.equals(temp.getConfigKey(), config.getConfigKey())) {
+ CacheUtils.evict(CacheNames.SYS_CONFIG, temp.getConfigKey());
+ }
+ row = baseMapper.updateById(config);
+ } else {
+ CacheUtils.evict(CacheNames.SYS_CONFIG, config.getConfigKey());
+ row = baseMapper.update(config, new LambdaQueryWrapper<SysConfig>()
+ .eq(SysConfig::getConfigKey, config.getConfigKey()));
+ }
+ if (row > 0) {
+ return config.getConfigValue();
+ }
+ throw new ServiceException("鎿嶄綔澶辫触");
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎鍙傛暟淇℃伅
+ *
+ * @param configIds 闇�瑕佸垹闄ょ殑鍙傛暟ID
+ */
+ @Override
+ public void deleteConfigByIds(List<Long> configIds) {
+ List<SysConfig> list = baseMapper.selectByIds(configIds);
+ list.forEach(config -> {
+ if (StringUtils.equals(SystemConstants.YES, config.getConfigType())) {
+ throw new ServiceException("鍐呯疆鍙傛暟銆恵}銆戜笉鑳藉垹闄�", config.getConfigKey());
+ }
+ CacheUtils.evict(CacheNames.SYS_CONFIG, config.getConfigKey());
+ });
+ baseMapper.deleteByIds(configIds);
+ }
+
+ /**
+ * 閲嶇疆鍙傛暟缂撳瓨鏁版嵁
+ */
+ @Override
+ public void resetConfigCache() {
+ CacheUtils.clear(CacheNames.SYS_CONFIG);
+ }
+
+ /**
+ * 鏍¢獙鍙傛暟閿悕鏄惁鍞竴
+ *
+ * @param config 鍙傛暟閰嶇疆淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkConfigKeyUnique(SysConfigBo config) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysConfig>()
+ .eq(SysConfig::getConfigKey, config.getConfigKey())
+ .ne(ObjectUtil.isNotNull(config.getConfigId()), SysConfig::getConfigId, config.getConfigId()));
+ return !exist;
+ }
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇鍙傛暟鍊�
+ *
+ * @param configKey 鍙傛暟 key
+ * @return 鍙傛暟鍊�
+ */
+ @Override
+ public String getConfigValue(String configKey) {
+ return SpringUtils.getAopProxy(this).selectConfigByKey(configKey);
+ }
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇 Map 绫诲瀷鐨勯厤缃�
+ *
+ * @param configKey 鍙傛暟 key
+ * @return Dict 瀵硅薄锛屽鏋滈厤缃负绌烘垨鏃犳硶瑙f瀽锛岃繑鍥炵┖ Dict
+ */
+ @Override
+ public Dict getConfigMap(String configKey) {
+ String configValue = getConfigValue(configKey);
+ return JsonUtils.parseMap(configValue);
+ }
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇 Map 绫诲瀷鐨勯厤缃垪琛�
+ *
+ * @param configKey 鍙傛暟 key
+ * @return Dict 鍒楄〃锛屽鏋滈厤缃负绌烘垨鏃犳硶瑙f瀽锛岃繑鍥炵┖鍒楄〃
+ */
+ @Override
+ public List<Dict> getConfigArrayMap(String configKey) {
+ String configValue = getConfigValue(configKey);
+ return JsonUtils.parseArrayMap(configValue);
+ }
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇鎸囧畾绫诲瀷鐨勯厤缃璞�
+ *
+ * @param configKey 鍙傛暟 key
+ * @param clazz 鐩爣瀵硅薄绫诲瀷
+ * @return 瀵硅薄瀹炰緥锛屽鏋滈厤缃负绌烘垨鏃犳硶瑙f瀽锛岃繑鍥� null
+ */
+ @Override
+ public <T> T getConfigObject(String configKey, Class<T> clazz) {
+ String configValue = getConfigValue(configKey);
+ return JsonUtils.parseObject(configValue, clazz);
+ }
+
+ /**
+ * 鏍规嵁鍙傛暟 key 鑾峰彇鎸囧畾绫诲瀷鐨勯厤缃垪琛�=
+ *
+ * @param configKey 鍙傛暟 key
+ * @param clazz 鐩爣鍏冪礌绫诲瀷
+ * @return 鎸囧畾绫诲瀷鍒楄〃锛屽鏋滈厤缃负绌烘垨鏃犳硶瑙f瀽锛岃繑鍥炵┖鍒楄〃
+ */
+ @Override
+ public <T> List<T> getConfigArray(String configKey, Class<T> clazz) {
+ String configValue = getConfigValue(configKey);
+ return JsonUtils.parseArray(configValue, clazz);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java
new file mode 100755
index 0000000..e300802
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java
@@ -0,0 +1,72 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.system.domain.SysRoleDept;
+import org.dromara.system.mapper.SysDeptMapper;
+import org.dromara.system.mapper.SysRoleDeptMapper;
+import org.dromara.system.service.ISysDataScopeService;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 鏁版嵁鏉冮檺 瀹炵幇
+ * <p>
+ * 娉ㄦ剰: 姝ervice鍐呬笉鍏佽璋冪敤鏍囨敞`鏁版嵁鏉冮檺`娉ㄨВ鐨勬柟娉�
+ * 渚嬪: deptMapper.selectList 姝� selectList 鏂规硶鏍囨敞浜哷鏁版嵁鏉冮檺`娉ㄨВ 浼氬嚭鐜板惊鐜В鏋愮殑闂
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Service("sdss")
+public class SysDataScopeServiceImpl implements ISysDataScopeService {
+
+ private final SysRoleDeptMapper roleDeptMapper;
+ private final SysDeptMapper deptMapper;
+
+ /**
+ * 鑾峰彇瑙掕壊鑷畾涔夋潈闄�
+ *
+ * @param roleId 瑙掕壊Id
+ * @return 閮ㄩ棬Id缁�
+ */
+ @Cacheable(cacheNames = CacheNames.SYS_ROLE_CUSTOM, key = "#roleId", condition = "#roleId != null")
+ @Override
+ public String getRoleCustom(Long roleId) {
+ if (ObjectUtil.isNull(roleId)) {
+ return "-1";
+ }
+ List<SysRoleDept> list = roleDeptMapper.selectList(
+ new LambdaQueryWrapper<SysRoleDept>()
+ .select(SysRoleDept::getDeptId)
+ .eq(SysRoleDept::getRoleId, roleId));
+ if (CollUtil.isNotEmpty(list)) {
+ return StreamUtils.join(list, rd -> Convert.toStr(rd.getDeptId()));
+ }
+ return "-1";
+ }
+
+ /**
+ * 鑾峰彇閮ㄩ棬鍙婁互涓嬫潈闄�
+ *
+ * @param deptId 閮ㄩ棬Id
+ * @return 閮ㄩ棬Id缁�
+ */
+ @Cacheable(cacheNames = CacheNames.SYS_DEPT_AND_CHILD, key = "#deptId", condition = "#deptId != null")
+ @Override
+ public String getDeptAndChild(Long deptId) {
+ if (ObjectUtil.isNull(deptId)) {
+ return "-1";
+ }
+ List<Long> deptIds = deptMapper.selectDeptAndChildById(deptId);
+ return CollUtil.isNotEmpty(deptIds) ? StreamUtils.join(deptIds, Convert::toStr) : "-1";
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java
new file mode 100755
index 0000000..4e6db15
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java
@@ -0,0 +1,426 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.dto.DeptDTO;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.DeptService;
+import org.dromara.common.core.utils.*;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.helper.DataBaseHelper;
+import org.dromara.common.redis.utils.CacheUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.SysDept;
+import org.dromara.system.domain.SysRole;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.bo.SysDeptBo;
+import org.dromara.system.domain.vo.SysDeptVo;
+import org.dromara.system.mapper.SysDeptMapper;
+import org.dromara.system.mapper.SysRoleMapper;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.system.service.ISysDeptService;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.cache.annotation.Caching;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
+/**
+ * 閮ㄩ棬绠$悊 鏈嶅姟瀹炵幇
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Service
+public class SysDeptServiceImpl implements ISysDeptService, DeptService {
+
+ private final SysDeptMapper baseMapper;
+ private final SysRoleMapper roleMapper;
+ private final SysUserMapper userMapper;
+
+ /**
+ * 鍒嗛〉鏌ヨ閮ㄩ棬绠$悊鏁版嵁
+ *
+ * @param dept 閮ㄩ棬淇℃伅
+ * @param pageQuery 鍒嗛〉瀵硅薄
+ * @return 閮ㄩ棬淇℃伅闆嗗悎
+ */
+ @Override
+ public TableDataInfo<SysDeptVo> selectPageDeptList(SysDeptBo dept, PageQuery pageQuery) {
+ Page<SysDeptVo> page = baseMapper.selectPageDeptList(pageQuery.build(), buildQueryWrapper(dept));
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏌ヨ閮ㄩ棬绠$悊鏁版嵁
+ *
+ * @param dept 閮ㄩ棬淇℃伅
+ * @return 閮ㄩ棬淇℃伅闆嗗悎
+ */
+ @Override
+ public List<SysDeptVo> selectDeptList(SysDeptBo dept) {
+ LambdaQueryWrapper<SysDept> lqw = buildQueryWrapper(dept);
+ return baseMapper.selectDeptList(lqw);
+ }
+
+ /**
+ * 鏌ヨ閮ㄩ棬鏍戠粨鏋勪俊鎭�
+ *
+ * @param bo 閮ㄩ棬淇℃伅
+ * @return 閮ㄩ棬鏍戜俊鎭泦鍚�
+ */
+ @Override
+ public List<Tree<Long>> selectDeptTreeList(SysDeptBo bo) {
+ LambdaQueryWrapper<SysDept> lqw = buildQueryWrapper(bo);
+ List<SysDeptVo> depts = baseMapper.selectDeptList(lqw);
+ return buildDeptTreeSelect(depts);
+ }
+
+ private LambdaQueryWrapper<SysDept> buildQueryWrapper(SysDeptBo bo) {
+ Map<String, Object> params = bo.getParams();
+ LambdaQueryWrapper<SysDept> lqw = Wrappers.lambdaQuery();
+ lqw.eq(SysDept::getDelFlag, SystemConstants.NORMAL);
+ lqw.eq(ObjectUtil.isNotNull(bo.getDeptId()), SysDept::getDeptId, bo.getDeptId());
+ lqw.eq(ObjectUtil.isNotNull(bo.getParentId()), SysDept::getParentId, bo.getParentId());
+ lqw.like(StringUtils.isNotBlank(bo.getDeptName()), SysDept::getDeptName, bo.getDeptName());
+ lqw.like(StringUtils.isNotBlank(bo.getDeptCategory()), SysDept::getDeptCategory, bo.getDeptCategory());
+ lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysDept::getStatus, bo.getStatus());
+ lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
+ SysDept::getCreateTime, params.get("beginTime"), params.get("endTime"));
+ lqw.orderByAsc(SysDept::getAncestors);
+ lqw.orderByAsc(SysDept::getParentId);
+ lqw.orderByAsc(SysDept::getOrderNum);
+ lqw.orderByAsc(SysDept::getDeptId);
+ if (ObjectUtil.isNotNull(bo.getBelongDeptId())) {
+ //閮ㄩ棬鏍戞悳绱�
+ lqw.and(x -> {
+ List<Long> deptIds = baseMapper.selectDeptAndChildById(bo.getBelongDeptId());
+ x.in(SysDept::getDeptId, deptIds);
+ });
+ }
+ return lqw;
+ }
+
+ /**
+ * 鏋勫缓鍓嶇鎵�闇�瑕佷笅鎷夋爲缁撴瀯
+ *
+ * @param depts 閮ㄩ棬鍒楄〃
+ * @return 涓嬫媺鏍戠粨鏋勫垪琛�
+ */
+ @Override
+ public List<Tree<Long>> buildDeptTreeSelect(List<SysDeptVo> depts) {
+ if (CollUtil.isEmpty(depts)) {
+ return CollUtil.newArrayList();
+ }
+ return TreeBuildUtils.buildMultiRoot(
+ depts,
+ SysDeptVo::getDeptId,
+ SysDeptVo::getParentId,
+ (node, treeNode) -> treeNode
+ .setId(node.getDeptId())
+ .setParentId(node.getParentId())
+ .setName(node.getDeptName())
+ .setWeight(node.getOrderNum())
+ .putExtra("disabled", SystemConstants.DISABLE.equals(node.getStatus()))
+ );
+ }
+
+ /**
+ * 鏍规嵁瑙掕壊ID鏌ヨ閮ㄩ棬鏍戜俊鎭�
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 閫変腑閮ㄩ棬鍒楄〃
+ */
+ @Override
+ public List<Long> selectDeptListByRoleId(Long roleId) {
+ SysRole role = roleMapper.selectById(roleId);
+ return baseMapper.selectDeptListByRoleId(roleId, role.getDeptCheckStrictly());
+ }
+
+ /**
+ * 鏍规嵁閮ㄩ棬ID鏌ヨ淇℃伅
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @return 閮ㄩ棬淇℃伅
+ */
+ @Cacheable(cacheNames = CacheNames.SYS_DEPT, key = "#deptId")
+ @Override
+ public SysDeptVo selectDeptById(Long deptId) {
+ SysDeptVo dept = baseMapper.selectVoById(deptId);
+ if (ObjectUtil.isNull(dept)) {
+ return null;
+ }
+ SysDeptVo parentDept = baseMapper.selectVoOne(new LambdaQueryWrapper<SysDept>()
+ .select(SysDept::getDeptName).eq(SysDept::getDeptId, dept.getParentId()));
+ dept.setParentName(ObjectUtils.notNullGetter(parentDept, SysDeptVo::getDeptName));
+ return dept;
+ }
+
+ @Override
+ public List<SysDeptVo> selectDeptByIds(List<Long> deptIds) {
+ return baseMapper.selectDeptList(new LambdaQueryWrapper<SysDept>()
+ .select(SysDept::getDeptId, SysDept::getDeptName, SysDept::getLeader)
+ .eq(SysDept::getStatus, SystemConstants.NORMAL)
+ .in(CollUtil.isNotEmpty(deptIds), SysDept::getDeptId, deptIds));
+ }
+
+ /**
+ * 閫氳繃閮ㄩ棬ID鏌ヨ閮ㄩ棬鍚嶇О
+ *
+ * @param deptIds 閮ㄩ棬ID涓查�楀彿鍒嗛殧
+ * @return 閮ㄩ棬鍚嶇О涓查�楀彿鍒嗛殧
+ */
+ @Override
+ public String selectDeptNameByIds(String deptIds) {
+ List<String> list = new ArrayList<>();
+ for (Long id : StringUtils.splitTo(deptIds, Convert::toLong)) {
+ SysDeptVo vo = SpringUtils.getAopProxy(this).selectDeptById(id);
+ if (ObjectUtil.isNotNull(vo)) {
+ list.add(vo.getDeptName());
+ }
+ }
+ return StringUtils.joinComma(list);
+ }
+
+ /**
+ * 鏍规嵁閮ㄩ棬ID鏌ヨ閮ㄩ棬璐熻矗浜�
+ *
+ * @param deptId 閮ㄩ棬ID锛岀敤浜庢寚瀹氶渶瑕佹煡璇㈢殑閮ㄩ棬
+ * @return 杩斿洖璇ラ儴闂ㄧ殑璐熻矗浜篒D
+ */
+ @Override
+ public Long selectDeptLeaderById(Long deptId) {
+ SysDeptVo vo = SpringUtils.getAopProxy(this).selectDeptById(deptId);
+ return vo.getLeader();
+ }
+
+ /**
+ * 鏌ヨ閮ㄩ棬
+ *
+ * @return 閮ㄩ棬鍒楄〃
+ */
+ @Override
+ public List<DeptDTO> selectDeptsByList() {
+ List<SysDeptVo> list = baseMapper.selectDeptList(new LambdaQueryWrapper<SysDept>()
+ .select(SysDept::getDeptId, SysDept::getDeptName, SysDept::getParentId)
+ .eq(SysDept::getStatus, SystemConstants.NORMAL));
+ return BeanUtil.copyToList(list, DeptDTO.class);
+ }
+
+ /**
+ * 鏍规嵁ID鏌ヨ鎵�鏈夊瓙閮ㄩ棬鏁帮紙姝e父鐘舵�侊級
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @return 瀛愰儴闂ㄦ暟
+ */
+ @Override
+ public long selectNormalChildrenDeptById(Long deptId) {
+ return baseMapper.selectCount(new LambdaQueryWrapper<SysDept>()
+ .eq(SysDept::getStatus, SystemConstants.NORMAL)
+ .apply(DataBaseHelper.findInSet(deptId, "ancestors")));
+ }
+
+ /**
+ * 鏄惁瀛樺湪瀛愯妭鐐�
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean hasChildByDeptId(Long deptId) {
+ return baseMapper.exists(new LambdaQueryWrapper<SysDept>()
+ .eq(SysDept::getParentId, deptId));
+ }
+
+ /**
+ * 鏌ヨ閮ㄩ棬鏄惁瀛樺湪鐢ㄦ埛
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @return 缁撴灉 true 瀛樺湪 false 涓嶅瓨鍦�
+ */
+ @Override
+ public boolean checkDeptExistUser(Long deptId) {
+ return userMapper.exists(new LambdaQueryWrapper<SysUser>()
+ .eq(SysUser::getDeptId, deptId));
+ }
+
+ /**
+ * 鏍¢獙閮ㄩ棬鍚嶇О鏄惁鍞竴
+ *
+ * @param dept 閮ㄩ棬淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkDeptNameUnique(SysDeptBo dept) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysDept>()
+ .eq(SysDept::getDeptName, dept.getDeptName())
+ .eq(SysDept::getParentId, dept.getParentId())
+ .ne(ObjectUtil.isNotNull(dept.getDeptId()), SysDept::getDeptId, dept.getDeptId()));
+ return !exist;
+ }
+
+ /**
+ * 鏍¢獙閮ㄩ棬鏄惁鏈夋暟鎹潈闄�
+ *
+ * @param deptId 閮ㄩ棬id
+ */
+ @Override
+ public void checkDeptDataScope(Long deptId) {
+ if (ObjectUtil.isNull(deptId)) {
+ return;
+ }
+ if (LoginHelper.isSuperAdmin()) {
+ return;
+ }
+ if (baseMapper.countDeptById(deptId) == 0) {
+ throw new ServiceException("娌℃湁鏉冮檺璁块棶閮ㄩ棬鏁版嵁锛�");
+ }
+ }
+
+ /**
+ * 鏂板淇濆瓨閮ㄩ棬淇℃伅
+ *
+ * @param bo 閮ㄩ棬淇℃伅
+ * @return 缁撴灉
+ */
+ @CacheEvict(cacheNames = CacheNames.SYS_DEPT_AND_CHILD, allEntries = true)
+ @Override
+ public int insertDept(SysDeptBo bo) {
+ SysDept info = baseMapper.selectById(bo.getParentId());
+ // 濡傛灉鐖惰妭鐐逛笉涓烘甯哥姸鎬�,鍒欎笉鍏佽鏂板瀛愯妭鐐�
+ if (!SystemConstants.NORMAL.equals(info.getStatus())) {
+ throw new ServiceException("閮ㄩ棬鍋滅敤锛屼笉鍏佽鏂板");
+ }
+ SysDept dept = MapstructUtils.convert(bo, SysDept.class);
+ dept.setAncestors(info.getAncestors() + StringUtils.SEPARATOR + dept.getParentId());
+ return baseMapper.insert(dept);
+ }
+
+ /**
+ * 淇敼淇濆瓨閮ㄩ棬淇℃伅
+ *
+ * @param bo 閮ㄩ棬淇℃伅
+ * @return 缁撴灉
+ */
+ @Caching(evict = {
+ @CacheEvict(cacheNames = CacheNames.SYS_DEPT, key = "#bo.deptId"),
+ @CacheEvict(cacheNames = CacheNames.SYS_DEPT_AND_CHILD, allEntries = true)
+ })
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int updateDept(SysDeptBo bo) {
+ SysDept dept = MapstructUtils.convert(bo, SysDept.class);
+ SysDept oldDept = baseMapper.selectById(dept.getDeptId());
+ if (ObjectUtil.isNull(oldDept)) {
+ throw new ServiceException("閮ㄩ棬涓嶅瓨鍦紝鏃犳硶淇敼");
+ }
+ if (!oldDept.getParentId().equals(dept.getParentId())) {
+ // 濡傛灉鏄柊鐖堕儴闂� 鍒欐牎楠屾槸鍚﹀叿鏈夋柊鐖堕儴闂ㄦ潈闄� 閬垮厤瓒婃潈
+ this.checkDeptDataScope(dept.getParentId());
+ SysDept newParentDept = baseMapper.selectById(dept.getParentId());
+ if (ObjectUtil.isNotNull(newParentDept)) {
+ String newAncestors = newParentDept.getAncestors() + StringUtils.SEPARATOR + newParentDept.getDeptId();
+ String oldAncestors = oldDept.getAncestors();
+ dept.setAncestors(newAncestors);
+ updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors);
+ }
+ } else {
+ dept.setAncestors(oldDept.getAncestors());
+ }
+ int result = baseMapper.updateById(dept);
+ // 濡傛灉閮ㄩ棬鐘舵�佷负鍚敤锛屼笖閮ㄩ棬绁栫骇鍒楄〃涓嶄负绌猴紝涓旈儴闂ㄧ绾у垪琛ㄤ笉绛変簬鏍归儴闂ㄧ绾у垪琛紙濡傛灉閮ㄩ棬绁栫骇鍒楄〃涓嶇瓑浜庢牴閮ㄩ棬绁栫骇鍒楄〃锛屽垯璇存槑瀛樺湪涓婄骇閮ㄩ棬锛�
+ if (SystemConstants.NORMAL.equals(dept.getStatus())
+ && StringUtils.isNotEmpty(dept.getAncestors())
+ && !StringUtils.equals(SystemConstants.ROOT_DEPT_ANCESTORS, dept.getAncestors())) {
+ // 濡傛灉璇ラ儴闂ㄦ槸鍚敤鐘舵�侊紝鍒欏惎鐢ㄨ閮ㄩ棬鐨勬墍鏈変笂绾ч儴闂�
+ updateParentDeptStatusNormal(dept);
+ }
+ return result;
+ }
+
+ /**
+ * 淇敼璇ラ儴闂ㄧ殑鐖剁骇閮ㄩ棬鐘舵��
+ *
+ * @param dept 褰撳墠閮ㄩ棬
+ */
+ private void updateParentDeptStatusNormal(SysDept dept) {
+ String ancestors = dept.getAncestors();
+ Long[] deptIds = Convert.toLongArray(ancestors);
+ baseMapper.update(null, new LambdaUpdateWrapper<SysDept>()
+ .set(SysDept::getStatus, SystemConstants.NORMAL)
+ .in(SysDept::getDeptId, Arrays.asList(deptIds)));
+ }
+
+ /**
+ * 淇敼瀛愬厓绱犲叧绯�
+ *
+ * @param deptId 琚慨鏀圭殑閮ㄩ棬ID
+ * @param newAncestors 鏂扮殑鐖禝D闆嗗悎
+ * @param oldAncestors 鏃х殑鐖禝D闆嗗悎
+ */
+ private void updateDeptChildren(Long deptId, String newAncestors, String oldAncestors) {
+ List<SysDept> children = baseMapper.selectList(new LambdaQueryWrapper<SysDept>()
+ .apply(DataBaseHelper.findInSet(deptId, "ancestors")));
+ List<SysDept> list = new ArrayList<>();
+ for (SysDept child : children) {
+ SysDept dept = new SysDept();
+ dept.setDeptId(child.getDeptId());
+ dept.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors));
+ list.add(dept);
+ }
+ if (CollUtil.isNotEmpty(list)) {
+ if (baseMapper.updateBatchById(list)) {
+ list.forEach(dept -> CacheUtils.evict(CacheNames.SYS_DEPT, dept.getDeptId()));
+ }
+ }
+ }
+
+ /**
+ * 鍒犻櫎閮ㄩ棬绠$悊淇℃伅
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @return 缁撴灉
+ */
+ @Caching(evict = {
+ @CacheEvict(cacheNames = CacheNames.SYS_DEPT, key = "#deptId"),
+ @CacheEvict(cacheNames = CacheNames.SYS_DEPT_AND_CHILD, key = "#deptId")
+ })
+ @Override
+ public int deleteDeptById(Long deptId) {
+ return baseMapper.deleteById(deptId);
+ }
+
+
+ /**
+ * 鏍规嵁閮ㄩ棬 ID 鍒楄〃鏌ヨ閮ㄩ棬鍚嶇О鏄犲皠鍏崇郴
+ *
+ * @param deptIds 閮ㄩ棬 ID 鍒楄〃
+ * @return Map锛屽叾涓� key 涓洪儴闂� ID锛寁alue 涓哄搴旂殑閮ㄩ棬鍚嶇О
+ */
+ @Override
+ public Map<Long, String> selectDeptNamesByIds(List<Long> deptIds) {
+ if (CollUtil.isEmpty(deptIds)) {
+ return Collections.emptyMap();
+ }
+ List<SysDept> list = baseMapper.selectList(
+ new LambdaQueryWrapper<SysDept>()
+ .select(SysDept::getDeptId, SysDept::getDeptName)
+ .in(SysDept::getDeptId, deptIds)
+ );
+ return StreamUtils.toMap(list, SysDept::getDeptId, SysDept::getDeptName);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictDataServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictDataServiceImpl.java
new file mode 100755
index 0000000..018f043
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictDataServiceImpl.java
@@ -0,0 +1,159 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.redis.utils.CacheUtils;
+import org.dromara.system.domain.SysDictData;
+import org.dromara.system.domain.bo.SysDictDataBo;
+import org.dromara.system.domain.vo.SysDictDataVo;
+import org.dromara.system.mapper.SysDictDataMapper;
+import org.dromara.system.service.ISysDictDataService;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 瀛楀吀 涓氬姟灞傚鐞�
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Service
+public class SysDictDataServiceImpl implements ISysDictDataService {
+
+ private final SysDictDataMapper baseMapper;
+
+ /**
+ * 鍒嗛〉鏌ヨ瀛楀吀鏁版嵁鍒楄〃
+ *
+ * @param dictData 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 瀛楀吀鏁版嵁鍒嗛〉鍒楄〃
+ */
+ @Override
+ public TableDataInfo<SysDictDataVo> selectPageDictDataList(SysDictDataBo dictData, PageQuery pageQuery) {
+ LambdaQueryWrapper<SysDictData> lqw = buildQueryWrapper(dictData);
+ Page<SysDictDataVo> page = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ瀛楀吀鏁版嵁
+ *
+ * @param dictData 瀛楀吀鏁版嵁淇℃伅
+ * @return 瀛楀吀鏁版嵁闆嗗悎淇℃伅
+ */
+ @Override
+ public List<SysDictDataVo> selectDictDataList(SysDictDataBo dictData) {
+ LambdaQueryWrapper<SysDictData> lqw = buildQueryWrapper(dictData);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper<SysDictData> buildQueryWrapper(SysDictDataBo bo) {
+ LambdaQueryWrapper<SysDictData> lqw = Wrappers.lambdaQuery();
+ lqw.eq(bo.getDictSort() != null, SysDictData::getDictSort, bo.getDictSort());
+ lqw.like(StringUtils.isNotBlank(bo.getDictLabel()), SysDictData::getDictLabel, bo.getDictLabel());
+ lqw.eq(StringUtils.isNotBlank(bo.getDictType()), SysDictData::getDictType, bo.getDictType());
+ lqw.orderByAsc(SysDictData::getDictSort, SysDictData::getDictCode);
+ return lqw;
+ }
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏搁敭鍊兼煡璇㈠瓧鍏告暟鎹俊鎭�
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @param dictValue 瀛楀吀閿��
+ * @return 瀛楀吀鏍囩
+ */
+ @Override
+ public String selectDictLabel(String dictType, String dictValue) {
+ return baseMapper.selectOne(new LambdaQueryWrapper<SysDictData>()
+ .select(SysDictData::getDictLabel)
+ .eq(SysDictData::getDictType, dictType)
+ .eq(SysDictData::getDictValue, dictValue))
+ .getDictLabel();
+ }
+
+ /**
+ * 鏍规嵁瀛楀吀鏁版嵁ID鏌ヨ淇℃伅
+ *
+ * @param dictCode 瀛楀吀鏁版嵁ID
+ * @return 瀛楀吀鏁版嵁
+ */
+ @Override
+ public SysDictDataVo selectDictDataById(Long dictCode) {
+ return baseMapper.selectVoById(dictCode);
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎瀛楀吀鏁版嵁淇℃伅
+ *
+ * @param dictCodes 闇�瑕佸垹闄ょ殑瀛楀吀鏁版嵁ID
+ */
+ @Override
+ public void deleteDictDataByIds(List<Long> dictCodes) {
+ List<SysDictData> list = baseMapper.selectByIds(dictCodes);
+ baseMapper.deleteByIds(dictCodes);
+ list.forEach(x -> CacheUtils.evict(CacheNames.SYS_DICT, x.getDictType()));
+ }
+
+ /**
+ * 鏂板淇濆瓨瀛楀吀鏁版嵁淇℃伅
+ *
+ * @param bo 瀛楀吀鏁版嵁淇℃伅
+ * @return 缁撴灉
+ */
+ @CachePut(cacheNames = CacheNames.SYS_DICT, key = "#bo.dictType")
+ @Override
+ public List<SysDictDataVo> insertDictData(SysDictDataBo bo) {
+ SysDictData data = MapstructUtils.convert(bo, SysDictData.class);
+ int row = baseMapper.insert(data);
+ if (row > 0) {
+ return baseMapper.selectDictDataByType(data.getDictType());
+ }
+ throw new ServiceException("鎿嶄綔澶辫触");
+ }
+
+ /**
+ * 淇敼淇濆瓨瀛楀吀鏁版嵁淇℃伅
+ *
+ * @param bo 瀛楀吀鏁版嵁淇℃伅
+ * @return 缁撴灉
+ */
+ @CachePut(cacheNames = CacheNames.SYS_DICT, key = "#bo.dictType")
+ @Override
+ public List<SysDictDataVo> updateDictData(SysDictDataBo bo) {
+ SysDictData data = MapstructUtils.convert(bo, SysDictData.class);
+ int row = baseMapper.updateById(data);
+ if (row > 0) {
+ return baseMapper.selectDictDataByType(data.getDictType());
+ }
+ throw new ServiceException("鎿嶄綔澶辫触");
+ }
+
+ /**
+ * 鏍¢獙瀛楀吀閿�兼槸鍚﹀敮涓�
+ *
+ * @param dict 瀛楀吀鏁版嵁
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkDictDataUnique(SysDictDataBo dict) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysDictData>()
+ .eq(SysDictData::getDictType, dict.getDictType())
+ .eq(SysDictData::getDictValue, dict.getDictValue())
+ .ne(ObjectUtil.isNotNull(dict.getDictCode()), SysDictData::getDictCode, dict.getDictCode()));
+ return !exist;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java
new file mode 100755
index 0000000..6d4d9fe
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java
@@ -0,0 +1,304 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.domain.dto.DictDataDTO;
+import org.dromara.common.core.domain.dto.DictTypeDTO;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.redis.utils.CacheUtils;
+import org.dromara.system.domain.SysDictData;
+import org.dromara.system.domain.SysDictType;
+import org.dromara.system.domain.bo.SysDictTypeBo;
+import org.dromara.system.domain.vo.SysDictDataVo;
+import org.dromara.system.domain.vo.SysDictTypeVo;
+import org.dromara.system.mapper.SysDictDataMapper;
+import org.dromara.system.mapper.SysDictTypeMapper;
+import org.dromara.system.service.ISysDictTypeService;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 瀛楀吀 涓氬姟灞傚鐞�
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Service
+public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService {
+
+ private final SysDictTypeMapper baseMapper;
+ private final SysDictDataMapper dictDataMapper;
+
+ /**
+ * 鍒嗛〉鏌ヨ瀛楀吀绫诲瀷鍒楄〃
+ *
+ * @param dictType 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 瀛楀吀绫诲瀷鍒嗛〉鍒楄〃
+ */
+ @Override
+ public TableDataInfo<SysDictTypeVo> selectPageDictTypeList(SysDictTypeBo dictType, PageQuery pageQuery) {
+ LambdaQueryWrapper<SysDictType> lqw = buildQueryWrapper(dictType);
+ Page<SysDictTypeVo> page = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ瀛楀吀绫诲瀷
+ *
+ * @param dictType 瀛楀吀绫诲瀷淇℃伅
+ * @return 瀛楀吀绫诲瀷闆嗗悎淇℃伅
+ */
+ @Override
+ public List<SysDictTypeVo> selectDictTypeList(SysDictTypeBo dictType) {
+ LambdaQueryWrapper<SysDictType> lqw = buildQueryWrapper(dictType);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper<SysDictType> buildQueryWrapper(SysDictTypeBo bo) {
+ Map<String, Object> params = bo.getParams();
+ LambdaQueryWrapper<SysDictType> lqw = Wrappers.lambdaQuery();
+ lqw.like(StringUtils.isNotBlank(bo.getDictName()), SysDictType::getDictName, bo.getDictName());
+ lqw.like(StringUtils.isNotBlank(bo.getDictType()), SysDictType::getDictType, bo.getDictType());
+ lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
+ SysDictType::getCreateTime, params.get("beginTime"), params.get("endTime"));
+ lqw.orderByAsc(SysDictType::getDictId);
+ return lqw;
+ }
+
+ /**
+ * 鏍规嵁鎵�鏈夊瓧鍏哥被鍨�
+ *
+ * @return 瀛楀吀绫诲瀷闆嗗悎淇℃伅
+ */
+ @Override
+ public List<SysDictTypeVo> selectDictTypeAll() {
+ return baseMapper.selectVoList();
+ }
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鏌ヨ瀛楀吀鏁版嵁
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return 瀛楀吀鏁版嵁闆嗗悎淇℃伅
+ */
+ @Cacheable(cacheNames = CacheNames.SYS_DICT, key = "#dictType")
+ @Override
+ public List<SysDictDataVo> selectDictDataByType(String dictType) {
+ List<SysDictDataVo> dictDatas = dictDataMapper.selectDictDataByType(dictType);
+ return CollUtil.isNotEmpty(dictDatas) ? dictDatas : null;
+ }
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷ID鏌ヨ淇℃伅
+ *
+ * @param dictId 瀛楀吀绫诲瀷ID
+ * @return 瀛楀吀绫诲瀷
+ */
+ @Override
+ public SysDictTypeVo selectDictTypeById(Long dictId) {
+ return baseMapper.selectVoById(dictId);
+ }
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鏌ヨ淇℃伅
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return 瀛楀吀绫诲瀷
+ */
+ @Cacheable(cacheNames = CacheNames.SYS_DICT_TYPE, key = "#dictType")
+ @Override
+ public SysDictTypeVo selectDictTypeByType(String dictType) {
+ return baseMapper.selectVoOne(new LambdaQueryWrapper<SysDictType>().eq(SysDictType::getDictType, dictType));
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎瀛楀吀绫诲瀷淇℃伅
+ *
+ * @param dictIds 闇�瑕佸垹闄ょ殑瀛楀吀ID
+ */
+ @Override
+ public void deleteDictTypeByIds(List<Long> dictIds) {
+ List<SysDictType> list = baseMapper.selectByIds(dictIds);
+ list.forEach(x -> {
+ boolean assigned = dictDataMapper.exists(new LambdaQueryWrapper<SysDictData>()
+ .eq(SysDictData::getDictType, x.getDictType()));
+ if (assigned) {
+ throw new ServiceException("{}宸插垎閰�,涓嶈兘鍒犻櫎", x.getDictName());
+ }
+ });
+ baseMapper.deleteByIds(dictIds);
+ list.forEach(x -> {
+ CacheUtils.evict(CacheNames.SYS_DICT, x.getDictType());
+ CacheUtils.evict(CacheNames.SYS_DICT_TYPE, x.getDictType());
+ });
+ }
+
+ /**
+ * 閲嶇疆瀛楀吀缂撳瓨鏁版嵁
+ */
+ @Override
+ public void resetDictCache() {
+ CacheUtils.clear(CacheNames.SYS_DICT);
+ CacheUtils.clear(CacheNames.SYS_DICT_TYPE);
+ }
+
+ /**
+ * 鏂板淇濆瓨瀛楀吀绫诲瀷淇℃伅
+ *
+ * @param bo 瀛楀吀绫诲瀷淇℃伅
+ * @return 缁撴灉
+ */
+ @CachePut(cacheNames = CacheNames.SYS_DICT, key = "#bo.dictType")
+ @Override
+ public List<SysDictDataVo> insertDictType(SysDictTypeBo bo) {
+ SysDictType dict = MapstructUtils.convert(bo, SysDictType.class);
+ int row = baseMapper.insert(dict);
+ if (row > 0) {
+ // 鏂板 type 涓嬫棤 data 鏁版嵁 杩斿洖绌洪槻姝㈢紦瀛樼┛閫�
+ return new ArrayList<>();
+ }
+ throw new ServiceException("鎿嶄綔澶辫触");
+ }
+
+ /**
+ * 淇敼淇濆瓨瀛楀吀绫诲瀷淇℃伅
+ *
+ * @param bo 瀛楀吀绫诲瀷淇℃伅
+ * @return 缁撴灉
+ */
+ @CachePut(cacheNames = CacheNames.SYS_DICT, key = "#bo.dictType")
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public List<SysDictDataVo> updateDictType(SysDictTypeBo bo) {
+ SysDictType dict = MapstructUtils.convert(bo, SysDictType.class);
+ SysDictType oldDict = baseMapper.selectById(dict.getDictId());
+ dictDataMapper.update(null, new LambdaUpdateWrapper<SysDictData>()
+ .set(SysDictData::getDictType, dict.getDictType())
+ .eq(SysDictData::getDictType, oldDict.getDictType()));
+ int row = baseMapper.updateById(dict);
+ if (row > 0) {
+ CacheUtils.evict(CacheNames.SYS_DICT, oldDict.getDictType());
+ CacheUtils.evict(CacheNames.SYS_DICT_TYPE, oldDict.getDictType());
+ return dictDataMapper.selectDictDataByType(dict.getDictType());
+ }
+ throw new ServiceException("鎿嶄綔澶辫触");
+ }
+
+ /**
+ * 鏍¢獙瀛楀吀绫诲瀷绉版槸鍚﹀敮涓�
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkDictTypeUnique(SysDictTypeBo dictType) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysDictType>()
+ .eq(SysDictType::getDictType, dictType.getDictType())
+ .ne(ObjectUtil.isNotNull(dictType.getDictId()), SysDictType::getDictId, dictType.getDictId()));
+ return !exist;
+ }
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏稿�艰幏鍙栧瓧鍏告爣绛�
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @param dictValue 瀛楀吀鍊�
+ * @param separator 鍒嗛殧绗�
+ * @return 瀛楀吀鏍囩
+ */
+ @Override
+ public String getDictLabel(String dictType, String dictValue, String separator) {
+ List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
+ Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel);
+ if (StringUtils.containsAny(dictValue, separator)) {
+ return Arrays.stream(dictValue.split(separator))
+ .map(v -> map.getOrDefault(v, StringUtils.EMPTY))
+ .collect(Collectors.joining(separator));
+ } else {
+ return map.getOrDefault(dictValue, StringUtils.EMPTY);
+ }
+ }
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏告爣绛捐幏鍙栧瓧鍏稿��
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @param dictLabel 瀛楀吀鏍囩
+ * @param separator 鍒嗛殧绗�
+ * @return 瀛楀吀鍊�
+ */
+ @Override
+ public String getDictValue(String dictType, String dictLabel, String separator) {
+ List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
+ Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictLabel, SysDictDataVo::getDictValue);
+ if (StringUtils.containsAny(dictLabel, separator)) {
+ return Arrays.stream(dictLabel.split(separator))
+ .map(l -> map.getOrDefault(l, StringUtils.EMPTY))
+ .collect(Collectors.joining(separator));
+ } else {
+ return map.getOrDefault(dictLabel, StringUtils.EMPTY);
+ }
+ }
+
+ /**
+ * 鑾峰彇瀛楀吀涓嬫墍鏈夌殑瀛楀吀鍊间笌鏍囩
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return dictValue涓簁ey锛宒ictLabel涓哄�肩粍鎴愮殑Map
+ */
+ @Override
+ public Map<String, String> getAllDictByDictType(String dictType) {
+ List<SysDictDataVo> list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
+ // 淇濊瘉椤哄簭
+ LinkedHashMap<String, String> map = new LinkedHashMap<>();
+ for (SysDictDataVo vo : list) {
+ map.put(vo.getDictValue(), vo.getDictLabel());
+ }
+ return map;
+ }
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鏌ヨ璇︾粏淇℃伅
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return 瀛楀吀绫诲瀷璇︾粏淇℃伅
+ */
+ @Override
+ public DictTypeDTO getDictType(String dictType) {
+ SysDictTypeVo vo = SpringUtils.getAopProxy(this).selectDictTypeByType(dictType);
+ return BeanUtil.toBean(vo, DictTypeDTO.class);
+ }
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鏌ヨ瀛楀吀鏁版嵁鍒楄〃
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return 瀛楀吀鏁版嵁鍒楄〃
+ */
+ @Override
+ public List<DictDataDTO> getDictData(String dictType) {
+ List<SysDictDataVo> list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
+ return BeanUtil.copyToList(list, DictDataDTO.class);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysLogininforServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysLogininforServiceImpl.java
new file mode 100755
index 0000000..c388d99
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysLogininforServiceImpl.java
@@ -0,0 +1,182 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.useragent.UserAgent;
+import cn.hutool.http.useragent.UserAgentUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.ip.AddressUtils;
+import org.dromara.common.log.event.LogininforEvent;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.SysLogininfor;
+import org.dromara.system.domain.bo.SysLogininforBo;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.system.domain.vo.SysLogininforVo;
+import org.dromara.system.mapper.SysLogininforMapper;
+import org.dromara.system.service.ISysClientService;
+import org.dromara.system.service.ISysLogininforService;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 绯荤粺璁块棶鏃ュ織鎯呭喌淇℃伅 鏈嶅姟灞傚鐞�
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Slf4j
+@Service
+public class SysLogininforServiceImpl implements ISysLogininforService {
+
+ private final SysLogininforMapper baseMapper;
+
+ private final ISysClientService clientService;
+
+ /**
+ * 璁板綍鐧诲綍淇℃伅
+ *
+ * @param logininforEvent 鐧诲綍浜嬩欢
+ */
+ @Async
+ @EventListener
+ public void recordLogininfor(LogininforEvent logininforEvent) {
+ HttpServletRequest request = logininforEvent.getRequest();
+ final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
+ final String ip = ServletUtils.getClientIP(request);
+ // 瀹㈡埛绔俊鎭�
+ String clientId = request.getHeader(LoginHelper.CLIENT_KEY);
+ SysClientVo client = null;
+ if (StringUtils.isNotBlank(clientId)) {
+ client = clientService.queryByClientId(clientId);
+ }
+
+ String address = AddressUtils.getRealAddressByIP(ip);
+ StringBuilder s = new StringBuilder();
+ s.append(getBlock(ip));
+ s.append(address);
+ s.append(getBlock(logininforEvent.getUsername()));
+ s.append(getBlock(logininforEvent.getStatus()));
+ s.append(getBlock(logininforEvent.getMessage()));
+ // 鎵撳嵃淇℃伅鍒版棩蹇�
+ log.info(s.toString(), logininforEvent.getArgs());
+ // 鑾峰彇瀹㈡埛绔搷浣滅郴缁�
+ String os = userAgent.getOs().getName();
+ // 鑾峰彇瀹㈡埛绔祻瑙堝櫒
+ String browser = userAgent.getBrowser().getName();
+ // 灏佽瀵硅薄
+ SysLogininforBo logininfor = new SysLogininforBo();
+ logininfor.setTenantId(logininforEvent.getTenantId());
+ logininfor.setUserName(logininforEvent.getUsername());
+ if (ObjectUtil.isNotNull(client)) {
+ logininfor.setClientKey(client.getClientKey());
+ logininfor.setDeviceType(client.getDeviceType());
+ }
+ logininfor.setIpaddr(ip);
+ logininfor.setLoginLocation(address);
+ logininfor.setBrowser(browser);
+ logininfor.setOs(os);
+ logininfor.setMsg(logininforEvent.getMessage());
+ // 鏃ュ織鐘舵��
+ if (StringUtils.equalsAny(logininforEvent.getStatus(), Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
+ logininfor.setStatus(Constants.SUCCESS);
+ } else if (Constants.LOGIN_FAIL.equals(logininforEvent.getStatus())) {
+ logininfor.setStatus(Constants.FAIL);
+ }
+ // 鎻掑叆鏁版嵁
+ insertLogininfor(logininfor);
+ }
+
+ private String getBlock(Object msg) {
+ if (msg == null) {
+ msg = "";
+ }
+ return "[" + msg.toString() + "]";
+ }
+
+ /**
+ * 鍒嗛〉鏌ヨ鐧诲綍鏃ュ織鍒楄〃
+ *
+ * @param logininfor 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 鐧诲綍鏃ュ織鍒嗛〉鍒楄〃
+ */
+ @Override
+ public TableDataInfo<SysLogininforVo> selectPageLogininforList(SysLogininforBo logininfor, PageQuery pageQuery) {
+ Map<String, Object> params = logininfor.getParams();
+ LambdaQueryWrapper<SysLogininfor> lqw = new LambdaQueryWrapper<SysLogininfor>()
+ .like(StringUtils.isNotBlank(logininfor.getIpaddr()), SysLogininfor::getIpaddr, logininfor.getIpaddr())
+ .eq(StringUtils.isNotBlank(logininfor.getStatus()), SysLogininfor::getStatus, logininfor.getStatus())
+ .like(StringUtils.isNotBlank(logininfor.getUserName()), SysLogininfor::getUserName, logininfor.getUserName())
+ .between(params.get("beginTime") != null && params.get("endTime") != null,
+ SysLogininfor::getLoginTime, params.get("beginTime"), params.get("endTime"));
+ if (StringUtils.isBlank(pageQuery.getOrderByColumn())) {
+ lqw.orderByDesc(SysLogininfor::getInfoId);
+ }
+ Page<SysLogininforVo> page = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏂板绯荤粺鐧诲綍鏃ュ織
+ *
+ * @param bo 璁块棶鏃ュ織瀵硅薄
+ */
+ @Override
+ public void insertLogininfor(SysLogininforBo bo) {
+ SysLogininfor logininfor = MapstructUtils.convert(bo, SysLogininfor.class);
+ logininfor.setLoginTime(new Date());
+ baseMapper.insert(logininfor);
+ }
+
+ /**
+ * 鏌ヨ绯荤粺鐧诲綍鏃ュ織闆嗗悎
+ *
+ * @param logininfor 璁块棶鏃ュ織瀵硅薄
+ * @return 鐧诲綍璁板綍闆嗗悎
+ */
+ @Override
+ public List<SysLogininforVo> selectLogininforList(SysLogininforBo logininfor) {
+ Map<String, Object> params = logininfor.getParams();
+ return baseMapper.selectVoList(new LambdaQueryWrapper<SysLogininfor>()
+ .like(StringUtils.isNotBlank(logininfor.getIpaddr()), SysLogininfor::getIpaddr, logininfor.getIpaddr())
+ .eq(StringUtils.isNotBlank(logininfor.getStatus()), SysLogininfor::getStatus, logininfor.getStatus())
+ .like(StringUtils.isNotBlank(logininfor.getUserName()), SysLogininfor::getUserName, logininfor.getUserName())
+ .between(params.get("beginTime") != null && params.get("endTime") != null,
+ SysLogininfor::getLoginTime, params.get("beginTime"), params.get("endTime"))
+ .orderByDesc(SysLogininfor::getInfoId));
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎绯荤粺鐧诲綍鏃ュ織
+ *
+ * @param infoIds 闇�瑕佸垹闄ょ殑鐧诲綍鏃ュ織ID
+ * @return 缁撴灉
+ */
+ @Override
+ public int deleteLogininforByIds(Long[] infoIds) {
+ return baseMapper.deleteByIds(Arrays.asList(infoIds));
+ }
+
+ /**
+ * 娓呯┖绯荤粺鐧诲綍鏃ュ織
+ */
+ @Override
+ public void cleanLogininfor() {
+ baseMapper.delete(new LambdaQueryWrapper<>());
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java
new file mode 100755
index 0000000..0c69b1f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java
@@ -0,0 +1,440 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.TreeBuildUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.SysMenu;
+import org.dromara.system.domain.SysRole;
+import org.dromara.system.domain.SysRoleMenu;
+import org.dromara.system.domain.SysTenantPackage;
+import org.dromara.system.domain.bo.SysMenuBo;
+import org.dromara.system.domain.vo.MetaVo;
+import org.dromara.system.domain.vo.RouterVo;
+import org.dromara.system.domain.vo.SysMenuVo;
+import org.dromara.system.mapper.SysMenuMapper;
+import org.dromara.system.mapper.SysRoleMapper;
+import org.dromara.system.mapper.SysRoleMenuMapper;
+import org.dromara.system.mapper.SysTenantPackageMapper;
+import org.dromara.system.service.ISysMenuService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 鑿滃崟 涓氬姟灞傚鐞�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class SysMenuServiceImpl implements ISysMenuService {
+
+ private final SysMenuMapper baseMapper;
+ private final SysRoleMapper roleMapper;
+ private final SysRoleMenuMapper roleMenuMapper;
+ private final SysTenantPackageMapper tenantPackageMapper;
+
+ /**
+ * 鏍规嵁鐢ㄦ埛鏌ヨ绯荤粺鑿滃崟鍒楄〃
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鑿滃崟鍒楄〃
+ */
+ @Override
+ public List<SysMenuVo> selectMenuList(Long userId) {
+ return selectMenuList(new SysMenuBo(), userId);
+ }
+
+ /**
+ * 鏌ヨ绯荤粺鑿滃崟鍒楄〃
+ *
+ * @param menu 鑿滃崟淇℃伅
+ * @return 鑿滃崟鍒楄〃
+ */
+ @Override
+ public List<SysMenuVo> selectMenuList(SysMenuBo menu, Long userId) {
+ List<SysMenuVo> menuList;
+ LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
+ // 绠$悊鍛樻樉绀烘墍鏈夎彍鍗曚俊鎭� 涓嶆槸绠$悊鍛� 鎸夌敤鎴穒d杩囨护鑿滃崟
+ if (!LoginHelper.isSuperAdmin(userId)) {
+ // 閫氳繃鐢ㄦ埛id鑾峰彇瑙掕壊id 閫氳繃瑙掕壊id鑾峰彇鑿滃崟id 鐒跺悗in鑿滃崟
+ wrapper.inSql(SysMenu::getMenuId, baseMapper.buildMenuByUserSql(userId));
+ }
+ menuList = baseMapper.selectVoList(
+ wrapper.like(StringUtils.isNotBlank(menu.getMenuName()), SysMenu::getMenuName, menu.getMenuName())
+ .eq(StringUtils.isNotBlank(menu.getVisible()), SysMenu::getVisible, menu.getVisible())
+ .eq(StringUtils.isNotBlank(menu.getStatus()), SysMenu::getStatus, menu.getStatus())
+ .eq(StringUtils.isNotBlank(menu.getMenuType()), SysMenu::getMenuType, menu.getMenuType())
+ .eq(ObjectUtil.isNotNull(menu.getParentId()), SysMenu::getParentId, menu.getParentId())
+ .orderByAsc(SysMenu::getParentId)
+ .orderByAsc(SysMenu::getOrderNum));
+ return menuList;
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ鏉冮檺
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鏉冮檺鍒楄〃
+ */
+ @Override
+ public Set<String> selectMenuPermsByUserId(Long userId) {
+ return baseMapper.selectMenuPermsByUserId(userId);
+ }
+
+ /**
+ * 鏍规嵁瑙掕壊ID鏌ヨ鏉冮檺
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 鏉冮檺鍒楄〃
+ */
+ @Override
+ public Set<String> selectMenuPermsByRoleId(Long roleId) {
+ return baseMapper.selectMenuPermsByRoleId(roleId);
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ鑿滃崟
+ *
+ * @param userId 鐢ㄦ埛鍚嶇О
+ * @return 鑿滃崟鍒楄〃
+ */
+ @Override
+ public List<SysMenu> selectMenuTreeByUserId(Long userId) {
+ List<SysMenu> menus;
+ if (LoginHelper.isSuperAdmin(userId)) {
+ menus = baseMapper.selectMenuTreeAll();
+ } else {
+ LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
+ menus = baseMapper.selectList(
+ wrapper.in(SysMenu::getMenuType, SystemConstants.TYPE_DIR, SystemConstants.TYPE_MENU)
+ .eq(SysMenu::getStatus, SystemConstants.NORMAL)
+ .inSql(SysMenu::getMenuId, baseMapper.buildMenuByUserSql(userId))
+ .orderByAsc(SysMenu::getParentId)
+ .orderByAsc(SysMenu::getOrderNum));
+ }
+ return getChildPerms(menus, Constants.TOP_PARENT_ID);
+ }
+
+ /**
+ * 鏍规嵁瑙掕壊ID鏌ヨ鑿滃崟鏍戜俊鎭�
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 閫変腑鑿滃崟鍒楄〃
+ */
+ @Override
+ public List<Long> selectMenuListByRoleId(Long roleId) {
+ SysRole role = roleMapper.selectById(roleId);
+ return baseMapper.selectMenuListByRoleId(roleId, role.getMenuCheckStrictly());
+ }
+
+ /**
+ * 鏍规嵁绉熸埛濂楅ID鏌ヨ鑿滃崟鏍戜俊鎭�
+ *
+ * @param packageId 绉熸埛濂楅ID
+ * @return 閫変腑鑿滃崟鍒楄〃
+ */
+ @Override
+ public List<Long> selectMenuListByPackageId(Long packageId) {
+ SysTenantPackage tenantPackage = tenantPackageMapper.selectById(packageId);
+ List<Long> menuIds = StringUtils.splitTo(tenantPackage.getMenuIds(), Convert::toLong);
+ if (CollUtil.isEmpty(menuIds)) {
+ return List.of();
+ }
+ List<Long> parentIds = null;
+ if (tenantPackage.getMenuCheckStrictly()) {
+ parentIds = baseMapper.selectObjs(new LambdaQueryWrapper<SysMenu>()
+ .select(SysMenu::getParentId)
+ .in(SysMenu::getMenuId, menuIds), x -> {
+ return Convert.toLong(x);
+ });
+ }
+ return baseMapper.selectObjs(new LambdaQueryWrapper<SysMenu>()
+ .select(SysMenu::getMenuId)
+ .in(SysMenu::getMenuId, menuIds)
+ .notIn(CollUtil.isNotEmpty(parentIds), SysMenu::getMenuId, parentIds), x -> {
+ return Convert.toLong(x);
+ });
+ }
+
+ /**
+ * 鏋勫缓鍓嶇璺敱鎵�闇�瑕佺殑鑿滃崟
+ * 璺敱name鍛藉悕瑙勫垯 path棣栧瓧姣嶈浆澶у啓 + id
+ *
+ * @param menus 鑿滃崟鍒楄〃
+ * @return 璺敱鍒楄〃
+ */
+ @Override
+ public List<RouterVo> buildMenus(List<SysMenu> menus) {
+ List<RouterVo> routers = new LinkedList<>();
+ for (SysMenu menu : menus) {
+ String name = menu.getRouteName() + menu.getMenuId();
+ RouterVo router = new RouterVo();
+ router.setHidden("1".equals(menu.getVisible()));
+ router.setName(name);
+ router.setPath(menu.getRouterPath());
+ router.setComponent(menu.getComponentInfo());
+ router.setQuery(menu.getQueryParam());
+ router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath(), menu.getRemark()));
+ List<SysMenu> cMenus = menu.getChildren();
+ if (CollUtil.isNotEmpty(cMenus) && SystemConstants.TYPE_DIR.equals(menu.getMenuType())) {
+ router.setAlwaysShow(true);
+ router.setRedirect("noRedirect");
+ router.setChildren(buildMenus(cMenus));
+ } else if (menu.isMenuFrame()) {
+ String frameName = StringUtils.capitalize(menu.getPath()) + menu.getMenuId();
+ router.setMeta(null);
+ List<RouterVo> childrenList = new ArrayList<>();
+ RouterVo children = new RouterVo();
+ children.setPath(menu.getPath());
+ children.setComponent(menu.getComponent());
+ children.setName(frameName);
+ children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath(), menu.getRemark()));
+ children.setQuery(menu.getQueryParam());
+ childrenList.add(children);
+ router.setChildren(childrenList);
+ } else if (menu.getParentId().equals(Constants.TOP_PARENT_ID) && menu.isInnerLink()) {
+ router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
+ router.setPath("/");
+ List<RouterVo> childrenList = new ArrayList<>();
+ RouterVo children = new RouterVo();
+ String routerPath = SysMenu.innerLinkReplaceEach(menu.getPath());
+ String innerLinkName = StringUtils.capitalize(routerPath) + menu.getMenuId();
+ children.setPath(routerPath);
+ children.setComponent(SystemConstants.INNER_LINK);
+ children.setName(innerLinkName);
+ children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
+ childrenList.add(children);
+ router.setChildren(childrenList);
+ }
+ routers.add(router);
+ }
+ return routers;
+ }
+
+ /**
+ * 鏋勫缓鍓嶇鎵�闇�瑕佷笅鎷夋爲缁撴瀯
+ *
+ * @param menus 鑿滃崟鍒楄〃
+ * @return 涓嬫媺鏍戠粨鏋勫垪琛�
+ */
+ @Override
+ public List<Tree<Long>> buildMenuTreeSelect(List<SysMenuVo> menus) {
+ if (CollUtil.isEmpty(menus)) {
+ return CollUtil.newArrayList();
+ }
+ return TreeBuildUtils.build(menus, (menu, tree) -> {
+ Tree<Long> menuTree = tree.setId(menu.getMenuId())
+ .setParentId(menu.getParentId())
+ .setName(menu.getMenuName())
+ .setWeight(menu.getOrderNum());
+ menuTree.put("menuType", menu.getMenuType());
+ menuTree.put("icon", menu.getIcon());
+ menuTree.put("visible", menu.getVisible());
+ menuTree.put("status", menu.getStatus());
+ });
+ }
+
+ /**
+ * 鏍规嵁鑿滃崟ID鏌ヨ淇℃伅
+ *
+ * @param menuId 鑿滃崟ID
+ * @return 鑿滃崟淇℃伅
+ */
+ @Override
+ public SysMenuVo selectMenuById(Long menuId) {
+ return baseMapper.selectVoById(menuId);
+ }
+
+ /**
+ * 鏄惁瀛樺湪鑿滃崟瀛愯妭鐐�
+ *
+ * @param menuId 鑿滃崟ID
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean hasChildByMenuId(Long menuId) {
+ return baseMapper.exists(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getParentId, menuId));
+ }
+
+ /**
+ * 鏄惁瀛樺湪鑿滃崟瀛愯妭鐐�
+ *
+ * @param menuIds 鑿滃崟ID涓�
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean hasChildByMenuId(List<Long> menuIds) {
+ return baseMapper.exists(new LambdaQueryWrapper<SysMenu>().in(SysMenu::getParentId, menuIds).notIn(SysMenu::getMenuId, menuIds));
+ }
+
+ /**
+ * 鏌ヨ鑿滃崟浣跨敤鏁伴噺
+ *
+ * @param menuId 鑿滃崟ID
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkMenuExistRole(Long menuId) {
+ return roleMenuMapper.exists(new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getMenuId, menuId));
+ }
+
+ /**
+ * 鏂板淇濆瓨鑿滃崟淇℃伅
+ *
+ * @param bo 鑿滃崟淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public int insertMenu(SysMenuBo bo) {
+ SysMenu menu = MapstructUtils.convert(bo, SysMenu.class);
+ return baseMapper.insert(menu);
+ }
+
+ /**
+ * 淇敼淇濆瓨鑿滃崟淇℃伅
+ *
+ * @param bo 鑿滃崟淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public int updateMenu(SysMenuBo bo) {
+ SysMenu menu = MapstructUtils.convert(bo, SysMenu.class);
+ return baseMapper.updateById(menu);
+ }
+
+ /**
+ * 鍒犻櫎鑿滃崟绠$悊淇℃伅
+ *
+ * @param menuId 鑿滃崟ID
+ * @return 缁撴灉
+ */
+ @Override
+ public int deleteMenuById(Long menuId) {
+ return baseMapper.deleteById(menuId);
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎鑿滃崟绠$悊淇℃伅
+ *
+ * @param menuIds 鑿滃崟ID涓�
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteMenuById(List<Long> menuIds) {
+ baseMapper.deleteByIds(menuIds);
+ roleMenuMapper.deleteByMenuIds(menuIds);
+ }
+
+ /**
+ * 鏍¢獙鑿滃崟鍚嶇О鏄惁鍞竴
+ *
+ * @param menu 鑿滃崟淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkMenuNameUnique(SysMenuBo menu) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysMenu>()
+ .eq(SysMenu::getMenuName, menu.getMenuName())
+ .eq(SysMenu::getParentId, menu.getParentId())
+ .ne(ObjectUtil.isNotNull(menu.getMenuId()), SysMenu::getMenuId, menu.getMenuId()));
+ return !exist;
+ }
+
+ /**
+ * 鏍¢獙璺敱鍚嶇О鏄惁鍞竴
+ *
+ * @param menuBo 鑿滃崟淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkRouteConfigUnique(SysMenuBo menuBo) {
+ SysMenu menu = MapstructUtils.convert(menuBo, SysMenu.class);
+ if (SystemConstants.TYPE_BUTTON.equals(menu.getMenuType())) {
+ return true;
+ }
+ long menuId = ObjectUtil.isNull(menu.getMenuId()) ? -1L : menu.getMenuId();
+ Long parentId = menu.getParentId();
+ String path = menu.getPath();
+ String routeName = StringUtils.isEmpty(menu.getRouteName()) ? path : menu.getRouteName();
+ List<SysMenu> sysMenuList = baseMapper.selectList(
+ new LambdaQueryWrapper<SysMenu>()
+ .in(SysMenu::getMenuType, SystemConstants.TYPE_DIR, SystemConstants.TYPE_MENU)
+ .and(w ->
+ w.eq(SysMenu::getPath, path).or().eq(SysMenu::getPath, routeName)
+ ));
+ for (SysMenu sysMenu : sysMenuList) {
+ if (!sysMenu.getMenuId().equals(menuId)) {
+ Long dbParentId = sysMenu.getParentId();
+ String dbPath = sysMenu.getPath();
+ String dbRouteName = StringUtils.isEmpty(sysMenu.getRouteName()) ? dbPath : sysMenu.getRouteName();
+ if (StringUtils.equalsAnyIgnoreCase(path, dbPath) && parentId.equals(dbParentId)) {
+ log.warn("[鍚岀骇璺敱鍐茬獊] 鍚岀骇涓嬪凡瀛樺湪鐩稿悓璺敱璺緞 '{}'锛屽啿绐佽彍鍗曪細{}", dbPath, sysMenu.getMenuName());
+ return false;
+ } else if (StringUtils.equalsAnyIgnoreCase(path, dbPath)
+ && Constants.TOP_PARENT_ID.equals(parentId)
+ && Constants.TOP_PARENT_ID.equals(dbParentId)) {
+ log.warn("[鏍圭洰褰曡矾鐢卞啿绐乚 鏍圭洰褰曚笅璺敱 '{}' 蹇呴』鍞竴锛屽凡琚彍鍗� '{}' 鍗犵敤", path, sysMenu.getMenuName());
+ return false;
+ } else if (StringUtils.equalsAnyIgnoreCase(routeName, dbRouteName)
+ && sysMenu.getMenuType().equals(menu.getMenuType())) {
+ log.warn("[璺敱鍚嶇О鍐茬獊] 璺敱鍚嶇О '{}' 闇�鍏ㄥ眬鍞竴锛屽凡琚彍鍗� '{}' 浣跨敤", routeName, sysMenu.getMenuName());
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 鏍规嵁鐖惰妭鐐圭殑ID鑾峰彇鎵�鏈夊瓙鑺傜偣
+ *
+ * @param list 鍒嗙被琛�
+ * @param parentId 浼犲叆鐨勭埗鑺傜偣ID
+ * @return String
+ */
+ private List<SysMenu> getChildPerms(List<SysMenu> list, Long parentId) {
+ List<SysMenu> returnList = new ArrayList<>();
+ for (SysMenu t : list) {
+ // 涓�銆佹牴鎹紶鍏ョ殑鏌愪釜鐖惰妭鐐笽D,閬嶅巻璇ョ埗鑺傜偣鐨勬墍鏈夊瓙鑺傜偣
+ if (t.getParentId().equals(parentId)) {
+ recursionFn(list, t);
+ returnList.add(t);
+ }
+ }
+ return returnList;
+ }
+
+ /**
+ * 閫掑綊鍒楄〃
+ */
+ private void recursionFn(List<SysMenu> list, SysMenu t) {
+ // 寰楀埌瀛愯妭鐐瑰垪琛�
+ List<SysMenu> childList = StreamUtils.filter(list, n -> n.getParentId().equals(t.getMenuId()));
+ t.setChildren(childList);
+ for (SysMenu tChild : childList) {
+ // 鍒ゆ柇鏄惁鏈夊瓙鑺傜偣
+ if (list.stream().anyMatch(n -> n.getParentId().equals(tChild.getMenuId()))) {
+ recursionFn(list, tChild);
+ }
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java
new file mode 100755
index 0000000..aa4135f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java
@@ -0,0 +1,131 @@
+package org.dromara.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.ObjectUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.SysNotice;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.bo.SysNoticeBo;
+import org.dromara.system.domain.vo.SysNoticeVo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.mapper.SysNoticeMapper;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.system.service.ISysNoticeService;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 鍏憡 鏈嶅姟灞傚疄鐜�
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Service
+public class SysNoticeServiceImpl implements ISysNoticeService {
+
+ private final SysNoticeMapper baseMapper;
+ private final SysUserMapper userMapper;
+
+ /**
+ * 鍒嗛〉鏌ヨ閫氱煡鍏憡鍒楄〃
+ *
+ * @param notice 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 閫氱煡鍏憡鍒嗛〉鍒楄〃
+ */
+ @Override
+ public TableDataInfo<SysNoticeVo> selectPageNoticeList(SysNoticeBo notice, PageQuery pageQuery) {
+ LambdaQueryWrapper<SysNotice> lqw = buildQueryWrapper(notice);
+ Page<SysNoticeVo> page = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏌ヨ鍏憡淇℃伅
+ *
+ * @param noticeId 鍏憡ID
+ * @return 鍏憡淇℃伅
+ */
+ @Override
+ public SysNoticeVo selectNoticeById(Long noticeId) {
+ return baseMapper.selectVoById(noticeId);
+ }
+
+ /**
+ * 鏌ヨ鍏憡鍒楄〃
+ *
+ * @param notice 鍏憡淇℃伅
+ * @return 鍏憡闆嗗悎
+ */
+ @Override
+ public List<SysNoticeVo> selectNoticeList(SysNoticeBo notice) {
+ LambdaQueryWrapper<SysNotice> lqw = buildQueryWrapper(notice);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper<SysNotice> buildQueryWrapper(SysNoticeBo bo) {
+ LambdaQueryWrapper<SysNotice> lqw = Wrappers.lambdaQuery();
+ lqw.like(StringUtils.isNotBlank(bo.getNoticeTitle()), SysNotice::getNoticeTitle, bo.getNoticeTitle());
+ lqw.eq(StringUtils.isNotBlank(bo.getNoticeType()), SysNotice::getNoticeType, bo.getNoticeType());
+ if (StringUtils.isNotBlank(bo.getCreateByName())) {
+ SysUserVo sysUser = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, bo.getCreateByName()));
+ lqw.eq(SysNotice::getCreateBy, ObjectUtils.notNullGetter(sysUser, SysUserVo::getUserId));
+ }
+ lqw.orderByAsc(SysNotice::getNoticeId);
+ return lqw;
+ }
+
+ /**
+ * 鏂板鍏憡
+ *
+ * @param bo 鍏憡淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public int insertNotice(SysNoticeBo bo) {
+ SysNotice notice = MapstructUtils.convert(bo, SysNotice.class);
+ return baseMapper.insert(notice);
+ }
+
+ /**
+ * 淇敼鍏憡
+ *
+ * @param bo 鍏憡淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public int updateNotice(SysNoticeBo bo) {
+ SysNotice notice = MapstructUtils.convert(bo, SysNotice.class);
+ return baseMapper.updateById(notice);
+ }
+
+ /**
+ * 鍒犻櫎鍏憡瀵硅薄
+ *
+ * @param noticeId 鍏憡ID
+ * @return 缁撴灉
+ */
+ @Override
+ public int deleteNoticeById(Long noticeId) {
+ return baseMapper.deleteById(noticeId);
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎鍏憡淇℃伅
+ *
+ * @param noticeIds 闇�瑕佸垹闄ょ殑鍏憡ID
+ * @return 缁撴灉
+ */
+ @Override
+ public int deleteNoticeByIds(Long[] noticeIds) {
+ return baseMapper.deleteByIds(Arrays.asList(noticeIds));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java
new file mode 100755
index 0000000..9399c90
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java
@@ -0,0 +1,141 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.util.ArrayUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.ip.AddressUtils;
+import org.dromara.common.log.event.OperLogEvent;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.SysOperLog;
+import org.dromara.system.domain.bo.SysOperLogBo;
+import org.dromara.system.domain.vo.SysOperLogVo;
+import org.dromara.system.mapper.SysOperLogMapper;
+import org.dromara.system.service.ISysOperLogService;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鎿嶄綔鏃ュ織 鏈嶅姟灞傚鐞�
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Service
+public class SysOperLogServiceImpl implements ISysOperLogService {
+
+ private final SysOperLogMapper baseMapper;
+
+ /**
+ * 鎿嶄綔鏃ュ織璁板綍
+ *
+ * @param operLogEvent 鎿嶄綔鏃ュ織浜嬩欢
+ */
+ @Async
+ @EventListener
+ public void recordOper(OperLogEvent operLogEvent) {
+ SysOperLogBo operLog = MapstructUtils.convert(operLogEvent, SysOperLogBo.class);
+ // 杩滅▼鏌ヨ鎿嶄綔鍦扮偣
+ operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
+ insertOperlog(operLog);
+ }
+
+ /**
+ * 鍒嗛〉鏌ヨ鎿嶄綔鏃ュ織鍒楄〃
+ *
+ * @param operLog 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 鎿嶄綔鏃ュ織鍒嗛〉鍒楄〃
+ */
+ @Override
+ public TableDataInfo<SysOperLogVo> selectPageOperLogList(SysOperLogBo operLog, PageQuery pageQuery) {
+ LambdaQueryWrapper<SysOperLog> lqw = buildQueryWrapper(operLog);
+ if (StringUtils.isBlank(pageQuery.getOrderByColumn())) {
+ lqw.orderByDesc(SysOperLog::getOperId);
+ }
+ Page<SysOperLogVo> page = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(page);
+ }
+
+ private LambdaQueryWrapper<SysOperLog> buildQueryWrapper(SysOperLogBo operLog) {
+ Map<String, Object> params = operLog.getParams();
+ return new LambdaQueryWrapper<SysOperLog>()
+ .like(StringUtils.isNotBlank(operLog.getOperIp()), SysOperLog::getOperIp, operLog.getOperIp())
+ .like(StringUtils.isNotBlank(operLog.getTitle()), SysOperLog::getTitle, operLog.getTitle())
+ .eq(operLog.getBusinessType() != null && operLog.getBusinessType() > 0,
+ SysOperLog::getBusinessType, operLog.getBusinessType())
+ .func(f -> {
+ if (ArrayUtil.isNotEmpty(operLog.getBusinessTypes())) {
+ f.in(SysOperLog::getBusinessType, Arrays.asList(operLog.getBusinessTypes()));
+ }
+ })
+ .eq(operLog.getStatus() != null,
+ SysOperLog::getStatus, operLog.getStatus())
+ .like(StringUtils.isNotBlank(operLog.getOperName()), SysOperLog::getOperName, operLog.getOperName())
+ .between(params.get("beginTime") != null && params.get("endTime") != null,
+ SysOperLog::getOperTime, params.get("beginTime"), params.get("endTime"));
+ }
+
+ /**
+ * 鏂板鎿嶄綔鏃ュ織
+ *
+ * @param bo 鎿嶄綔鏃ュ織瀵硅薄
+ */
+ @Override
+ public void insertOperlog(SysOperLogBo bo) {
+ SysOperLog operLog = MapstructUtils.convert(bo, SysOperLog.class);
+ operLog.setOperTime(new Date());
+ baseMapper.insert(operLog);
+ }
+
+ /**
+ * 鏌ヨ绯荤粺鎿嶄綔鏃ュ織闆嗗悎
+ *
+ * @param operLog 鎿嶄綔鏃ュ織瀵硅薄
+ * @return 鎿嶄綔鏃ュ織闆嗗悎
+ */
+ @Override
+ public List<SysOperLogVo> selectOperLogList(SysOperLogBo operLog) {
+ LambdaQueryWrapper<SysOperLog> lqw = buildQueryWrapper(operLog);
+ return baseMapper.selectVoList(lqw.orderByDesc(SysOperLog::getOperId));
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎绯荤粺鎿嶄綔鏃ュ織
+ *
+ * @param operIds 闇�瑕佸垹闄ょ殑鎿嶄綔鏃ュ織ID
+ * @return 缁撴灉
+ */
+ @Override
+ public int deleteOperLogByIds(Long[] operIds) {
+ return baseMapper.deleteByIds(Arrays.asList(operIds));
+ }
+
+ /**
+ * 鏌ヨ鎿嶄綔鏃ュ織璇︾粏
+ *
+ * @param operId 鎿嶄綔ID
+ * @return 鎿嶄綔鏃ュ織瀵硅薄
+ */
+ @Override
+ public SysOperLogVo selectOperLogById(Long operId) {
+ return baseMapper.selectVoById(operId);
+ }
+
+ /**
+ * 娓呯┖鎿嶄綔鏃ュ織
+ */
+ @Override
+ public void cleanOperLog() {
+ baseMapper.delete(new LambdaQueryWrapper<>());
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssConfigServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssConfigServiceImpl.java
new file mode 100755
index 0000000..539c1fc
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssConfigServiceImpl.java
@@ -0,0 +1,177 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.ObjectUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.oss.constant.OssConstant;
+import org.dromara.common.redis.utils.CacheUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.system.domain.SysOssConfig;
+import org.dromara.system.domain.bo.SysOssConfigBo;
+import org.dromara.system.domain.vo.SysOssConfigVo;
+import org.dromara.system.mapper.SysOssConfigMapper;
+import org.dromara.system.service.ISysOssConfigService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 瀵硅薄瀛樺偍閰嶇疆Service涓氬姟灞傚鐞�
+ *
+ * @author Lion Li
+ * @author 瀛よ垷鐑熼洦
+ * @date 2021-08-13
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class SysOssConfigServiceImpl implements ISysOssConfigService {
+
+ private final SysOssConfigMapper baseMapper;
+
+ /**
+ * 椤圭洰鍚姩鏃讹紝鍒濆鍖栧弬鏁板埌缂撳瓨锛屽姞杞介厤缃被
+ */
+ @Override
+ public void init() {
+ List<SysOssConfig> list = baseMapper.selectList();
+ // 鍔犺浇OSS鍒濆鍖栭厤缃�
+ for (SysOssConfig config : list) {
+ String configKey = config.getConfigKey();
+ if ("0".equals(config.getStatus())) {
+ RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, configKey);
+ }
+ CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
+ }
+ }
+
+ @Override
+ public SysOssConfigVo queryById(Long ossConfigId) {
+ return baseMapper.selectVoById(ossConfigId);
+ }
+
+ @Override
+ public TableDataInfo<SysOssConfigVo> queryPageList(SysOssConfigBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper<SysOssConfig> lqw = buildQueryWrapper(bo);
+ Page<SysOssConfigVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+
+ private LambdaQueryWrapper<SysOssConfig> buildQueryWrapper(SysOssConfigBo bo) {
+ LambdaQueryWrapper<SysOssConfig> lqw = Wrappers.lambdaQuery();
+ lqw.eq(StringUtils.isNotBlank(bo.getConfigKey()), SysOssConfig::getConfigKey, bo.getConfigKey());
+ lqw.like(StringUtils.isNotBlank(bo.getBucketName()), SysOssConfig::getBucketName, bo.getBucketName());
+ lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysOssConfig::getStatus, bo.getStatus());
+ lqw.orderByAsc(SysOssConfig::getOssConfigId);
+ return lqw;
+ }
+
+ @Override
+ public Boolean insertByBo(SysOssConfigBo bo) {
+ SysOssConfig config = MapstructUtils.convert(bo, SysOssConfig.class);
+ validEntityBeforeSave(config);
+ boolean flag = baseMapper.insert(config) > 0;
+ if (flag) {
+ // 浠庢暟鎹簱鏌ヨ瀹屾暣鐨勬暟鎹仛缂撳瓨
+ config = baseMapper.selectById(config.getOssConfigId());
+ CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
+ }
+ return flag;
+ }
+
+ @Override
+ public Boolean updateByBo(SysOssConfigBo bo) {
+ SysOssConfig config = MapstructUtils.convert(bo, SysOssConfig.class);
+ validEntityBeforeSave(config);
+ LambdaUpdateWrapper<SysOssConfig> luw = new LambdaUpdateWrapper<>();
+ luw.set(ObjectUtil.isNull(config.getPrefix()), SysOssConfig::getPrefix, "");
+ luw.set(ObjectUtil.isNull(config.getRegion()), SysOssConfig::getRegion, "");
+ luw.set(ObjectUtil.isNull(config.getExt1()), SysOssConfig::getExt1, "");
+ luw.set(ObjectUtil.isNull(config.getRemark()), SysOssConfig::getRemark, "");
+ luw.eq(SysOssConfig::getOssConfigId, config.getOssConfigId());
+ boolean flag = baseMapper.update(config, luw) > 0;
+ if (flag) {
+ // 浠庢暟鎹簱鏌ヨ瀹屾暣鐨勬暟鎹仛缂撳瓨
+ config = baseMapper.selectById(config.getOssConfigId());
+ CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
+ }
+ return flag;
+ }
+
+ /**
+ * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+ */
+ private void validEntityBeforeSave(SysOssConfig entity) {
+ if (StringUtils.isNotEmpty(entity.getConfigKey())
+ && !checkConfigKeyUnique(entity)) {
+ throw new ServiceException("鎿嶄綔閰嶇疆'{}'澶辫触, 閰嶇疆key宸插瓨鍦�!", entity.getConfigKey());
+ }
+ }
+
+ @Override
+ public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+ if (isValid) {
+ if (CollUtil.containsAny(ids, OssConstant.SYSTEM_DATA_IDS)) {
+ throw new ServiceException("绯荤粺鍐呯疆, 涓嶅彲鍒犻櫎!");
+ }
+ }
+ List<SysOssConfig> list = CollUtil.newArrayList();
+ for (Long configId : ids) {
+ SysOssConfig config = baseMapper.selectById(configId);
+ list.add(config);
+ }
+ boolean flag = baseMapper.deleteByIds(ids) > 0;
+ if (flag) {
+ list.forEach(sysOssConfig ->
+ CacheUtils.evict(CacheNames.SYS_OSS_CONFIG, sysOssConfig.getConfigKey()));
+ }
+ return flag;
+ }
+
+ /**
+ * 鍒ゆ柇configKey鏄惁鍞竴
+ */
+ private boolean checkConfigKeyUnique(SysOssConfig sysOssConfig) {
+ long ossConfigId = ObjectUtils.notNull(sysOssConfig.getOssConfigId(), -1L);
+ SysOssConfig info = baseMapper.selectOne(new LambdaQueryWrapper<SysOssConfig>()
+ .select(SysOssConfig::getOssConfigId, SysOssConfig::getConfigKey)
+ .eq(SysOssConfig::getConfigKey, sysOssConfig.getConfigKey()));
+ if (ObjectUtil.isNotNull(info) && info.getOssConfigId() != ossConfigId) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 鍚敤绂佺敤鐘舵��
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int updateOssConfigStatus(SysOssConfigBo bo) {
+ SysOssConfig sysOssConfig = MapstructUtils.convert(bo, SysOssConfig.class);
+ int row = baseMapper.update(null, new LambdaUpdateWrapper<SysOssConfig>()
+ .set(SysOssConfig::getStatus, "1"));
+ row += baseMapper.updateById(sysOssConfig);
+ if (row > 0) {
+ RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, sysOssConfig.getConfigKey());
+ }
+ return row;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java
new file mode 100755
index 0000000..d920ddb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java
@@ -0,0 +1,284 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.domain.dto.OssDTO;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.OssService;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.file.FileUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.oss.core.OssClient;
+import org.dromara.common.oss.entity.UploadResult;
+import org.dromara.common.oss.enums.AccessPolicyType;
+import org.dromara.common.oss.factory.OssFactory;
+import org.dromara.system.domain.SysOss;
+import org.dromara.system.domain.SysOssExt;
+import org.dromara.system.domain.bo.SysOssBo;
+import org.dromara.system.domain.vo.SysOssVo;
+import org.dromara.system.mapper.SysOssMapper;
+import org.dromara.system.service.ISysOssService;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鏂囦欢涓婁紶 鏈嶅姟灞傚疄鐜�
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Service
+public class SysOssServiceImpl implements ISysOssService, OssService {
+
+ private final SysOssMapper baseMapper;
+
+ /**
+ * 鏌ヨOSS瀵硅薄瀛樺偍鍒楄〃
+ *
+ * @param bo OSS瀵硅薄瀛樺偍鍒嗛〉鏌ヨ瀵硅薄
+ * @param pageQuery 鍒嗛〉鏌ヨ瀹炰綋绫�
+ * @return 缁撴灉
+ */
+ @Override
+ public TableDataInfo<SysOssVo> queryPageList(SysOssBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper<SysOss> lqw = buildQueryWrapper(bo);
+ Page<SysOssVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ List<SysOssVo> filterResult = StreamUtils.toList(result.getRecords(), this::matchingUrl);
+ result.setRecords(filterResult);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 鏍规嵁涓�缁� ossIds 鑾峰彇瀵瑰簲鐨� SysOssVo 鍒楄〃
+ *
+ * @param ossIds 涓�缁勬枃浠跺湪鏁版嵁搴撲腑鐨勫敮涓�鏍囪瘑闆嗗悎
+ * @return 鍖呭惈 SysOssVo 瀵硅薄鐨勫垪琛�
+ */
+ @Override
+ public List<SysOssVo> listByIds(Collection<Long> ossIds) {
+ List<SysOssVo> list = new ArrayList<>();
+ SysOssServiceImpl ossService = SpringUtils.getAopProxy(this);
+ for (Long id : ossIds) {
+ SysOssVo vo = ossService.getById(id);
+ if (ObjectUtil.isNotNull(vo)) {
+ try {
+ list.add(this.matchingUrl(vo));
+ } catch (Exception ignored) {
+ // 濡傛灉oss寮傚父鏃犳硶杩炴帴鍒欏皢鏁版嵁鐩存帴杩斿洖
+ list.add(vo);
+ }
+ }
+ }
+ return list;
+ }
+
+ /**
+ * 鏍规嵁涓�缁� ossIds 鑾峰彇瀵瑰簲鏂囦欢鐨� URL 鍒楄〃
+ *
+ * @param ossIds 浠ラ�楀彿鍒嗛殧鐨� ossId 瀛楃涓�
+ * @return 浠ラ�楀彿鍒嗛殧鐨勬枃浠� URL 瀛楃涓�
+ */
+ @Override
+ public String selectUrlByIds(String ossIds) {
+ List<String> list = new ArrayList<>();
+ SysOssServiceImpl ossService = SpringUtils.getAopProxy(this);
+ for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) {
+ SysOssVo vo = ossService.getById(id);
+ if (ObjectUtil.isNotNull(vo)) {
+ try {
+ list.add(this.matchingUrl(vo).getUrl());
+ } catch (Exception ignored) {
+ // 濡傛灉oss寮傚父鏃犳硶杩炴帴鍒欏皢鏁版嵁鐩存帴杩斿洖
+ list.add(vo.getUrl());
+ }
+ }
+ }
+ return StringUtils.joinComma(list);
+ }
+
+ @Override
+ public List<OssDTO> selectByIds(String ossIds) {
+ List<OssDTO> list = new ArrayList<>();
+ for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) {
+ SysOssVo vo = SpringUtils.getAopProxy(this).getById(id);
+ if (ObjectUtil.isNotNull(vo)) {
+ try {
+ vo.setUrl(this.matchingUrl(vo).getUrl());
+ list.add(BeanUtil.toBean(vo, OssDTO.class));
+ } catch (Exception ignored) {
+ // 濡傛灉oss寮傚父鏃犳硶杩炴帴鍒欏皢鏁版嵁鐩存帴杩斿洖
+ list.add(BeanUtil.toBean(vo, OssDTO.class));
+ }
+ }
+ }
+ return list;
+ }
+
+ private LambdaQueryWrapper<SysOss> buildQueryWrapper(SysOssBo bo) {
+ Map<String, Object> params = bo.getParams();
+ LambdaQueryWrapper<SysOss> lqw = Wrappers.lambdaQuery();
+ lqw.like(StringUtils.isNotBlank(bo.getFileName()), SysOss::getFileName, bo.getFileName());
+ lqw.like(StringUtils.isNotBlank(bo.getOriginalName()), SysOss::getOriginalName, bo.getOriginalName());
+ lqw.eq(StringUtils.isNotBlank(bo.getFileSuffix()), SysOss::getFileSuffix, bo.getFileSuffix());
+ lqw.eq(StringUtils.isNotBlank(bo.getUrl()), SysOss::getUrl, bo.getUrl());
+ lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
+ SysOss::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
+ lqw.eq(ObjectUtil.isNotNull(bo.getCreateBy()), SysOss::getCreateBy, bo.getCreateBy());
+ lqw.eq(StringUtils.isNotBlank(bo.getService()), SysOss::getService, bo.getService());
+ lqw.orderByAsc(SysOss::getOssId);
+ return lqw;
+ }
+
+ /**
+ * 鏍规嵁 ossId 浠庣紦瀛樻垨鏁版嵁搴撲腑鑾峰彇 SysOssVo 瀵硅薄
+ *
+ * @param ossId 鏂囦欢鍦ㄦ暟鎹簱涓殑鍞竴鏍囪瘑
+ * @return SysOssVo 瀵硅薄锛屽寘鍚枃浠朵俊鎭�
+ */
+ @Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId")
+ @Override
+ public SysOssVo getById(Long ossId) {
+ return baseMapper.selectVoById(ossId);
+ }
+
+
+ /**
+ * 鏂囦欢涓嬭浇鏂规硶锛屾敮鎸佷竴娆℃�т笅杞藉畬鏁存枃浠�
+ *
+ * @param ossId OSS瀵硅薄ID
+ * @param response HttpServletResponse瀵硅薄锛岀敤浜庤缃搷搴斿ご鍜屽悜瀹㈡埛绔彂閫佹枃浠跺唴瀹�
+ */
+ @Override
+ public void download(Long ossId, HttpServletResponse response) throws IOException {
+ SysOssVo sysOss = SpringUtils.getAopProxy(this).getById(ossId);
+ if (ObjectUtil.isNull(sysOss)) {
+ throw new ServiceException("鏂囦欢鏁版嵁涓嶅瓨鍦�!");
+ }
+ FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
+ response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
+ OssClient storage = OssFactory.instance(sysOss.getService());
+ storage.download(sysOss.getFileName(), response.getOutputStream(), response::setContentLengthLong);
+ }
+
+ /**
+ * 涓婁紶 MultipartFile 鍒板璞″瓨鍌ㄦ湇鍔★紝骞朵繚瀛樻枃浠朵俊鎭埌鏁版嵁搴�
+ *
+ * @param file 瑕佷笂浼犵殑 MultipartFile 瀵硅薄
+ * @return 涓婁紶鎴愬姛鍚庣殑 SysOssVo 瀵硅薄锛屽寘鍚枃浠朵俊鎭�
+ * @throws ServiceException 濡傛灉涓婁紶杩囩▼涓彂鐢熷紓甯革紝鍒欐姏鍑� ServiceException 寮傚父
+ */
+ @Override
+ public SysOssVo upload(MultipartFile file) {
+ if (ObjectUtil.isNull(file) || file.isEmpty()) {
+ throw new ServiceException("涓婁紶鏂囦欢涓嶈兘涓虹┖");
+ }
+ String originalfileName = file.getOriginalFilename();
+ String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
+ OssClient storage = OssFactory.instance();
+ UploadResult uploadResult;
+ try {
+ uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
+ } catch (IOException e) {
+ throw new ServiceException(e.getMessage());
+ }
+ SysOssExt ext1 = new SysOssExt();
+ ext1.setFileSize(file.getSize());
+ ext1.setContentType(file.getContentType());
+ // 淇濆瓨鏂囦欢淇℃伅
+ return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, ext1);
+ }
+
+ /**
+ * 涓婁紶鏂囦欢鍒板璞″瓨鍌ㄦ湇鍔★紝骞朵繚瀛樻枃浠朵俊鎭埌鏁版嵁搴�
+ *
+ * @param file 瑕佷笂浼犵殑鏂囦欢瀵硅薄
+ * @return 涓婁紶鎴愬姛鍚庣殑 SysOssVo 瀵硅薄锛屽寘鍚枃浠朵俊鎭�
+ */
+ @Override
+ public SysOssVo upload(File file) {
+ if (ObjectUtil.isNull(file) || !file.isFile() || file.length() <= 0) {
+ throw new ServiceException("涓婁紶鏂囦欢涓嶈兘涓虹┖");
+ }
+ String originalfileName = file.getName();
+ String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
+ OssClient storage = OssFactory.instance();
+ long length = file.length();
+ UploadResult uploadResult = storage.uploadSuffix(file, suffix);
+ SysOssExt ext1 = new SysOssExt();
+ ext1.setFileSize(length);
+ // 淇濆瓨鏂囦欢淇℃伅
+ return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, ext1);
+ }
+
+ @NotNull
+ private SysOssVo buildResultEntity(String originalfileName, String suffix, String configKey, UploadResult uploadResult, SysOssExt ext1) {
+ SysOss oss = new SysOss();
+ oss.setUrl(uploadResult.getUrl());
+ oss.setFileSuffix(suffix);
+ oss.setFileName(uploadResult.getFilename());
+ oss.setOriginalName(originalfileName);
+ oss.setService(configKey);
+ oss.setExt1(JsonUtils.toJsonString(ext1));
+ baseMapper.insert(oss);
+ SysOssVo sysOssVo = MapstructUtils.convert(oss, SysOssVo.class);
+ return this.matchingUrl(sysOssVo);
+ }
+
+ /**
+ * 鍒犻櫎OSS瀵硅薄瀛樺偍
+ *
+ * @param ids OSS瀵硅薄ID涓�
+ * @param isValid 鍒ゆ柇鏄惁闇�瑕佹牎楠�
+ * @return 缁撴灉
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+ if (isValid) {
+ // 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+ }
+ List<SysOss> list = baseMapper.selectByIds(ids);
+ for (SysOss sysOss : list) {
+ OssClient storage = OssFactory.instance(sysOss.getService());
+ storage.delete(sysOss.getUrl());
+ }
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+
+ /**
+ * 妗剁被鍨嬩负 private 鐨刄RL 淇敼涓轰复鏃禪RL鏃堕暱涓�120s
+ *
+ * @param oss OSS瀵硅薄
+ * @return oss 鍖归厤Url鐨凮SS瀵硅薄
+ */
+ private SysOssVo matchingUrl(SysOssVo oss) {
+ OssClient storage = OssFactory.instance(oss.getService());
+ // 浠呬慨鏀规《绫诲瀷涓� private 鐨刄RL锛屼复鏃禪RL鏃堕暱涓�120s
+ if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
+ oss.setUrl(storage.createPresignedGetUrl(oss.getFileName(), Duration.ofSeconds(120)));
+ }
+ return oss;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPermissionServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPermissionServiceImpl.java
new file mode 100755
index 0000000..4bf6974
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPermissionServiceImpl.java
@@ -0,0 +1,62 @@
+package org.dromara.system.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.TenantConstants;
+import org.dromara.common.core.service.PermissionService;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.service.ISysMenuService;
+import org.dromara.system.service.ISysPermissionService;
+import org.dromara.system.service.ISysRoleService;
+import org.springframework.stereotype.Service;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * 鐢ㄦ埛鏉冮檺澶勭悊
+ *
+ * @author ruoyi
+ */
+@RequiredArgsConstructor
+@Service
+public class SysPermissionServiceImpl implements ISysPermissionService, PermissionService {
+
+ private final ISysRoleService roleService;
+ private final ISysMenuService menuService;
+
+ /**
+ * 鑾峰彇瑙掕壊鏁版嵁鏉冮檺
+ *
+ * @param userId 鐢ㄦ埛id
+ * @return 瑙掕壊鏉冮檺淇℃伅
+ */
+ @Override
+ public Set<String> getRolePermission(Long userId) {
+ Set<String> roles = new HashSet<>();
+ // 绠$悊鍛樻嫢鏈夋墍鏈夋潈闄�
+ if (LoginHelper.isSuperAdmin(userId)) {
+ roles.add(TenantConstants.SUPER_ADMIN_ROLE_KEY);
+ } else {
+ roles.addAll(roleService.selectRolePermissionByUserId(userId));
+ }
+ return roles;
+ }
+
+ /**
+ * 鑾峰彇鑿滃崟鏁版嵁鏉冮檺
+ *
+ * @param userId 鐢ㄦ埛id
+ * @return 鑿滃崟鏉冮檺淇℃伅
+ */
+ @Override
+ public Set<String> getMenuPermission(Long userId) {
+ Set<String> perms = new HashSet<>();
+ // 绠$悊鍛樻嫢鏈夋墍鏈夋潈闄�
+ if (LoginHelper.isSuperAdmin(userId)) {
+ perms.add("*:*:*");
+ } else {
+ perms.addAll(menuService.selectMenuPermsByUserId(userId));
+ }
+ return perms;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java
new file mode 100755
index 0000000..56a0c4b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java
@@ -0,0 +1,277 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.PostService;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.SysPost;
+import org.dromara.system.domain.SysUserPost;
+import org.dromara.system.domain.bo.SysPostBo;
+import org.dromara.system.domain.vo.SysPostVo;
+import org.dromara.system.mapper.SysDeptMapper;
+import org.dromara.system.mapper.SysPostMapper;
+import org.dromara.system.mapper.SysUserPostMapper;
+import org.dromara.system.service.ISysPostService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 宀椾綅淇℃伅 鏈嶅姟灞傚鐞�
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Service
+public class SysPostServiceImpl implements ISysPostService, PostService {
+
+ private final SysPostMapper baseMapper;
+ private final SysDeptMapper deptMapper;
+ private final SysUserPostMapper userPostMapper;
+
+ /**
+ * 鍒嗛〉鏌ヨ宀椾綅鍒楄〃
+ *
+ * @param post 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 宀椾綅鍒嗛〉鍒楄〃
+ */
+ @Override
+ public TableDataInfo<SysPostVo> selectPagePostList(SysPostBo post, PageQuery pageQuery) {
+ Page<SysPostVo> page = baseMapper.selectPagePostList(pageQuery.build(), buildQueryWrapper(post));
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏌ヨ宀椾綅淇℃伅闆嗗悎
+ *
+ * @param post 宀椾綅淇℃伅
+ * @return 宀椾綅淇℃伅闆嗗悎
+ */
+ @Override
+ public List<SysPostVo> selectPostList(SysPostBo post) {
+ return baseMapper.selectVoList(buildQueryWrapper(post));
+ }
+
+ /**
+ * 鏌ヨ鐢ㄦ埛鎵�灞炲矖浣嶇粍
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 宀椾綅ID
+ */
+ @Override
+ public List<SysPostVo> selectPostsByUserId(Long userId) {
+ return baseMapper.selectPostsByUserId(userId);
+ }
+
+ /**
+ * 鏍规嵁鏌ヨ鏉′欢鏋勫缓鏌ヨ鍖呰鍣�
+ *
+ * @param bo 鏌ヨ鏉′欢瀵硅薄
+ * @return 鏋勫缓濂界殑鏌ヨ鍖呰鍣�
+ */
+ private LambdaQueryWrapper<SysPost> buildQueryWrapper(SysPostBo bo) {
+ Map<String, Object> params = bo.getParams();
+ LambdaQueryWrapper<SysPost> wrapper = new LambdaQueryWrapper<>();
+ wrapper.like(StringUtils.isNotBlank(bo.getPostCode()), SysPost::getPostCode, bo.getPostCode())
+ .like(StringUtils.isNotBlank(bo.getPostCategory()), SysPost::getPostCategory, bo.getPostCategory())
+ .like(StringUtils.isNotBlank(bo.getPostName()), SysPost::getPostName, bo.getPostName())
+ .eq(StringUtils.isNotBlank(bo.getStatus()), SysPost::getStatus, bo.getStatus())
+ .between(params.get("beginTime") != null && params.get("endTime") != null,
+ SysPost::getCreateTime, params.get("beginTime"), params.get("endTime"))
+ .orderByAsc(SysPost::getPostSort);
+ if (ObjectUtil.isNotNull(bo.getDeptId())) {
+ //浼樺厛鍗曢儴闂ㄦ悳绱�
+ wrapper.eq(SysPost::getDeptId, bo.getDeptId());
+ } else if (ObjectUtil.isNotNull(bo.getBelongDeptId())) {
+ //閮ㄩ棬鏍戞悳绱�
+ wrapper.and(x -> {
+ List<Long> deptIds = deptMapper.selectDeptAndChildById(bo.getBelongDeptId());
+ x.in(SysPost::getDeptId, deptIds);
+ });
+ }
+ return wrapper;
+ }
+
+ /**
+ * 鏌ヨ鎵�鏈夊矖浣�
+ *
+ * @return 宀椾綅鍒楄〃
+ */
+ @Override
+ public List<SysPostVo> selectPostAll() {
+ return baseMapper.selectVoList(new QueryWrapper<>());
+ }
+
+ /**
+ * 閫氳繃宀椾綅ID鏌ヨ宀椾綅淇℃伅
+ *
+ * @param postId 宀椾綅ID
+ * @return 瑙掕壊瀵硅薄淇℃伅
+ */
+ @Override
+ public SysPostVo selectPostById(Long postId) {
+ return baseMapper.selectVoById(postId);
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鑾峰彇宀椾綅閫夋嫨妗嗗垪琛�
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 閫変腑宀椾綅ID鍒楄〃
+ */
+ @Override
+ public List<Long> selectPostListByUserId(Long userId) {
+ List<SysPostVo> list = baseMapper.selectPostsByUserId(userId);
+ return StreamUtils.toList(list, SysPostVo::getPostId);
+ }
+
+ /**
+ * 閫氳繃宀椾綅ID涓叉煡璇㈠矖浣�
+ *
+ * @param postIds 宀椾綅id涓�
+ * @return 宀椾綅鍒楄〃淇℃伅
+ */
+ @Override
+ public List<SysPostVo> selectPostByIds(List<Long> postIds) {
+ return baseMapper.selectVoList(new LambdaQueryWrapper<SysPost>()
+ .select(SysPost::getPostId, SysPost::getPostName, SysPost::getPostCode)
+ .eq(SysPost::getStatus, SystemConstants.NORMAL)
+ .in(CollUtil.isNotEmpty(postIds), SysPost::getPostId, postIds));
+ }
+
+ /**
+ * 鏍¢獙宀椾綅鍚嶇О鏄惁鍞竴
+ *
+ * @param post 宀椾綅淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkPostNameUnique(SysPostBo post) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysPost>()
+ .eq(SysPost::getPostName, post.getPostName())
+ .eq(SysPost::getDeptId, post.getDeptId())
+ .ne(ObjectUtil.isNotNull(post.getPostId()), SysPost::getPostId, post.getPostId()));
+ return !exist;
+ }
+
+ /**
+ * 鏍¢獙宀椾綅缂栫爜鏄惁鍞竴
+ *
+ * @param post 宀椾綅淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkPostCodeUnique(SysPostBo post) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysPost>()
+ .eq(SysPost::getPostCode, post.getPostCode())
+ .ne(ObjectUtil.isNotNull(post.getPostId()), SysPost::getPostId, post.getPostId()));
+ return !exist;
+ }
+
+ /**
+ * 閫氳繃宀椾綅ID鏌ヨ宀椾綅浣跨敤鏁伴噺
+ *
+ * @param postId 宀椾綅ID
+ * @return 缁撴灉
+ */
+ @Override
+ public long countUserPostById(Long postId) {
+ return userPostMapper.selectCount(new LambdaQueryWrapper<SysUserPost>().eq(SysUserPost::getPostId, postId));
+ }
+
+ /**
+ * 閫氳繃閮ㄩ棬ID鏌ヨ宀椾綅浣跨敤鏁伴噺
+ *
+ * @param deptId 閮ㄩ棬id
+ * @return 缁撴灉
+ */
+ @Override
+ public long countPostByDeptId(Long deptId) {
+ return baseMapper.selectCount(new LambdaQueryWrapper<SysPost>().eq(SysPost::getDeptId, deptId));
+ }
+
+ /**
+ * 鍒犻櫎宀椾綅淇℃伅
+ *
+ * @param postId 宀椾綅ID
+ * @return 缁撴灉
+ */
+ @Override
+ public int deletePostById(Long postId) {
+ return baseMapper.deleteById(postId);
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎宀椾綅淇℃伅
+ *
+ * @param postIds 闇�瑕佸垹闄ょ殑宀椾綅ID
+ * @return 缁撴灉
+ */
+ @Override
+ public int deletePostByIds(List<Long> postIds) {
+ List<SysPost> list = baseMapper.selectByIds(postIds);
+ for (SysPost post : list) {
+ if (this.countUserPostById(post.getPostId()) > 0) {
+ throw new ServiceException("{}宸插垎閰嶏紝涓嶈兘鍒犻櫎!", post.getPostName());
+ }
+ }
+ return baseMapper.deleteByIds(postIds);
+ }
+
+ /**
+ * 鏂板淇濆瓨宀椾綅淇℃伅
+ *
+ * @param bo 宀椾綅淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public int insertPost(SysPostBo bo) {
+ SysPost post = MapstructUtils.convert(bo, SysPost.class);
+ return baseMapper.insert(post);
+ }
+
+ /**
+ * 淇敼淇濆瓨宀椾綅淇℃伅
+ *
+ * @param bo 宀椾綅淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public int updatePost(SysPostBo bo) {
+ SysPost post = MapstructUtils.convert(bo, SysPost.class);
+ return baseMapper.updateById(post);
+ }
+
+ /**
+ * 鏍规嵁宀椾綅 ID 鍒楄〃鏌ヨ宀椾綅鍚嶇О鏄犲皠鍏崇郴
+ *
+ * @param postIds 宀椾綅 ID 鍒楄〃
+ * @return Map锛屽叾涓� key 涓哄矖浣� ID锛寁alue 涓哄搴旂殑宀椾綅鍚嶇О
+ */
+ @Override
+ public Map<Long, String> selectPostNamesByIds(List<Long> postIds) {
+ if (CollUtil.isEmpty(postIds)) {
+ return Collections.emptyMap();
+ }
+ List<SysPost> list = baseMapper.selectList(
+ new LambdaQueryWrapper<SysPost>()
+ .select(SysPost::getPostId, SysPost::getPostName)
+ .in(SysPost::getPostId, postIds)
+ );
+ return StreamUtils.toMap(list, SysPost::getPostId, SysPost::getPostName);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java
new file mode 100755
index 0000000..53d270f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java
@@ -0,0 +1,614 @@
+package org.dromara.system.service.impl;
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.constant.TenantConstants;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.RoleService;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.SysRole;
+import org.dromara.system.domain.SysRoleDept;
+import org.dromara.system.domain.SysRoleMenu;
+import org.dromara.system.domain.SysUserRole;
+import org.dromara.system.domain.bo.SysRoleBo;
+import org.dromara.system.domain.vo.SysRoleVo;
+import org.dromara.system.mapper.SysRoleDeptMapper;
+import org.dromara.system.mapper.SysRoleMapper;
+import org.dromara.system.mapper.SysRoleMenuMapper;
+import org.dromara.system.mapper.SysUserRoleMapper;
+import org.dromara.system.service.ISysRoleService;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
+/**
+ * 瑙掕壊 涓氬姟灞傚鐞�
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Service
+public class SysRoleServiceImpl implements ISysRoleService, RoleService {
+
+ private final SysRoleMapper baseMapper;
+ private final SysRoleMenuMapper roleMenuMapper;
+ private final SysUserRoleMapper userRoleMapper;
+ private final SysRoleDeptMapper roleDeptMapper;
+
+ /**
+ * 鍒嗛〉鏌ヨ瑙掕壊鍒楄〃
+ *
+ * @param role 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 瑙掕壊鍒嗛〉鍒楄〃
+ */
+ @Override
+ public TableDataInfo<SysRoleVo> selectPageRoleList(SysRoleBo role, PageQuery pageQuery) {
+ Page<SysRoleVo> page = baseMapper.selectPageRoleList(pageQuery.build(), this.buildQueryWrapper(role));
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鏌ヨ瑙掕壊鏁版嵁
+ *
+ * @param role 瑙掕壊淇℃伅
+ * @return 瑙掕壊鏁版嵁闆嗗悎淇℃伅
+ */
+ @Override
+ public List<SysRoleVo> selectRoleList(SysRoleBo role) {
+ return baseMapper.selectRoleList(this.buildQueryWrapper(role));
+ }
+
+ private Wrapper<SysRole> buildQueryWrapper(SysRoleBo bo) {
+ Map<String, Object> params = bo.getParams();
+ LambdaQueryWrapper<SysRole> wrapper = Wrappers.lambdaQuery();
+ wrapper.eq(ObjectUtil.isNotNull(bo.getRoleId()), SysRole::getRoleId, bo.getRoleId())
+ .like(StringUtils.isNotBlank(bo.getRoleName()), SysRole::getRoleName, bo.getRoleName())
+ .eq(StringUtils.isNotBlank(bo.getStatus()), SysRole::getStatus, bo.getStatus())
+ .like(StringUtils.isNotBlank(bo.getRoleKey()), SysRole::getRoleKey, bo.getRoleKey())
+ .between(params.get("beginTime") != null && params.get("endTime") != null,
+ SysRole::getCreateTime, params.get("beginTime"), params.get("endTime"))
+ .orderByAsc(SysRole::getRoleSort).orderByAsc(SysRole::getCreateTime);
+ return wrapper;
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ瑙掕壊
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 瑙掕壊鍒楄〃
+ */
+ @Override
+ public List<SysRoleVo> selectRolesByUserId(Long userId) {
+ return baseMapper.selectRolesByUserId(userId);
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ瑙掕壊鍒楄〃(鍖呭惈琚巿鏉冪姸鎬�)
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 瑙掕壊鍒楄〃
+ */
+ @Override
+ public List<SysRoleVo> selectRolesAuthByUserId(Long userId) {
+ List<SysRoleVo> userRoles = baseMapper.selectRolesByUserId(userId);
+ List<SysRoleVo> roles = selectRoleAll();
+ // 浣跨敤HashSet鎻愰珮鏌ユ壘鏁堢巼
+ Set<Long> userRoleIds = StreamUtils.toSet(userRoles, SysRoleVo::getRoleId);
+ for (SysRoleVo role : roles) {
+ if (userRoleIds.contains(role.getRoleId())) {
+ role.setFlag(true);
+ }
+ }
+ return roles;
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鏌ヨ鏉冮檺
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鏉冮檺鍒楄〃
+ */
+ @Override
+ public Set<String> selectRolePermissionByUserId(Long userId) {
+ List<SysRoleVo> perms = baseMapper.selectRolesByUserId(userId);
+ Set<String> permsSet = new HashSet<>();
+ for (SysRoleVo perm : perms) {
+ if (ObjectUtil.isNotNull(perm)) {
+ permsSet.addAll(StringUtils.splitList(perm.getRoleKey().trim()));
+ }
+ }
+ return permsSet;
+ }
+
+ /**
+ * 鏌ヨ鎵�鏈夎鑹�
+ *
+ * @return 瑙掕壊鍒楄〃
+ */
+ @Override
+ public List<SysRoleVo> selectRoleAll() {
+ return this.selectRoleList(new SysRoleBo());
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鑾峰彇瑙掕壊閫夋嫨妗嗗垪琛�
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 閫変腑瑙掕壊ID鍒楄〃
+ */
+ @Override
+ public List<Long> selectRoleListByUserId(Long userId) {
+ List<SysRoleVo> list = baseMapper.selectRolesByUserId(userId);
+ return StreamUtils.toList(list, SysRoleVo::getRoleId);
+ }
+
+ /**
+ * 閫氳繃瑙掕壊ID鏌ヨ瑙掕壊
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 瑙掕壊瀵硅薄淇℃伅
+ */
+ @Override
+ public SysRoleVo selectRoleById(Long roleId) {
+ return baseMapper.selectRoleById(roleId);
+ }
+
+ /**
+ * 閫氳繃瑙掕壊ID涓叉煡璇㈣鑹�
+ *
+ * @param roleIds 瑙掕壊ID涓�
+ * @return 瑙掕壊鍒楄〃淇℃伅
+ */
+ @Override
+ public List<SysRoleVo> selectRoleByIds(List<Long> roleIds) {
+ return baseMapper.selectRoleList(new LambdaQueryWrapper<SysRole>()
+ .eq(SysRole::getStatus, SystemConstants.NORMAL)
+ .in(CollUtil.isNotEmpty(roleIds), SysRole::getRoleId, roleIds));
+ }
+
+ /**
+ * 鏍¢獙瑙掕壊鍚嶇О鏄惁鍞竴
+ *
+ * @param role 瑙掕壊淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkRoleNameUnique(SysRoleBo role) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysRole>()
+ .eq(SysRole::getRoleName, role.getRoleName())
+ .ne(ObjectUtil.isNotNull(role.getRoleId()), SysRole::getRoleId, role.getRoleId()));
+ return !exist;
+ }
+
+ /**
+ * 鏍¢獙瑙掕壊鏉冮檺鏄惁鍞竴
+ *
+ * @param role 瑙掕壊淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkRoleKeyUnique(SysRoleBo role) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysRole>()
+ .eq(SysRole::getRoleKey, role.getRoleKey())
+ .ne(ObjectUtil.isNotNull(role.getRoleId()), SysRole::getRoleId, role.getRoleId()));
+ return !exist;
+ }
+
+ /**
+ * 鏍¢獙瑙掕壊鏄惁鍏佽鎿嶄綔
+ *
+ * @param role 瑙掕壊淇℃伅
+ */
+ @Override
+ public void checkRoleAllowed(SysRoleBo role) {
+ if (ObjectUtil.isNotNull(role.getRoleId()) && LoginHelper.isSuperAdmin(role.getRoleId())) {
+ throw new ServiceException("涓嶅厑璁告搷浣滆秴绾х鐞嗗憳瑙掕壊");
+ }
+ String[] keys = new String[]{TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.TENANT_ADMIN_ROLE_KEY};
+ // 鏂板涓嶅厑璁镐娇鐢� 绠$悊鍛樻爣璇嗙
+ if (ObjectUtil.isNull(role.getRoleId())
+ && StringUtils.equalsAny(role.getRoleKey(), keys)) {
+ throw new ServiceException("涓嶅厑璁镐娇鐢ㄧ郴缁熷唴缃鐞嗗憳瑙掕壊鏍囪瘑绗�!");
+ }
+ // 淇敼涓嶅厑璁镐慨鏀� 绠$悊鍛樻爣璇嗙
+ if (ObjectUtil.isNotNull(role.getRoleId())) {
+ SysRole sysRole = baseMapper.selectById(role.getRoleId());
+ // 濡傛灉鏍囪瘑绗︿笉鐩哥瓑 鍒ゆ柇涓轰慨鏀逛簡绠$悊鍛樻爣璇嗙
+ if (!StringUtils.equals(sysRole.getRoleKey(), role.getRoleKey())) {
+ if (StringUtils.equalsAny(sysRole.getRoleKey(), keys)) {
+ throw new ServiceException("涓嶅厑璁镐慨鏀圭郴缁熷唴缃鐞嗗憳瑙掕壊鏍囪瘑绗�!");
+ } else if (StringUtils.equalsAny(role.getRoleKey(), keys)) {
+ throw new ServiceException("涓嶅厑璁镐娇鐢ㄧ郴缁熷唴缃鐞嗗憳瑙掕壊鏍囪瘑绗�!");
+ }
+ }
+ }
+ }
+
+ /**
+ * 鏍¢獙瑙掕壊鏄惁鏈夋暟鎹潈闄�
+ *
+ * @param roleId 瑙掕壊id
+ */
+ @Override
+ public void checkRoleDataScope(Long roleId) {
+ if (ObjectUtil.isNull(roleId)) {
+ return;
+ }
+ this.checkRoleDataScope(Collections.singletonList(roleId));
+ }
+
+ /**
+ * 鏍¢獙瑙掕壊鏄惁鏈夋暟鎹潈闄�
+ *
+ * @param roleIds 瑙掕壊ID鍒楄〃锛堟敮鎸佷紶鍗曚釜ID锛�
+ */
+ @Override
+ public void checkRoleDataScope(List<Long> roleIds) {
+ if (CollUtil.isEmpty(roleIds) || LoginHelper.isSuperAdmin()) {
+ return;
+ }
+ long count = baseMapper.selectRoleCount(roleIds);
+ if (count != roleIds.size()) {
+ throw new ServiceException("娌℃湁鏉冮檺璁块棶閮ㄥ垎瑙掕壊鏁版嵁锛�");
+ }
+ }
+
+ /**
+ * 閫氳繃瑙掕壊ID鏌ヨ瑙掕壊浣跨敤鏁伴噺
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 缁撴灉
+ */
+ @Override
+ public long countUserRoleByRoleId(Long roleId) {
+ return userRoleMapper.selectCount(new LambdaQueryWrapper<SysUserRole>().eq(SysUserRole::getRoleId, roleId));
+ }
+
+ /**
+ * 鏂板淇濆瓨瑙掕壊淇℃伅
+ *
+ * @param bo 瑙掕壊淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int insertRole(SysRoleBo bo) {
+ SysRole role = MapstructUtils.convert(bo, SysRole.class);
+ // 鏂板瑙掕壊淇℃伅
+ baseMapper.insert(role);
+ bo.setRoleId(role.getRoleId());
+ return insertRoleMenu(bo);
+ }
+
+ /**
+ * 淇敼淇濆瓨瑙掕壊淇℃伅
+ *
+ * @param bo 瑙掕壊淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int updateRole(SysRoleBo bo) {
+ SysRole role = MapstructUtils.convert(bo, SysRole.class);
+
+ if (SystemConstants.DISABLE.equals(role.getStatus()) && this.countUserRoleByRoleId(role.getRoleId()) > 0) {
+ throw new ServiceException("瑙掕壊宸插垎閰嶏紝涓嶈兘绂佺敤!");
+ }
+ // 淇敼瑙掕壊淇℃伅
+ baseMapper.updateById(role);
+ // 鍒犻櫎瑙掕壊涓庤彍鍗曞叧鑱�
+ roleMenuMapper.delete(new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, role.getRoleId()));
+ return insertRoleMenu(bo);
+ }
+
+ /**
+ * 淇敼瑙掕壊鐘舵��
+ *
+ * @param roleId 瑙掕壊ID
+ * @param status 瑙掕壊鐘舵��
+ * @return 缁撴灉
+ */
+ @Override
+ public int updateRoleStatus(Long roleId, String status) {
+ if (SystemConstants.DISABLE.equals(status) && this.countUserRoleByRoleId(roleId) > 0) {
+ throw new ServiceException("瑙掕壊宸插垎閰嶏紝涓嶈兘绂佺敤!");
+ }
+ return baseMapper.update(null,
+ new LambdaUpdateWrapper<SysRole>()
+ .set(SysRole::getStatus, status)
+ .eq(SysRole::getRoleId, roleId));
+ }
+
+ /**
+ * 淇敼鏁版嵁鏉冮檺淇℃伅
+ *
+ * @param bo 瑙掕壊淇℃伅
+ * @return 缁撴灉
+ */
+ @CacheEvict(cacheNames = CacheNames.SYS_ROLE_CUSTOM, key = "#bo.roleId")
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int authDataScope(SysRoleBo bo) {
+ SysRole role = MapstructUtils.convert(bo, SysRole.class);
+ // 淇敼瑙掕壊淇℃伅
+ baseMapper.updateById(role);
+ // 鍒犻櫎瑙掕壊涓庨儴闂ㄥ叧鑱�
+ roleDeptMapper.delete(new LambdaQueryWrapper<SysRoleDept>().eq(SysRoleDept::getRoleId, role.getRoleId()));
+ // 鏂板瑙掕壊鍜岄儴闂ㄤ俊鎭紙鏁版嵁鏉冮檺锛�
+ return insertRoleDept(bo);
+ }
+
+ /**
+ * 鏂板瑙掕壊鑿滃崟淇℃伅
+ *
+ * @param role 瑙掕壊瀵硅薄
+ */
+ private int insertRoleMenu(SysRoleBo role) {
+ int rows = 1;
+ // 鏂板鐢ㄦ埛涓庤鑹茬鐞�
+ List<SysRoleMenu> list = new ArrayList<>();
+ for (Long menuId : role.getMenuIds()) {
+ SysRoleMenu rm = new SysRoleMenu();
+ rm.setRoleId(role.getRoleId());
+ rm.setMenuId(menuId);
+ list.add(rm);
+ }
+ if (CollUtil.isNotEmpty(list)) {
+ rows = roleMenuMapper.insertBatch(list) ? list.size() : 0;
+ }
+ return rows;
+ }
+
+ /**
+ * 鏂板瑙掕壊閮ㄩ棬淇℃伅(鏁版嵁鏉冮檺)
+ *
+ * @param role 瑙掕壊瀵硅薄
+ */
+ private int insertRoleDept(SysRoleBo role) {
+ int rows = 1;
+ // 鏂板瑙掕壊涓庨儴闂紙鏁版嵁鏉冮檺锛夌鐞�
+ List<SysRoleDept> list = new ArrayList<>();
+ for (Long deptId : role.getDeptIds()) {
+ SysRoleDept rd = new SysRoleDept();
+ rd.setRoleId(role.getRoleId());
+ rd.setDeptId(deptId);
+ list.add(rd);
+ }
+ if (CollUtil.isNotEmpty(list)) {
+ rows = roleDeptMapper.insertBatch(list) ? list.size() : 0;
+ }
+ return rows;
+ }
+
+ /**
+ * 閫氳繃瑙掕壊ID鍒犻櫎瑙掕壊
+ *
+ * @param roleId 瑙掕壊ID
+ * @return 缁撴灉
+ */
+ @CacheEvict(cacheNames = CacheNames.SYS_ROLE_CUSTOM, key = "#roleId")
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int deleteRoleById(Long roleId) {
+ // 鍒犻櫎瑙掕壊涓庤彍鍗曞叧鑱�
+ roleMenuMapper.delete(new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, roleId));
+ // 鍒犻櫎瑙掕壊涓庨儴闂ㄥ叧鑱�
+ roleDeptMapper.delete(new LambdaQueryWrapper<SysRoleDept>().eq(SysRoleDept::getRoleId, roleId));
+ return baseMapper.deleteById(roleId);
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎瑙掕壊淇℃伅
+ *
+ * @param roleIds 闇�瑕佸垹闄ょ殑瑙掕壊ID
+ * @return 缁撴灉
+ */
+ @CacheEvict(cacheNames = CacheNames.SYS_ROLE_CUSTOM, allEntries = true)
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int deleteRoleByIds(List<Long> roleIds) {
+ this.checkRoleDataScope(roleIds);
+ List<SysRole> roles = baseMapper.selectByIds(roleIds);
+ for (SysRole role : roles) {
+ checkRoleAllowed(BeanUtil.toBean(role, SysRoleBo.class));
+ if (countUserRoleByRoleId(role.getRoleId()) > 0) {
+ throw new ServiceException(String.format("%1$s宸插垎閰嶏紝涓嶈兘鍒犻櫎!", role.getRoleName()));
+ }
+ }
+ // 鍒犻櫎瑙掕壊涓庤彍鍗曞叧鑱�
+ roleMenuMapper.delete(new LambdaQueryWrapper<SysRoleMenu>().in(SysRoleMenu::getRoleId, roleIds));
+ // 鍒犻櫎瑙掕壊涓庨儴闂ㄥ叧鑱�
+ roleDeptMapper.delete(new LambdaQueryWrapper<SysRoleDept>().in(SysRoleDept::getRoleId, roleIds));
+ return baseMapper.deleteByIds(roleIds);
+ }
+
+ /**
+ * 鍙栨秷鎺堟潈鐢ㄦ埛瑙掕壊
+ *
+ * @param userRole 鐢ㄦ埛鍜岃鑹插叧鑱斾俊鎭�
+ * @return 缁撴灉
+ */
+ @Override
+ public int deleteAuthUser(SysUserRole userRole) {
+ if (LoginHelper.getUserId().equals(userRole.getUserId())) {
+ throw new ServiceException("涓嶅厑璁镐慨鏀瑰綋鍓嶇敤鎴疯鑹�!");
+ }
+ int rows = userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
+ .eq(SysUserRole::getRoleId, userRole.getRoleId())
+ .eq(SysUserRole::getUserId, userRole.getUserId()));
+ if (rows > 0) {
+ cleanOnlineUser(List.of(userRole.getUserId()));
+ }
+ return rows;
+ }
+
+ /**
+ * 鎵归噺鍙栨秷鎺堟潈鐢ㄦ埛瑙掕壊
+ *
+ * @param roleId 瑙掕壊ID
+ * @param userIds 闇�瑕佸彇娑堟巿鏉冪殑鐢ㄦ埛鏁版嵁ID
+ * @return 缁撴灉
+ */
+ @Override
+ public int deleteAuthUsers(Long roleId, Long[] userIds) {
+ List<Long> ids = List.of(userIds);
+ if (ids.contains(LoginHelper.getUserId())) {
+ throw new ServiceException("涓嶅厑璁镐慨鏀瑰綋鍓嶇敤鎴疯鑹�!");
+ }
+ int rows = userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
+ .eq(SysUserRole::getRoleId, roleId)
+ .in(SysUserRole::getUserId, ids));
+ if (rows > 0) {
+ cleanOnlineUser(ids);
+ }
+ return rows;
+ }
+
+ /**
+ * 鎵归噺閫夋嫨鎺堟潈鐢ㄦ埛瑙掕壊
+ *
+ * @param roleId 瑙掕壊ID
+ * @param userIds 闇�瑕佹巿鏉冪殑鐢ㄦ埛鏁版嵁ID
+ * @return 缁撴灉
+ */
+ @Override
+ public int insertAuthUsers(Long roleId, Long[] userIds) {
+ // 鏂板鐢ㄦ埛涓庤鑹茬鐞�
+ int rows = 1;
+ List<Long> ids = List.of(userIds);
+ if (ids.contains(LoginHelper.getUserId())) {
+ throw new ServiceException("涓嶅厑璁镐慨鏀瑰綋鍓嶇敤鎴疯鑹�!");
+ }
+ List<SysUserRole> list = StreamUtils.toList(ids, userId -> {
+ SysUserRole ur = new SysUserRole();
+ ur.setUserId(userId);
+ ur.setRoleId(roleId);
+ return ur;
+ });
+ if (CollUtil.isNotEmpty(list)) {
+ rows = userRoleMapper.insertBatch(list) ? list.size() : 0;
+ }
+ if (rows > 0) {
+ cleanOnlineUser(ids);
+ }
+ return rows;
+ }
+
+ /**
+ * 鏍规嵁瑙掕壊ID娓呴櫎璇ヨ鑹插叧鑱旂殑鎵�鏈夊湪绾跨敤鎴风殑鐧诲綍鐘舵�侊紙韪㈠嚭鍦ㄧ嚎鐢ㄦ埛锛�
+ *
+ * <p>
+ * 鍏堝垽鏂鑹叉槸鍚︾粦瀹氱敤鎴凤紝鑻ユ棤缁戝畾鍒欑洿鎺ヨ繑鍥�
+ * 鐒跺悗閬嶅巻褰撳墠鎵�鏈夊湪绾縏oken锛屾煡鎵炬嫢鏈夎瑙掕壊鐨勭敤鎴峰苟寮哄埗鐧诲嚭
+ * 娉ㄦ剰锛氬湪绾跨敤鎴烽噺杩囧ぇ鏃讹紝鎿嶄綔鍙兘瀵艰嚧 Redis 闃诲锛岄渶璋ㄦ厧璋冪敤
+ * </p>
+ *
+ * @param roleId 瑙掕壊ID
+ */
+ @Override
+ public void cleanOnlineUserByRole(Long roleId) {
+ // 濡傛灉瑙掕壊鏈粦瀹氱敤鎴� 鐩存帴杩斿洖
+ Long num = userRoleMapper.selectCount(new LambdaQueryWrapper<SysUserRole>().eq(SysUserRole::getRoleId, roleId));
+ if (num == 0) {
+ return;
+ }
+ List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
+ if (CollUtil.isEmpty(keys)) {
+ return;
+ }
+ // 瑙掕壊鍏宠仈鐨勫湪绾跨敤鎴烽噺杩囧ぇ浼氬鑷磖edis闃诲鍗¢】 璋ㄦ厧鎿嶄綔
+ keys.parallelStream().forEach(key -> {
+ String token = StringUtils.substringAfterLast(key, ":");
+ // 濡傛灉宸茬粡杩囨湡鍒欒烦杩�
+ if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
+ return;
+ }
+ LoginUser loginUser = LoginHelper.getLoginUser(token);
+ if (ObjectUtil.isNull(loginUser) || CollUtil.isEmpty(loginUser.getRoles())) {
+ return;
+ }
+ if (loginUser.getRoles().stream().anyMatch(r -> r.getRoleId().equals(roleId))) {
+ try {
+ StpUtil.logoutByTokenValue(token);
+ } catch (NotLoginException ignored) {
+ }
+ }
+ });
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛ID鍒楄〃娓呴櫎瀵瑰簲鍦ㄧ嚎鐢ㄦ埛鐨勭櫥褰曠姸鎬侊紙韪㈠嚭鎸囧畾鐢ㄦ埛锛�
+ *
+ * <p>
+ * 閬嶅巻褰撳墠鎵�鏈夊湪绾縏oken锛屽尮閰嶇敤鎴稩D鍒楄〃涓殑鐢ㄦ埛锛屽己鍒剁櫥鍑�
+ * 娉ㄦ剰锛氬湪绾跨敤鎴烽噺杩囧ぇ鏃讹紝鎿嶄綔鍙兘瀵艰嚧 Redis 闃诲锛岄渶璋ㄦ厧璋冪敤
+ * </p>
+ *
+ * @param userIds 闇�瑕佹竻闄ょ殑鐢ㄦ埛ID鍒楄〃
+ */
+ @Override
+ public void cleanOnlineUser(List<Long> userIds) {
+ List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
+ if (CollUtil.isEmpty(keys)) {
+ return;
+ }
+ // 瑙掕壊鍏宠仈鐨勫湪绾跨敤鎴烽噺杩囧ぇ浼氬鑷磖edis闃诲鍗¢】 璋ㄦ厧鎿嶄綔
+ keys.parallelStream().forEach(key -> {
+ String token = StringUtils.substringAfterLast(key, ":");
+ // 濡傛灉宸茬粡杩囨湡鍒欒烦杩�
+ if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
+ return;
+ }
+ LoginUser loginUser = LoginHelper.getLoginUser(token);
+ if (ObjectUtil.isNull(loginUser)) {
+ return;
+ }
+ if (userIds.contains(loginUser.getUserId())) {
+ try {
+ StpUtil.logoutByTokenValue(token);
+ } catch (NotLoginException ignored) {
+ }
+ }
+ });
+ }
+
+ /**
+ * 鏍规嵁瑙掕壊 ID 鍒楄〃鏌ヨ瑙掕壊鍚嶇О鏄犲皠鍏崇郴
+ *
+ * @param roleIds 瑙掕壊 ID 鍒楄〃
+ * @return Map锛屽叾涓� key 涓鸿鑹� ID锛寁alue 涓哄搴旂殑瑙掕壊鍚嶇О
+ */
+ @Override
+ public Map<Long, String> selectRoleNamesByIds(List<Long> roleIds) {
+ if (CollUtil.isEmpty(roleIds)) {
+ return Collections.emptyMap();
+ }
+ List<SysRole> list = baseMapper.selectList(
+ new LambdaQueryWrapper<SysRole>()
+ .select(SysRole::getRoleId, SysRole::getRoleName)
+ .in(SysRole::getRoleId, roleIds)
+ );
+ return StreamUtils.toMap(list, SysRole::getRoleId, SysRole::getRoleName);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSensitiveServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSensitiveServiceImpl.java
new file mode 100755
index 0000000..8a0d45e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSensitiveServiceImpl.java
@@ -0,0 +1,47 @@
+package org.dromara.system.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.util.ArrayUtil;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.sensitive.core.SensitiveService;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.springframework.stereotype.Service;
+
+/**
+ * 鑴辨晱鏈嶅姟
+ * 榛樿绠$悊鍛樹笉杩囨护
+ * 闇�鑷鏍规嵁涓氬姟閲嶅啓瀹炵幇
+ *
+ * @author Lion Li
+ * @version 3.6.0
+ */
+@Service
+public class SysSensitiveServiceImpl implements SensitiveService {
+
+ /**
+ * 鏄惁鑴辨晱
+ */
+ @Override
+ public boolean isSensitive(String[] roleKey, String[] perms) {
+ if (!LoginHelper.isLogin()) {
+ return true;
+ }
+ boolean roleExist = ArrayUtil.isNotEmpty(roleKey);
+ boolean permsExist = ArrayUtil.isNotEmpty(perms);
+ if (roleExist && permsExist) {
+ if (StpUtil.hasRoleOr(roleKey) && StpUtil.hasPermissionOr(perms)) {
+ return false;
+ }
+ } else if (roleExist && StpUtil.hasRoleOr(roleKey)) {
+ return false;
+ } else if (permsExist && StpUtil.hasPermissionOr(perms)) {
+ return false;
+ }
+
+ if (TenantHelper.isEnable()) {
+ return !LoginHelper.isSuperAdmin() && !LoginHelper.isTenantAdmin();
+ }
+ return !LoginHelper.isSuperAdmin();
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSocialServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSocialServiceImpl.java
new file mode 100755
index 0000000..9c54cbc
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSocialServiceImpl.java
@@ -0,0 +1,112 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.system.domain.SysSocial;
+import org.dromara.system.domain.bo.SysSocialBo;
+import org.dromara.system.domain.vo.SysSocialVo;
+import org.dromara.system.mapper.SysSocialMapper;
+import org.dromara.system.service.ISysSocialService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 绀句細鍖栧叧绯籗ervice涓氬姟灞傚鐞�
+ *
+ * @author thiszhc
+ * @date 2023-06-12
+ */
+@RequiredArgsConstructor
+@Service
+public class SysSocialServiceImpl implements ISysSocialService {
+
+ private final SysSocialMapper baseMapper;
+
+
+ /**
+ * 鏌ヨ绀句細鍖栧叧绯�
+ */
+ @Override
+ public SysSocialVo queryById(String id) {
+ return baseMapper.selectVoById(id);
+ }
+
+ /**
+ * 鎺堟潈鍒楄〃
+ */
+ @Override
+ public List<SysSocialVo> queryList(SysSocialBo bo) {
+ LambdaQueryWrapper<SysSocial> lqw = new LambdaQueryWrapper<SysSocial>()
+ .eq(ObjectUtil.isNotNull(bo.getUserId()), SysSocial::getUserId, bo.getUserId())
+ .eq(StringUtils.isNotBlank(bo.getAuthId()), SysSocial::getAuthId, bo.getAuthId())
+ .eq(StringUtils.isNotBlank(bo.getSource()), SysSocial::getSource, bo.getSource());
+ return baseMapper.selectVoList(lqw);
+ }
+
+ @Override
+ public List<SysSocialVo> queryListByUserId(Long userId) {
+ return baseMapper.selectVoList(new LambdaQueryWrapper<SysSocial>().eq(SysSocial::getUserId, userId));
+ }
+
+
+ /**
+ * 鏂板绀句細鍖栧叧绯�
+ */
+ @Override
+ public Boolean insertByBo(SysSocialBo bo) {
+ SysSocial add = MapstructUtils.convert(bo, SysSocial.class);
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ if (add != null) {
+ bo.setId(add.getId());
+ } else {
+ return false;
+ }
+ }
+ return flag;
+ }
+
+ /**
+ * 鏇存柊绀句細鍖栧叧绯�
+ */
+ @Override
+ public Boolean updateByBo(SysSocialBo bo) {
+ SysSocial update = MapstructUtils.convert(bo, SysSocial.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+ */
+ private void validEntityBeforeSave(SysSocial entity) {
+ //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+ }
+
+
+ /**
+ * 鍒犻櫎绀句細鍖栧叧绯�
+ */
+ @Override
+ public Boolean deleteWithValidById(Long id) {
+ return baseMapper.deleteById(id) > 0;
+ }
+
+
+ /**
+ * 鏍规嵁 authId 鏌ヨ鐢ㄦ埛淇℃伅
+ *
+ * @param authId 璁よ瘉id
+ * @return 鎺堟潈淇℃伅
+ */
+ @Override
+ public List<SysSocialVo> selectByAuthId(String authId) {
+ return baseMapper.selectVoList(new LambdaQueryWrapper<SysSocial>().eq(SysSocial::getAuthId, authId));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTaskAssigneeServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTaskAssigneeServiceImpl.java
new file mode 100755
index 0000000..7b31f7b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTaskAssigneeServiceImpl.java
@@ -0,0 +1,138 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.convert.Convert;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
+import org.dromara.common.core.domain.model.TaskAssigneeBody;
+import org.dromara.common.core.service.TaskAssigneeService;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.SysDeptBo;
+import org.dromara.system.domain.bo.SysPostBo;
+import org.dromara.system.domain.bo.SysRoleBo;
+import org.dromara.system.domain.bo.SysUserBo;
+import org.dromara.system.domain.vo.SysDeptVo;
+import org.dromara.system.domain.vo.SysPostVo;
+import org.dromara.system.domain.vo.SysRoleVo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.service.ISysDeptService;
+import org.dromara.system.service.ISysPostService;
+import org.dromara.system.service.ISysRoleService;
+import org.dromara.system.service.ISysUserService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 宸ヤ綔娴佽璁″櫒鑾峰彇浠诲姟鎵ц浜�
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@Service
+public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
+
+ // 涓婄骇Service娉ㄥ叆涓嬬骇Service 鍏朵粬Service姘歌繙涓嶅彲鑳芥敞鍏ュ綋鍓嶇被 閬垮厤寰幆娉ㄥ叆
+ private final ISysPostService postService;
+ private final ISysDeptService deptService;
+ private final ISysUserService userService;
+ private final ISysRoleService roleService;
+
+ /**
+ * 鏌ヨ瑙掕壊骞惰繑鍥炰换鍔℃寚娲剧殑鍒楄〃锛屾敮鎸佸垎椤�
+ *
+ * @param taskQuery 鏌ヨ鏉′欢
+ * @return 鍔炵悊浜�
+ */
+ @Override
+ public TaskAssigneeDTO selectRolesByTaskAssigneeList(TaskAssigneeBody taskQuery) {
+ PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum());
+ SysRoleBo bo = new SysRoleBo();
+ bo.setRoleKey(taskQuery.getHandlerCode());
+ bo.setRoleName(taskQuery.getHandlerName());
+ bo.setStatus(SystemConstants.NORMAL);
+ Map<String, Object> params = bo.getParams();
+ params.put("beginTime", taskQuery.getBeginTime());
+ params.put("endTime", taskQuery.getEndTime());
+ TableDataInfo<SysRoleVo> page = roleService.selectPageRoleList(bo, pageQuery);
+ // 浣跨敤灏佽鐨勫瓧娈垫槧灏勬柟娉曡繘琛岃浆鎹�
+ List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
+ item -> Convert.toStr(item.getRoleId()), SysRoleVo::getRoleKey, SysRoleVo::getRoleName, item -> "", SysRoleVo::getCreateTime);
+ return new TaskAssigneeDTO(page.getTotal(), handlers);
+ }
+
+ /**
+ * 鏌ヨ宀椾綅骞惰繑鍥炰换鍔℃寚娲剧殑鍒楄〃锛屾敮鎸佸垎椤�
+ *
+ * @param taskQuery 鏌ヨ鏉′欢
+ * @return 鍔炵悊浜�
+ */
+ @Override
+ public TaskAssigneeDTO selectPostsByTaskAssigneeList(TaskAssigneeBody taskQuery) {
+ PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum());
+ SysPostBo bo = new SysPostBo();
+ bo.setPostCategory(taskQuery.getHandlerCode());
+ bo.setPostName(taskQuery.getHandlerName());
+ bo.setStatus(SystemConstants.NORMAL);
+ Map<String, Object> params = bo.getParams();
+ params.put("beginTime", taskQuery.getBeginTime());
+ params.put("endTime", taskQuery.getEndTime());
+ bo.setBelongDeptId(Convert.toLong(taskQuery.getGroupId()));
+ TableDataInfo<SysPostVo> page = postService.selectPagePostList(bo, pageQuery);
+ // 浣跨敤灏佽鐨勫瓧娈垫槧灏勬柟娉曡繘琛岃浆鎹�
+ List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
+ item -> Convert.toStr(item.getPostId()), SysPostVo::getPostCategory, SysPostVo::getPostName, item -> Convert.toStr(item.getDeptId()), SysPostVo::getCreateTime);
+ return new TaskAssigneeDTO(page.getTotal(), handlers);
+ }
+
+ /**
+ * 鏌ヨ閮ㄩ棬骞惰繑鍥炰换鍔℃寚娲剧殑鍒楄〃锛屾敮鎸佸垎椤�
+ *
+ * @param taskQuery 鏌ヨ鏉′欢
+ * @return 鍔炵悊浜�
+ */
+ @Override
+ public TaskAssigneeDTO selectDeptsByTaskAssigneeList(TaskAssigneeBody taskQuery) {
+ PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum());
+ SysDeptBo bo = new SysDeptBo();
+ bo.setDeptCategory(taskQuery.getHandlerCode());
+ bo.setDeptName(taskQuery.getHandlerName());
+ bo.setStatus(SystemConstants.NORMAL);
+ Map<String, Object> params = bo.getParams();
+ params.put("beginTime", taskQuery.getBeginTime());
+ params.put("endTime", taskQuery.getEndTime());
+ bo.setBelongDeptId(Convert.toLong(taskQuery.getGroupId()));
+ TableDataInfo<SysDeptVo> page = deptService.selectPageDeptList(bo, pageQuery);
+ // 浣跨敤灏佽鐨勫瓧娈垫槧灏勬柟娉曡繘琛岃浆鎹�
+ List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
+ item -> Convert.toStr(item.getDeptId()), SysDeptVo::getDeptCategory, SysDeptVo::getDeptName, item -> Convert.toStr(item.getParentId()), SysDeptVo::getCreateTime);
+ return new TaskAssigneeDTO(page.getTotal(), handlers);
+ }
+
+ /**
+ * 鏌ヨ鐢ㄦ埛骞惰繑鍥炰换鍔℃寚娲剧殑鍒楄〃锛屾敮鎸佸垎椤�
+ *
+ * @param taskQuery 鏌ヨ鏉′欢
+ * @return 鍔炵悊浜�
+ */
+ @Override
+ public TaskAssigneeDTO selectUsersByTaskAssigneeList(TaskAssigneeBody taskQuery) {
+ PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum());
+ SysUserBo bo = new SysUserBo();
+ bo.setUserName(taskQuery.getHandlerCode());
+ bo.setNickName(taskQuery.getHandlerName());
+ bo.setStatus(SystemConstants.NORMAL);
+ Map<String, Object> params = bo.getParams();
+ params.put("beginTime", taskQuery.getBeginTime());
+ params.put("endTime", taskQuery.getEndTime());
+ bo.setDeptId(Convert.toLong(taskQuery.getGroupId()));
+ TableDataInfo<SysUserVo> page = userService.selectPageUserList(bo, pageQuery);
+ // 浣跨敤灏佽鐨勫瓧娈垫槧灏勬柟娉曡繘琛岃浆鎹�
+ List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
+ item -> Convert.toStr(item.getUserId()), SysUserVo::getUserName, SysUserVo::getNickName, item -> Convert.toStr(item.getDeptId()), SysUserVo::getCreateTime);
+ return new TaskAssigneeDTO(page.getTotal(), handlers);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java
new file mode 100755
index 0000000..baefdf7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java
@@ -0,0 +1,149 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.SysTenant;
+import org.dromara.system.domain.SysTenantPackage;
+import org.dromara.system.domain.bo.SysTenantPackageBo;
+import org.dromara.system.domain.vo.SysTenantPackageVo;
+import org.dromara.system.mapper.SysTenantMapper;
+import org.dromara.system.mapper.SysTenantPackageMapper;
+import org.dromara.system.service.ISysTenantPackageService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 绉熸埛濂楅Service涓氬姟灞傚鐞�
+ *
+ * @author Michelle.Chung
+ */
+@RequiredArgsConstructor
+@Service
+public class SysTenantPackageServiceImpl implements ISysTenantPackageService {
+
+ private final SysTenantPackageMapper baseMapper;
+ private final SysTenantMapper tenantMapper;
+
+ /**
+ * 鏌ヨ绉熸埛濂楅
+ */
+ @Override
+ public SysTenantPackageVo queryById(Long packageId){
+ return baseMapper.selectVoById(packageId);
+ }
+
+ /**
+ * 鏌ヨ绉熸埛濂楅鍒楄〃
+ */
+ @Override
+ public TableDataInfo<SysTenantPackageVo> queryPageList(SysTenantPackageBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper<SysTenantPackage> lqw = buildQueryWrapper(bo);
+ Page<SysTenantPackageVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ @Override
+ public List<SysTenantPackageVo> selectList() {
+ return baseMapper.selectVoList(new LambdaQueryWrapper<SysTenantPackage>()
+ .eq(SysTenantPackage::getStatus, SystemConstants.NORMAL));
+ }
+
+ /**
+ * 鏌ヨ绉熸埛濂楅鍒楄〃
+ */
+ @Override
+ public List<SysTenantPackageVo> queryList(SysTenantPackageBo bo) {
+ LambdaQueryWrapper<SysTenantPackage> lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper<SysTenantPackage> buildQueryWrapper(SysTenantPackageBo bo) {
+ LambdaQueryWrapper<SysTenantPackage> lqw = Wrappers.lambdaQuery();
+ lqw.like(StringUtils.isNotBlank(bo.getPackageName()), SysTenantPackage::getPackageName, bo.getPackageName());
+ lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysTenantPackage::getStatus, bo.getStatus());
+ lqw.orderByAsc(SysTenantPackage::getPackageId);
+ return lqw;
+ }
+
+ /**
+ * 鏂板绉熸埛濂楅
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean insertByBo(SysTenantPackageBo bo) {
+ SysTenantPackage add = MapstructUtils.convert(bo, SysTenantPackage.class);
+ // 淇濆瓨鑿滃崟id
+ List<Long> menuIds = Arrays.asList(bo.getMenuIds());
+ add.setMenuIds(CollUtil.isNotEmpty(menuIds) ? StringUtils.joinComma(menuIds) : "");
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setPackageId(add.getPackageId());
+ }
+ return flag;
+ }
+
+ /**
+ * 淇敼绉熸埛濂楅
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean updateByBo(SysTenantPackageBo bo) {
+ SysTenantPackage update = MapstructUtils.convert(bo, SysTenantPackage.class);
+ // 淇濆瓨鑿滃崟id
+ List<Long> menuIds = Arrays.asList(bo.getMenuIds());
+ update.setMenuIds(CollUtil.isNotEmpty(menuIds) ? StringUtils.joinComma(menuIds) : "");
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 鏍¢獙濂楅鍚嶇О鏄惁鍞竴
+ */
+ @Override
+ public boolean checkPackageNameUnique(SysTenantPackageBo bo) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysTenantPackage>()
+ .eq(SysTenantPackage::getPackageName, bo.getPackageName())
+ .ne(ObjectUtil.isNotNull(bo.getPackageId()), SysTenantPackage::getPackageId, bo.getPackageId()));
+ return !exist;
+ }
+
+ /**
+ * 淇敼濂楅鐘舵��
+ *
+ * @param bo 濂楅淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public int updatePackageStatus(SysTenantPackageBo bo) {
+ SysTenantPackage tenantPackage = MapstructUtils.convert(bo, SysTenantPackage.class);
+ return baseMapper.updateById(tenantPackage);
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎绉熸埛濂楅
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+ if(isValid){
+ boolean exists = tenantMapper.exists(new LambdaQueryWrapper<SysTenant>().in(SysTenant::getPackageId, ids));
+ if (exists) {
+ throw new ServiceException("绉熸埛濂楅宸茶浣跨敤");
+ }
+ }
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java
new file mode 100755
index 0000000..efbb040
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java
@@ -0,0 +1,567 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.crypto.digest.BCrypt;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.constant.TenantConstants;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.WorkflowService;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.redis.utils.CacheUtils;
+import org.dromara.common.tenant.core.TenantEntity;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.domain.*;
+import org.dromara.system.domain.bo.SysTenantBo;
+import org.dromara.system.domain.vo.SysTenantVo;
+import org.dromara.system.mapper.*;
+import org.dromara.system.service.ISysTenantService;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
+/**
+ * 绉熸埛Service涓氬姟灞傚鐞�
+ *
+ * @author Michelle.Chung
+ */
+@RequiredArgsConstructor
+@Service
+public class SysTenantServiceImpl implements ISysTenantService {
+
+ private final SysTenantMapper baseMapper;
+ private final SysTenantPackageMapper tenantPackageMapper;
+ private final SysUserMapper userMapper;
+ private final SysDeptMapper deptMapper;
+ private final SysRoleMapper roleMapper;
+ private final SysRoleMenuMapper roleMenuMapper;
+ private final SysRoleDeptMapper roleDeptMapper;
+ private final SysUserRoleMapper userRoleMapper;
+ private final SysDictTypeMapper dictTypeMapper;
+ private final SysDictDataMapper dictDataMapper;
+ private final SysConfigMapper configMapper;
+
+ /**
+ * 鏌ヨ绉熸埛
+ */
+ @Override
+ public SysTenantVo queryById(Long id) {
+ return baseMapper.selectVoById(id);
+ }
+
+ /**
+ * 鍩轰簬绉熸埛ID鏌ヨ绉熸埛
+ */
+ @Cacheable(cacheNames = CacheNames.SYS_TENANT, key = "#tenantId")
+ @Override
+ public SysTenantVo queryByTenantId(String tenantId) {
+ return baseMapper.selectVoOne(new LambdaQueryWrapper<SysTenant>().eq(SysTenant::getTenantId, tenantId));
+ }
+
+ /**
+ * 鏌ヨ绉熸埛鍒楄〃
+ */
+ @Override
+ public TableDataInfo<SysTenantVo> queryPageList(SysTenantBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper<SysTenant> lqw = buildQueryWrapper(bo);
+ Page<SysTenantVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 鏌ヨ绉熸埛鍒楄〃
+ */
+ @Override
+ public List<SysTenantVo> queryList(SysTenantBo bo) {
+ LambdaQueryWrapper<SysTenant> lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper<SysTenant> buildQueryWrapper(SysTenantBo bo) {
+ LambdaQueryWrapper<SysTenant> lqw = Wrappers.lambdaQuery();
+ lqw.eq(StringUtils.isNotBlank(bo.getTenantId()), SysTenant::getTenantId, bo.getTenantId());
+ lqw.like(StringUtils.isNotBlank(bo.getContactUserName()), SysTenant::getContactUserName, bo.getContactUserName());
+ lqw.eq(StringUtils.isNotBlank(bo.getContactPhone()), SysTenant::getContactPhone, bo.getContactPhone());
+ lqw.like(StringUtils.isNotBlank(bo.getCompanyName()), SysTenant::getCompanyName, bo.getCompanyName());
+ lqw.eq(StringUtils.isNotBlank(bo.getLicenseNumber()), SysTenant::getLicenseNumber, bo.getLicenseNumber());
+ lqw.eq(StringUtils.isNotBlank(bo.getAddress()), SysTenant::getAddress, bo.getAddress());
+ lqw.eq(StringUtils.isNotBlank(bo.getIntro()), SysTenant::getIntro, bo.getIntro());
+ lqw.like(StringUtils.isNotBlank(bo.getDomain()), SysTenant::getDomain, bo.getDomain());
+ lqw.eq(bo.getPackageId() != null, SysTenant::getPackageId, bo.getPackageId());
+ lqw.eq(bo.getExpireTime() != null, SysTenant::getExpireTime, bo.getExpireTime());
+ lqw.eq(bo.getAccountCount() != null, SysTenant::getAccountCount, bo.getAccountCount());
+ lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysTenant::getStatus, bo.getStatus());
+ lqw.orderByAsc(SysTenant::getId);
+ return lqw;
+ }
+
+ /**
+ * 鏂板绉熸埛
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean insertByBo(SysTenantBo bo) {
+ SysTenant add = MapstructUtils.convert(bo, SysTenant.class);
+
+ // 鑾峰彇鎵�鏈夌鎴风紪鍙�
+ List<String> tenantIds = baseMapper.selectObjs(
+ new LambdaQueryWrapper<SysTenant>().select(SysTenant::getTenantId), x -> {
+ return Convert.toStr(x);
+ });
+ String tenantId = generateTenantId(tenantIds);
+ add.setTenantId(tenantId);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (!flag) {
+ throw new ServiceException("鍒涘缓绉熸埛澶辫触");
+ }
+ bo.setId(add.getId());
+
+ // 鏍规嵁濂楅鍒涘缓瑙掕壊
+ Long roleId = createTenantRole(tenantId, bo.getPackageId());
+
+ // 鍒涘缓閮ㄩ棬: 鍏徃鍚嶆槸閮ㄩ棬鍚嶇О
+ SysDept dept = new SysDept();
+ dept.setTenantId(tenantId);
+ dept.setDeptName(bo.getCompanyName());
+ dept.setParentId(Constants.TOP_PARENT_ID);
+ dept.setAncestors(Constants.TOP_PARENT_ID.toString());
+ deptMapper.insert(dept);
+ Long deptId = dept.getDeptId();
+
+ // 瑙掕壊鍜岄儴闂ㄥ叧鑱旇〃
+ SysRoleDept roleDept = new SysRoleDept();
+ roleDept.setRoleId(roleId);
+ roleDept.setDeptId(deptId);
+ roleDeptMapper.insert(roleDept);
+
+ // 鍒涘缓绯荤粺鐢ㄦ埛
+ SysUser user = new SysUser();
+ user.setTenantId(tenantId);
+ user.setUserName(bo.getUsername());
+ user.setNickName(bo.getUsername());
+ user.setPassword(BCrypt.hashpw(bo.getPassword()));
+ user.setDeptId(deptId);
+ userMapper.insert(user);
+ //鏂板绯荤粺鐢ㄦ埛鍚庯紝榛樿褰撳墠鐢ㄦ埛涓洪儴闂ㄧ殑璐熻矗浜�
+ SysDept sd = new SysDept();
+ sd.setLeader(user.getUserId());
+ sd.setDeptId(deptId);
+ deptMapper.updateById(sd);
+
+ // 鐢ㄦ埛鍜岃鑹插叧鑱旇〃
+ SysUserRole userRole = new SysUserRole();
+ userRole.setUserId(user.getUserId());
+ userRole.setRoleId(roleId);
+ userRoleMapper.insert(userRole);
+
+ String defaultTenantId = TenantConstants.DEFAULT_TENANT_ID;
+ List<SysDictType> dictTypeList = dictTypeMapper.selectList(
+ new LambdaQueryWrapper<SysDictType>().eq(SysDictType::getTenantId, defaultTenantId));
+ List<SysDictData> dictDataList = dictDataMapper.selectList(
+ new LambdaQueryWrapper<SysDictData>().eq(SysDictData::getTenantId, defaultTenantId));
+ for (SysDictType dictType : dictTypeList) {
+ dictType.setDictId(null);
+ dictType.setTenantId(tenantId);
+ dictType.setCreateDept(null);
+ dictType.setCreateBy(null);
+ dictType.setCreateTime(null);
+ dictType.setUpdateBy(null);
+ dictType.setUpdateTime(null);
+ }
+ for (SysDictData dictData : dictDataList) {
+ dictData.setDictCode(null);
+ dictData.setTenantId(tenantId);
+ dictData.setCreateDept(null);
+ dictData.setCreateBy(null);
+ dictData.setCreateTime(null);
+ dictData.setUpdateBy(null);
+ dictData.setUpdateTime(null);
+ }
+ dictTypeMapper.insertBatch(dictTypeList);
+ dictDataMapper.insertBatch(dictDataList);
+
+ List<SysConfig> sysConfigList = configMapper.selectList(
+ new LambdaQueryWrapper<SysConfig>().eq(SysConfig::getTenantId, defaultTenantId));
+ for (SysConfig config : sysConfigList) {
+ config.setConfigId(null);
+ config.setTenantId(tenantId);
+ config.setCreateDept(null);
+ config.setCreateBy(null);
+ config.setCreateTime(null);
+ config.setUpdateBy(null);
+ config.setUpdateTime(null);
+ }
+ configMapper.insertBatch(sysConfigList);
+
+ // 鏈紑鍚伐浣滄祦涓嶆墽琛屼笅鏂规搷浣�
+ if (SpringUtils.getProperty("warm-flow.enabled", Boolean.class, false)) {
+ WorkflowService workflowService = SpringUtils.getBean(WorkflowService.class);
+ // 鏂板绉熸埛娴佺▼瀹氫箟
+ workflowService.syncDef(tenantId);
+ }
+ return true;
+ }
+
+ /**
+ * 鐢熸垚绉熸埛id
+ *
+ * @param tenantIds 宸叉湁绉熸埛id鍒楄〃
+ * @return 绉熸埛id
+ */
+ private String generateTenantId(List<String> tenantIds) {
+ // 闅忔満鐢熸垚6浣�
+ String numbers = RandomUtil.randomNumbers(6);
+ // 鍒ゆ柇鏄惁瀛樺湪锛屽鏋滃瓨鍦ㄥ垯閲嶆柊鐢熸垚
+ if (tenantIds.contains(numbers)) {
+ return generateTenantId(tenantIds);
+ }
+ return numbers;
+ }
+
+ /**
+ * 鏍规嵁绉熸埛鑿滃崟鍒涘缓绉熸埛瑙掕壊
+ *
+ * @param tenantId 绉熸埛缂栧彿
+ * @param packageId 绉熸埛濂楅id
+ * @return 瑙掕壊id
+ */
+ private Long createTenantRole(String tenantId, Long packageId) {
+ // 鑾峰彇绉熸埛濂楅
+ SysTenantPackage tenantPackage = tenantPackageMapper.selectById(packageId);
+ if (ObjectUtil.isNull(tenantPackage)) {
+ throw new ServiceException("濂楅涓嶅瓨鍦�");
+ }
+ // 鑾峰彇濂楅鑿滃崟id
+ List<Long> menuIds = StringUtils.splitTo(tenantPackage.getMenuIds(), Convert::toLong);
+
+ // 鍒涘缓瑙掕壊
+ SysRole role = new SysRole();
+ role.setTenantId(tenantId);
+ role.setRoleName(TenantConstants.TENANT_ADMIN_ROLE_NAME);
+ role.setRoleKey(TenantConstants.TENANT_ADMIN_ROLE_KEY);
+ role.setRoleSort(1);
+ role.setStatus(SystemConstants.NORMAL);
+ roleMapper.insert(role);
+ Long roleId = role.getRoleId();
+
+ // 鍒涘缓瑙掕壊鑿滃崟
+ List<SysRoleMenu> roleMenus = new ArrayList<>(menuIds.size());
+ menuIds.forEach(menuId -> {
+ SysRoleMenu roleMenu = new SysRoleMenu();
+ roleMenu.setRoleId(roleId);
+ roleMenu.setMenuId(menuId);
+ roleMenus.add(roleMenu);
+ });
+ roleMenuMapper.insertBatch(roleMenus);
+
+ return roleId;
+ }
+
+ /**
+ * 淇敼绉熸埛
+ */
+ @CacheEvict(cacheNames = CacheNames.SYS_TENANT, key = "#bo.tenantId")
+ @Override
+ public Boolean updateByBo(SysTenantBo bo) {
+ SysTenant tenant = MapstructUtils.convert(bo, SysTenant.class);
+ tenant.setTenantId(null);
+ tenant.setPackageId(null);
+ return baseMapper.updateById(tenant) > 0;
+ }
+
+ /**
+ * 淇敼绉熸埛鐘舵��
+ *
+ * @param bo 绉熸埛淇℃伅
+ * @return 缁撴灉
+ */
+ @CacheEvict(cacheNames = CacheNames.SYS_TENANT, key = "#bo.tenantId")
+ @Override
+ public int updateTenantStatus(SysTenantBo bo) {
+ SysTenant tenant = new SysTenant();
+ tenant.setId(bo.getId());
+ tenant.setStatus(bo.getStatus());
+ return baseMapper.updateById(tenant);
+ }
+
+ /**
+ * 鏍¢獙绉熸埛鏄惁鍏佽鎿嶄綔
+ *
+ * @param tenantId 绉熸埛ID
+ */
+ @Override
+ public void checkTenantAllowed(String tenantId) {
+ if (ObjectUtil.isNotNull(tenantId) && TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
+ throw new ServiceException("涓嶅厑璁告搷浣滅鐞嗙鎴�");
+ }
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎绉熸埛
+ */
+ @CacheEvict(cacheNames = CacheNames.SYS_TENANT, allEntries = true)
+ @Override
+ public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+ if (isValid) {
+ // 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+ if (ids.contains(TenantConstants.SUPER_ADMIN_ID)) {
+ throw new ServiceException("瓒呯绉熸埛涓嶈兘鍒犻櫎");
+ }
+ }
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+
+ /**
+ * 鏍¢獙浼佷笟鍚嶇О鏄惁鍞竴
+ */
+ @Override
+ public boolean checkCompanyNameUnique(SysTenantBo bo) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysTenant>()
+ .eq(SysTenant::getCompanyName, bo.getCompanyName())
+ .ne(ObjectUtil.isNotNull(bo.getTenantId()), SysTenant::getTenantId, bo.getTenantId()));
+ return !exist;
+ }
+
+ /**
+ * 鏍¢獙璐﹀彿浣欓
+ */
+ @Override
+ public boolean checkAccountBalance(String tenantId) {
+ SysTenantVo tenant = SpringUtils.getAopProxy(this).queryByTenantId(tenantId);
+ // 濡傛灉浣欓涓�-1浠h〃涓嶉檺鍒�
+ if (tenant.getAccountCount() == -1) {
+ return true;
+ }
+ Long userNumber = userMapper.selectCount(new LambdaQueryWrapper<>());
+ // 濡傛灉浣欓澶т簬0浠h〃杩樻湁鍙敤鍚嶉
+ return tenant.getAccountCount() - userNumber > 0;
+ }
+
+ /**
+ * 鏍¢獙鏈夋晥鏈�
+ */
+ @Override
+ public boolean checkExpireTime(String tenantId) {
+ SysTenantVo tenant = SpringUtils.getAopProxy(this).queryByTenantId(tenantId);
+ // 濡傛灉鏈缃繃鏈熸椂闂翠唬琛ㄤ笉闄愬埗
+ if (ObjectUtil.isNull(tenant.getExpireTime())) {
+ return true;
+ }
+ // 濡傛灉褰撳墠鏃堕棿鍦ㄨ繃鏈熸椂闂翠箣鍓嶅垯閫氳繃
+ return new Date().before(tenant.getExpireTime());
+ }
+
+ /**
+ * 鍚屾绉熸埛濂楅
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean syncTenantPackage(String tenantId, Long packageId) {
+ SysTenantPackage tenantPackage = tenantPackageMapper.selectById(packageId);
+ List<SysRole> roles = roleMapper.selectList(
+ new LambdaQueryWrapper<SysRole>().eq(SysRole::getTenantId, tenantId));
+ List<Long> roleIds = new ArrayList<>(roles.size() - 1);
+ List<Long> menuIds = StringUtils.splitTo(tenantPackage.getMenuIds(), Convert::toLong);
+ roles.forEach(item -> {
+ if (TenantConstants.TENANT_ADMIN_ROLE_KEY.equals(item.getRoleKey())) {
+ List<SysRoleMenu> roleMenus = new ArrayList<>(menuIds.size());
+ menuIds.forEach(menuId -> {
+ SysRoleMenu roleMenu = new SysRoleMenu();
+ roleMenu.setRoleId(item.getRoleId());
+ roleMenu.setMenuId(menuId);
+ roleMenus.add(roleMenu);
+ });
+ roleMenuMapper.delete(new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, item.getRoleId()));
+ roleMenuMapper.insertBatch(roleMenus);
+ } else {
+ roleIds.add(item.getRoleId());
+ }
+ });
+ if (!roleIds.isEmpty()) {
+ roleMenuMapper.delete(
+ new LambdaQueryWrapper<SysRoleMenu>().in(SysRoleMenu::getRoleId, roleIds).notIn(!menuIds.isEmpty(), SysRoleMenu::getMenuId, menuIds));
+ }
+ return true;
+ }
+
+ /**
+ * 鍚屾绉熸埛瀛楀吀
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void syncTenantDict() {
+ // 鏌ヨ瓒呯 鎵�鏈夊瓧鍏告暟鎹�
+ List<SysDictType> dictTypeList = new ArrayList<>();
+ List<SysDictData> dictDataList = new ArrayList<>();
+ TenantHelper.ignore(() -> {
+ dictTypeList.addAll(dictTypeMapper.selectList());
+ dictDataList.addAll(dictDataMapper.selectList());
+ });
+ // 鎵�鏈夌鎴峰瓧鍏哥被鍨�
+ Map<String, List<SysDictType>> dictTypeMap = StreamUtils.groupByKey(dictTypeList, TenantEntity::getTenantId);
+ // 鎵�鏈夌鎴峰瓧鍏告暟鎹�
+ Map<String, Map<String, List<SysDictData>>> dictDataMap = StreamUtils.groupBy2Key(dictDataList, TenantEntity::getTenantId, SysDictData::getDictType);
+
+ // 榛樿绉熸埛瀛楀吀绫诲瀷鍒楄〃
+ List<SysDictType> defaultDictTypeList = dictTypeMap.get(TenantConstants.DEFAULT_TENANT_ID);
+ // 榛樿绉熸埛瀛楀吀鏁版嵁
+ Map<String, List<SysDictData>> defaultDictDataMap = dictDataMap.get(TenantConstants.DEFAULT_TENANT_ID);
+
+ // 鑾峰彇鎵�鏈夌鎴风紪鍙�
+ List<String> tenantIds = baseMapper.selectObjs(
+ new LambdaQueryWrapper<SysTenant>().select(SysTenant::getTenantId)
+ .eq(SysTenant::getStatus, SystemConstants.NORMAL), x -> {
+ return Convert.toStr(x);
+ });
+ // 寰呭叆搴撶殑瀛楀吀绫诲瀷鍜屽瓧鍏告暟鎹�
+ List<SysDictType> saveTypeList = new ArrayList<>();
+ List<SysDictData> saveDataList = new ArrayList<>();
+ // 寰呭悓姝ョ殑绉熸埛缂栧彿锛堢敤浜庢竻闄ゅ浜庣鎴风殑瀛楀吀缂撳瓨锛�
+ Set<String> syncTenantIds = new HashSet<>();
+ // 寰幆鎵�鏈夌鎴凤紝澶勭悊闇�瑕佸悓姝ョ殑鏁版嵁
+ for (String tenantId : tenantIds) {
+ // 鎺掗櫎榛樿绉熸埛
+ if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
+ continue;
+ }
+ // 鏍规嵁榛樿绉熸埛鐨勫瓧鍏哥被鍨嬭繘琛屾暟鎹悓姝�
+ for (SysDictType dictType : defaultDictTypeList) {
+ // 鑾峰彇褰撳墠绉熸埛鐨勫瓧鍏哥被鍨嬪垪琛�
+ List<String> typeList = StreamUtils.toList(dictTypeMap.get(tenantId), SysDictType::getDictType);
+ // 鏍规嵁瀛楀吀绫诲瀷鑾峰彇榛樿绉熸埛鐨勫瓧鍏告暟鎹�
+ List<SysDictData> defaultDictDataList = defaultDictDataMap.get(dictType.getDictType());
+ // 鎺掗櫎涓嶉渶瑕佸悓姝ョ殑瀛楀吀鏁版嵁
+ Set<String> excludeDictDataSet = CollUtil.newHashSet();
+ // 澶勭悊 瀛樺湪type涓嶅瓨鍦╠ata 鐨勬儏鍐�
+ if (typeList.contains(dictType.getDictType())) {
+ // 鑾峰彇绉熸埛瀛楀吀鏁版嵁
+ Optional.ofNullable(dictDataMap.get(tenantId))
+ // 鑾峰彇绉熸埛褰撳墠瀛楀吀绫诲瀷鐨勫瓧鍏告暟鎹�
+ .map(tenantDictDataMap -> tenantDictDataMap.get(dictType.getDictType()))
+ // 淇濆瓨瀛楀吀鏁版嵁椤圭殑瀛楀吀閿�硷紝鐢ㄤ簬鍒ゆ柇鏁版嵁鏄惁闇�瑕佸悓姝�
+ .map(data -> StreamUtils.toSet(data, SysDictData::getDictValue))
+ // 娣诲姞鍒版帓闄ら泦鍚堜腑
+ .ifPresent(excludeDictDataSet::addAll);
+ } else {
+ // 鍚屾瀛楀吀绫诲瀷
+ SysDictType type = BeanUtil.toBean(dictType, SysDictType.class);
+ type.setDictId(null);
+ type.setTenantId(tenantId);
+ type.setCreateTime(null);
+ type.setUpdateTime(null);
+ syncTenantIds.add(tenantId);
+ saveTypeList.add(type);
+ }
+
+ // 榛樿绉熸埛瀛楀吀鏁版嵁涓嶄负绌哄啀鍘诲鐞�
+ if (CollUtil.isNotEmpty(defaultDictDataList)) {
+ // 鎻愬墠浼樺寲鎺掗櫎鍒ゆ柇if鏉′欢璇彞锛屽浜� && 骞惰仈鏉′欢锛岃浼樺寲鍙互閬垮厤涓嶅繀瑕佺殑 excludeDictDataSet.contains() 鍑芥暟璋冪敤
+ boolean isExclude = CollUtil.isNotEmpty(excludeDictDataSet);
+ // 绛涢�夊嚭 dictType 瀵瑰簲鐨� data
+ for (SysDictData dictData : defaultDictDataList) {
+ // 鎺掗櫎涓嶉渶瑕佸悓姝ョ殑瀛楀吀鏁版嵁
+ if (isExclude && excludeDictDataSet.contains(dictData.getDictValue())) {
+ continue;
+ }
+ SysDictData data = BeanUtil.toBean(dictData, SysDictData.class);
+ // 璁剧疆瀛楀吀缂栫爜涓� null
+ data.setDictCode(null);
+ data.setTenantId(tenantId);
+ data.setCreateTime(null);
+ data.setUpdateTime(null);
+ data.setCreateDept(null);
+ data.setCreateBy(null);
+ data.setUpdateBy(null);
+ syncTenantIds.add(tenantId);
+ saveDataList.add(data);
+ }
+ }
+ }
+ }
+ TenantHelper.ignore(() -> {
+ if (CollUtil.isNotEmpty(saveTypeList)) {
+ dictTypeMapper.insertBatch(saveTypeList);
+ }
+ if (CollUtil.isNotEmpty(saveDataList)) {
+ dictDataMapper.insertBatch(saveDataList);
+ }
+ });
+ for (String tenantId : syncTenantIds) {
+ TenantHelper.dynamic(tenantId, () -> CacheUtils.clear(CacheNames.SYS_DICT));
+ }
+ }
+
+ /**
+ * 鍚屾绉熸埛鍙傛暟閰嶇疆
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void syncTenantConfig() {
+ // 鏌ヨ瓒呯 鎵�鏈夊弬鏁伴厤缃�
+ List<SysConfig> configList = TenantHelper.ignore(() -> configMapper.selectList());
+
+ // 鎵�鏈夌鎴峰弬鏁伴厤缃�
+ Map<String, List<SysConfig>> configMap = StreamUtils.groupByKey(configList, TenantEntity::getTenantId);
+
+ // 榛樿绉熸埛瀛楀吀绫诲瀷鍒楄〃
+ List<SysConfig> defaultConfigList = configMap.get(TenantConstants.DEFAULT_TENANT_ID);
+
+ // 鑾峰彇鎵�鏈夌鎴风紪鍙�
+ List<String> tenantIds = baseMapper.selectObjs(
+ new LambdaQueryWrapper<SysTenant>().select(SysTenant::getTenantId)
+ .eq(SysTenant::getStatus, SystemConstants.NORMAL), x -> {
+ return Convert.toStr(x);
+ });
+ // 寰呭叆搴撶殑瀛楀吀绫诲瀷鍜屽瓧鍏告暟鎹�
+ List<SysConfig> saveConfigList = new ArrayList<>();
+ // 寰呭悓姝ョ殑绉熸埛缂栧彿锛堢敤浜庢竻闄ゅ浜庣鎴风殑瀛楀吀缂撳瓨锛�
+ Set<String> syncTenantIds = new HashSet<>();
+ // 寰幆鎵�鏈夌鎴凤紝澶勭悊闇�瑕佸悓姝ョ殑鏁版嵁
+ for (String tenantId : tenantIds) {
+ // 鎺掗櫎榛樿绉熸埛
+ if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
+ continue;
+ }
+ // 鏍规嵁榛樿绉熸埛鐨勫瓧鍏哥被鍨嬭繘琛屾暟鎹悓姝�
+ for (SysConfig config : defaultConfigList) {
+ // 鑾峰彇褰撳墠绉熸埛鐨勫瓧鍏哥被鍨嬪垪琛�
+ List<String> typeList = StreamUtils.toList(configMap.get(tenantId), SysConfig::getConfigKey);
+ if (!typeList.contains(config.getConfigKey())) {
+ SysConfig type = BeanUtil.toBean(config, SysConfig.class);
+ type.setConfigId(null);
+ type.setTenantId(tenantId);
+ type.setCreateTime(null);
+ type.setUpdateTime(null);
+ syncTenantIds.add(tenantId);
+ saveConfigList.add(type);
+ }
+ }
+ }
+ TenantHelper.ignore(() -> {
+ if (CollUtil.isNotEmpty(saveConfigList)) {
+ configMapper.insertBatch(saveConfigList);
+ }
+ });
+ for (String tenantId : syncTenantIds) {
+ TenantHelper.dynamic(tenantId, () -> CacheUtils.clear(CacheNames.SYS_CONFIG));
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java
new file mode 100755
index 0000000..39ad4cb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java
@@ -0,0 +1,772 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.dto.UserDTO;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.UserService;
+import org.dromara.common.core.utils.*;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.SysUserPost;
+import org.dromara.system.domain.SysUserRole;
+import org.dromara.system.domain.bo.SysUserBo;
+import org.dromara.system.domain.vo.SysPostVo;
+import org.dromara.system.domain.vo.SysRoleVo;
+import org.dromara.system.domain.vo.SysUserExportVo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.mapper.*;
+import org.dromara.system.service.ISysUserService;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
+/**
+ * 鐢ㄦ埛 涓氬姟灞傚鐞�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class SysUserServiceImpl implements ISysUserService, UserService {
+
+ private final SysUserMapper baseMapper;
+ private final SysDeptMapper deptMapper;
+ private final SysRoleMapper roleMapper;
+ private final SysPostMapper postMapper;
+ private final SysUserRoleMapper userRoleMapper;
+ private final SysUserPostMapper userPostMapper;
+
+ @Override
+ public TableDataInfo<SysUserVo> selectPageUserList(SysUserBo user, PageQuery pageQuery) {
+ Page<SysUserVo> page = baseMapper.selectPageUserList(pageQuery.build(), this.buildQueryWrapper(user));
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ鐢ㄦ埛鍒楄〃
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 鐢ㄦ埛淇℃伅闆嗗悎淇℃伅
+ */
+ @Override
+ public List<SysUserExportVo> selectUserExportList(SysUserBo user) {
+ Map<String, Object> params = user.getParams();
+ QueryWrapper<SysUser> wrapper = Wrappers.query();
+ wrapper.eq("u.del_flag", SystemConstants.NORMAL)
+ .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
+ .like(StringUtils.isNotBlank(user.getNickName()), "u.nick_name", user.getNickName())
+ .eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus())
+ .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber())
+ .between(params.get("beginTime") != null && params.get("endTime") != null,
+ "u.create_time", params.get("beginTime"), params.get("endTime"))
+ .and(ObjectUtil.isNotNull(user.getDeptId()), w -> {
+ List<Long> deptIds = deptMapper.selectDeptAndChildById(user.getDeptId());
+ w.in("u.dept_id", deptIds);
+ }).orderByAsc("u.user_id");
+ return baseMapper.selectUserExportList(wrapper);
+ }
+
+ private Wrapper<SysUser> buildQueryWrapper(SysUserBo user) {
+ Map<String, Object> params = user.getParams();
+ LambdaQueryWrapper<SysUser> wrapper = Wrappers.lambdaQuery();
+ wrapper.eq(SysUser::getDelFlag, SystemConstants.NORMAL)
+ .eq(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId())
+ .in(StringUtils.isNotBlank(user.getUserIds()), SysUser::getUserId, StringUtils.splitTo(user.getUserIds(), Convert::toLong))
+ .like(StringUtils.isNotBlank(user.getUserName()), SysUser::getUserName, user.getUserName())
+ .like(StringUtils.isNotBlank(user.getNickName()), SysUser::getNickName, user.getNickName())
+ .eq(StringUtils.isNotBlank(user.getStatus()), SysUser::getStatus, user.getStatus())
+ .like(StringUtils.isNotBlank(user.getPhonenumber()), SysUser::getPhonenumber, user.getPhonenumber())
+ .between(params.get("beginTime") != null && params.get("endTime") != null,
+ SysUser::getCreateTime, params.get("beginTime"), params.get("endTime"))
+ .and(ObjectUtil.isNotNull(user.getDeptId()), w -> {
+ List<Long> ids = deptMapper.selectDeptAndChildById(user.getDeptId());
+ w.in(SysUser::getDeptId, ids);
+ }).orderByAsc(SysUser::getUserId);
+ if (StringUtils.isNotBlank(user.getExcludeUserIds())) {
+ wrapper.notIn(SysUser::getUserId, StringUtils.splitTo(user.getExcludeUserIds(), Convert::toLong));
+ }
+ return wrapper;
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ宸插垎閰嶇敤鎴疯鑹插垪琛�
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 鐢ㄦ埛淇℃伅闆嗗悎淇℃伅
+ */
+ @Override
+ public TableDataInfo<SysUserVo> selectAllocatedList(SysUserBo user, PageQuery pageQuery) {
+ QueryWrapper<SysUser> wrapper = Wrappers.query();
+ wrapper.eq("u.del_flag", SystemConstants.NORMAL)
+ .eq(ObjectUtil.isNotNull(user.getRoleId()), "r.role_id", user.getRoleId())
+ .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
+ .eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus())
+ .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber())
+ .orderByAsc("u.user_id");
+ Page<SysUserVo> page = baseMapper.selectAllocatedList(pageQuery.build(), wrapper);
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏍规嵁鏉′欢鍒嗛〉鏌ヨ鏈垎閰嶇敤鎴疯鑹插垪琛�
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 鐢ㄦ埛淇℃伅闆嗗悎淇℃伅
+ */
+ @Override
+ public TableDataInfo<SysUserVo> selectUnallocatedList(SysUserBo user, PageQuery pageQuery) {
+ List<Long> userIds = userRoleMapper.selectUserIdsByRoleId(user.getRoleId());
+ QueryWrapper<SysUser> wrapper = Wrappers.query();
+ wrapper.eq("u.del_flag", SystemConstants.NORMAL)
+ .and(w -> w.ne("r.role_id", user.getRoleId()).or().isNull("r.role_id"))
+ .notIn(CollUtil.isNotEmpty(userIds), "u.user_id", userIds)
+ .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
+ .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber())
+ .orderByAsc("u.user_id");
+ Page<SysUserVo> page = baseMapper.selectUnallocatedList(pageQuery.build(), wrapper);
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 閫氳繃鐢ㄦ埛鍚嶆煡璇㈢敤鎴�
+ *
+ * @param userName 鐢ㄦ埛鍚�
+ * @return 鐢ㄦ埛瀵硅薄淇℃伅
+ */
+ @Override
+ public SysUserVo selectUserByUserName(String userName) {
+ return baseMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, userName));
+ }
+
+ /**
+ * 閫氳繃鎵嬫満鍙锋煡璇㈢敤鎴�
+ *
+ * @param phonenumber 鎵嬫満鍙�
+ * @return 鐢ㄦ埛瀵硅薄淇℃伅
+ */
+ @Override
+ public SysUserVo selectUserByPhonenumber(String phonenumber) {
+ return baseMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
+ }
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鐢ㄦ埛瀵硅薄淇℃伅
+ */
+ @Override
+ public SysUserVo selectUserById(Long userId) {
+ SysUserVo user = baseMapper.selectVoById(userId);
+ if (ObjectUtil.isNull(user)) {
+ return user;
+ }
+ user.setRoles(roleMapper.selectRolesByUserId(user.getUserId()));
+ return user;
+ }
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID涓叉煡璇㈢敤鎴�
+ *
+ * @param userIds 鐢ㄦ埛ID涓�
+ * @param deptId 閮ㄩ棬id
+ * @return 鐢ㄦ埛鍒楄〃淇℃伅
+ */
+ @Override
+ public List<SysUserVo> selectUserByIds(List<Long> userIds, Long deptId) {
+ return baseMapper.selectUserList(new LambdaQueryWrapper<SysUser>()
+ .select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName)
+ .eq(SysUser::getStatus, SystemConstants.NORMAL)
+ .eq(ObjectUtil.isNotNull(deptId), SysUser::getDeptId, deptId)
+ .in(CollUtil.isNotEmpty(userIds), SysUser::getUserId, userIds));
+ }
+
+ /**
+ * 鏌ヨ鐢ㄦ埛鎵�灞炶鑹茬粍
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 缁撴灉
+ */
+ @Override
+ public String selectUserRoleGroup(Long userId) {
+ List<SysRoleVo> list = roleMapper.selectRolesByUserId(userId);
+ if (CollUtil.isEmpty(list)) {
+ return StringUtils.EMPTY;
+ }
+ return StreamUtils.join(list, SysRoleVo::getRoleName);
+ }
+
+ /**
+ * 鏌ヨ鐢ㄦ埛鎵�灞炲矖浣嶇粍
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 缁撴灉
+ */
+ @Override
+ public String selectUserPostGroup(Long userId) {
+ List<SysPostVo> list = postMapper.selectPostsByUserId(userId);
+ if (CollUtil.isEmpty(list)) {
+ return StringUtils.EMPTY;
+ }
+ return StreamUtils.join(list, SysPostVo::getPostName);
+ }
+
+ /**
+ * 鏍¢獙鐢ㄦ埛鍚嶇О鏄惁鍞竴
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkUserNameUnique(SysUserBo user) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysUser>()
+ .eq(SysUser::getUserName, user.getUserName())
+ .ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
+ return !exist;
+ }
+
+ /**
+ * 鏍¢獙鎵嬫満鍙风爜鏄惁鍞竴
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ */
+ @Override
+ public boolean checkPhoneUnique(SysUserBo user) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysUser>()
+ .eq(SysUser::getPhonenumber, user.getPhonenumber())
+ .ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
+ return !exist;
+ }
+
+ /**
+ * 鏍¢獙email鏄惁鍞竴
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ */
+ @Override
+ public boolean checkEmailUnique(SysUserBo user) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysUser>()
+ .eq(SysUser::getEmail, user.getEmail())
+ .ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
+ return !exist;
+ }
+
+ /**
+ * 鏍¢獙鐢ㄦ埛鏄惁鍏佽鎿嶄綔
+ *
+ * @param userId 鐢ㄦ埛ID
+ */
+ @Override
+ public void checkUserAllowed(Long userId) {
+ if (ObjectUtil.isNotNull(userId) && LoginHelper.isSuperAdmin(userId)) {
+ throw new ServiceException("涓嶅厑璁告搷浣滆秴绾х鐞嗗憳鐢ㄦ埛");
+ }
+ }
+
+ /**
+ * 鏍¢獙鐢ㄦ埛鏄惁鏈夋暟鎹潈闄�
+ *
+ * @param userId 鐢ㄦ埛id
+ */
+ @Override
+ public void checkUserDataScope(Long userId) {
+ if (ObjectUtil.isNull(userId)) {
+ return;
+ }
+ if (LoginHelper.isSuperAdmin()) {
+ return;
+ }
+ if (baseMapper.countUserById(userId) == 0) {
+ throw new ServiceException("娌℃湁鏉冮檺璁块棶鐢ㄦ埛鏁版嵁锛�");
+ }
+ }
+
+ /**
+ * 鏂板淇濆瓨鐢ㄦ埛淇℃伅
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int insertUser(SysUserBo user) {
+ SysUser sysUser = MapstructUtils.convert(user, SysUser.class);
+ // 鏂板鐢ㄦ埛淇℃伅
+ int rows = baseMapper.insert(sysUser);
+ user.setUserId(sysUser.getUserId());
+ // 鏂板鐢ㄦ埛宀椾綅鍏宠仈
+ insertUserPost(user, false);
+ // 鏂板鐢ㄦ埛涓庤鑹茬鐞�
+ insertUserRole(user, false);
+ return rows;
+ }
+
+ /**
+ * 娉ㄥ唽鐢ㄦ埛淇℃伅
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean registerUser(SysUserBo user, String tenantId) {
+ user.setCreateBy(0L);
+ user.setUpdateBy(0L);
+ SysUser sysUser = MapstructUtils.convert(user, SysUser.class);
+ sysUser.setTenantId(tenantId);
+ return baseMapper.insert(sysUser) > 0;
+ }
+
+ /**
+ * 淇敼淇濆瓨鐢ㄦ埛淇℃伅
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ @CacheEvict(cacheNames = CacheNames.SYS_NICKNAME, key = "#user.userId")
+ @Transactional(rollbackFor = Exception.class)
+ public int updateUser(SysUserBo user) {
+ // 鏂板鐢ㄦ埛涓庤鑹茬鐞�
+ insertUserRole(user, true);
+ // 鏂板鐢ㄦ埛涓庡矖浣嶇鐞�
+ insertUserPost(user, true);
+ SysUser sysUser = MapstructUtils.convert(user, SysUser.class);
+ // 闃叉閿欒鏇存柊鍚庡鑷寸殑鏁版嵁璇垹闄�
+ int flag = baseMapper.updateById(sysUser);
+ if (flag < 1) {
+ throw new ServiceException("淇敼鐢ㄦ埛{}淇℃伅澶辫触", user.getUserName());
+ }
+ return flag;
+ }
+
+ /**
+ * 鐢ㄦ埛鎺堟潈瑙掕壊
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @param roleIds 瑙掕壊缁�
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void insertUserAuth(Long userId, Long[] roleIds) {
+ insertUserRole(userId, roleIds, true);
+ }
+
+ /**
+ * 淇敼鐢ㄦ埛鐘舵��
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @param status 璐﹀彿鐘舵��
+ * @return 缁撴灉
+ */
+ @Override
+ public int updateUserStatus(Long userId, String status) {
+ return baseMapper.update(null,
+ new LambdaUpdateWrapper<SysUser>()
+ .set(SysUser::getStatus, status)
+ .eq(SysUser::getUserId, userId));
+ }
+
+ /**
+ * 淇敼鐢ㄦ埛鍩烘湰淇℃伅
+ *
+ * @param user 鐢ㄦ埛淇℃伅
+ * @return 缁撴灉
+ */
+ @CacheEvict(cacheNames = CacheNames.SYS_NICKNAME, key = "#user.userId")
+ @Override
+ public int updateUserProfile(SysUserBo user) {
+ return baseMapper.update(null,
+ new LambdaUpdateWrapper<SysUser>()
+ .set(ObjectUtil.isNotNull(user.getNickName()), SysUser::getNickName, user.getNickName())
+ .set(SysUser::getPhonenumber, user.getPhonenumber())
+ .set(SysUser::getEmail, user.getEmail())
+ .set(SysUser::getSex, user.getSex())
+ .eq(SysUser::getUserId, user.getUserId()));
+ }
+
+ /**
+ * 淇敼鐢ㄦ埛澶村儚
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @param avatar 澶村儚鍦板潃
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean updateUserAvatar(Long userId, Long avatar) {
+ return baseMapper.update(null,
+ new LambdaUpdateWrapper<SysUser>()
+ .set(SysUser::getAvatar, avatar)
+ .eq(SysUser::getUserId, userId)) > 0;
+ }
+
+ /**
+ * 閲嶇疆鐢ㄦ埛瀵嗙爜
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @param password 瀵嗙爜
+ * @return 缁撴灉
+ */
+ @Override
+ public int resetUserPwd(Long userId, String password) {
+ return baseMapper.update(null,
+ new LambdaUpdateWrapper<SysUser>()
+ .set(SysUser::getPassword, password)
+ .eq(SysUser::getUserId, userId));
+ }
+
+ /**
+ * 鏂板鐢ㄦ埛瑙掕壊淇℃伅
+ *
+ * @param user 鐢ㄦ埛瀵硅薄
+ * @param clear 娓呴櫎宸插瓨鍦ㄧ殑鍏宠仈鏁版嵁
+ */
+ private void insertUserRole(SysUserBo user, boolean clear) {
+ this.insertUserRole(user.getUserId(), user.getRoleIds(), clear);
+ }
+
+ /**
+ * 鏂板鐢ㄦ埛宀椾綅淇℃伅
+ *
+ * @param user 鐢ㄦ埛瀵硅薄
+ * @param clear 娓呴櫎宸插瓨鍦ㄧ殑鍏宠仈鏁版嵁
+ */
+ private void insertUserPost(SysUserBo user, boolean clear) {
+ Long[] postIdArr = user.getPostIds();
+ if (ArrayUtil.isEmpty(postIdArr)) {
+ return;
+ }
+ List<Long> postIds = Arrays.asList(postIdArr);
+
+ // 鏍¢獙鏄惁鏈夋潈闄愭搷浣滆繖浜涘矖浣嶏紙鍚暟鎹潈闄愭帶鍒讹級
+ if (postMapper.selectPostCount(postIds) != postIds.size()) {
+ throw new ServiceException("娌℃湁鏉冮檺璁块棶宀椾綅鐨勬暟鎹�");
+ }
+
+ // 鏄惁娓呴櫎鏃х殑鐢ㄦ埛宀椾綅缁戝畾
+ if (clear) {
+ userPostMapper.delete(new LambdaQueryWrapper<SysUserPost>().eq(SysUserPost::getUserId, user.getUserId()));
+ }
+
+ // 鏋勫缓鐢ㄦ埛宀椾綅鍏宠仈鍒楄〃骞舵壒閲忔彃鍏�
+ List<SysUserPost> list = StreamUtils.toList(postIds,
+ postId -> {
+ SysUserPost up = new SysUserPost();
+ up.setUserId(user.getUserId());
+ up.setPostId(postId);
+ return up;
+ });
+ userPostMapper.insertBatch(list);
+ }
+
+ /**
+ * 鏂板鐢ㄦ埛瑙掕壊淇℃伅
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @param roleIds 瑙掕壊缁�
+ * @param clear 娓呴櫎宸插瓨鍦ㄧ殑鍏宠仈鏁版嵁
+ */
+ private void insertUserRole(Long userId, Long[] roleIds, boolean clear) {
+ if (ArrayUtil.isEmpty(roleIds)) {
+ return;
+ }
+
+ List<Long> roleList = new ArrayList<>(Arrays.asList(roleIds));
+
+ // 闈炶秴绾х鐞嗗憳锛岀姝㈠寘鍚秴绾х鐞嗗憳瑙掕壊
+ if (!LoginHelper.isSuperAdmin(userId)) {
+ roleList.remove(SystemConstants.SUPER_ADMIN_ID);
+ }
+
+ // 鏍¢獙鏄惁鏈夋潈闄愯闂繖浜涜鑹诧紙鍚暟鎹潈闄愭帶鍒讹級
+ if (roleMapper.selectRoleCount(roleList) != roleList.size()) {
+ throw new ServiceException("娌℃湁鏉冮檺璁块棶瑙掕壊鐨勬暟鎹�");
+ }
+
+ // 鏄惁娓呴櫎鍘熸湁缁戝畾
+ if (clear) {
+ userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>().eq(SysUserRole::getUserId, userId));
+ }
+
+ // 鎵归噺鎻掑叆鐢ㄦ埛-瑙掕壊鍏宠仈
+ List<SysUserRole> list = StreamUtils.toList(roleList,
+ roleId -> {
+ SysUserRole ur = new SysUserRole();
+ ur.setUserId(userId);
+ ur.setRoleId(roleId);
+ return ur;
+ });
+ userRoleMapper.insertBatch(list);
+ }
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鍒犻櫎鐢ㄦ埛
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int deleteUserById(Long userId) {
+ // 鍒犻櫎鐢ㄦ埛涓庤鑹插叧鑱�
+ userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>().eq(SysUserRole::getUserId, userId));
+ // 鍒犻櫎鐢ㄦ埛涓庡矖浣嶈〃
+ userPostMapper.delete(new LambdaQueryWrapper<SysUserPost>().eq(SysUserPost::getUserId, userId));
+ // 闃叉鏇存柊澶辫触瀵艰嚧鐨勬暟鎹垹闄�
+ int flag = baseMapper.deleteById(userId);
+ if (flag < 1) {
+ throw new ServiceException("鍒犻櫎鐢ㄦ埛澶辫触!");
+ }
+ return flag;
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎鐢ㄦ埛淇℃伅
+ *
+ * @param userIds 闇�瑕佸垹闄ょ殑鐢ㄦ埛ID
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int deleteUserByIds(Long[] userIds) {
+ for (Long userId : userIds) {
+ checkUserAllowed(userId);
+ checkUserDataScope(userId);
+ }
+ List<Long> ids = List.of(userIds);
+ // 鍒犻櫎鐢ㄦ埛涓庤鑹插叧鑱�
+ userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>().in(SysUserRole::getUserId, ids));
+ // 鍒犻櫎鐢ㄦ埛涓庡矖浣嶈〃
+ userPostMapper.delete(new LambdaQueryWrapper<SysUserPost>().in(SysUserPost::getUserId, ids));
+ // 闃叉鏇存柊澶辫触瀵艰嚧鐨勬暟鎹垹闄�
+ int flag = baseMapper.deleteByIds(ids);
+ if (flag < 1) {
+ throw new ServiceException("鍒犻櫎鐢ㄦ埛澶辫触!");
+ }
+ return flag;
+ }
+
+ /**
+ * 閫氳繃閮ㄩ棬id鏌ヨ褰撳墠閮ㄩ棬鎵�鏈夌敤鎴�
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @return 鐢ㄦ埛淇℃伅闆嗗悎淇℃伅
+ */
+ @Override
+ public List<SysUserVo> selectUserListByDept(Long deptId) {
+ LambdaQueryWrapper<SysUser> lqw = Wrappers.lambdaQuery();
+ lqw.eq(SysUser::getDeptId, deptId);
+ lqw.orderByAsc(SysUser::getUserId);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛璐︽埛
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鐢ㄦ埛璐︽埛
+ */
+ @Cacheable(cacheNames = CacheNames.SYS_USER_NAME, key = "#userId")
+ @Override
+ public String selectUserNameById(Long userId) {
+ SysUser sysUser = baseMapper.selectOne(new LambdaQueryWrapper<SysUser>()
+ .select(SysUser::getUserName).eq(SysUser::getUserId, userId));
+ return ObjectUtils.notNullGetter(sysUser, SysUser::getUserName);
+ }
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛璐︽埛
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鐢ㄦ埛璐︽埛
+ */
+ @Override
+ @Cacheable(cacheNames = CacheNames.SYS_NICKNAME, key = "#userId")
+ public String selectNicknameById(Long userId) {
+ SysUser sysUser = baseMapper.selectOne(new LambdaQueryWrapper<SysUser>()
+ .select(SysUser::getNickName).eq(SysUser::getUserId, userId));
+ return ObjectUtils.notNullGetter(sysUser, SysUser::getNickName);
+ }
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛璐︽埛
+ *
+ * @param userIds 鐢ㄦ埛ID 澶氫釜鐢ㄩ�楀彿闅斿紑
+ * @return 鐢ㄦ埛璐︽埛
+ */
+ @Override
+ public String selectNicknameByIds(String userIds) {
+ List<String> list = new ArrayList<>();
+ for (Long id : StringUtils.splitTo(userIds, Convert::toLong)) {
+ String nickname = SpringUtils.getAopProxy(this).selectNicknameById(id);
+ if (StringUtils.isNotBlank(nickname)) {
+ list.add(nickname);
+ }
+ }
+ return StringUtils.joinComma(list);
+ }
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛鎵嬫満鍙�
+ *
+ * @param userId 鐢ㄦ埛id
+ * @return 鐢ㄦ埛鎵嬫満鍙�
+ */
+ @Override
+ public String selectPhonenumberById(Long userId) {
+ SysUser sysUser = baseMapper.selectOne(new LambdaQueryWrapper<SysUser>()
+ .select(SysUser::getPhonenumber).eq(SysUser::getUserId, userId));
+ return ObjectUtils.notNullGetter(sysUser, SysUser::getPhonenumber);
+ }
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛閭
+ *
+ * @param userId 鐢ㄦ埛id
+ * @return 鐢ㄦ埛閭
+ */
+ @Override
+ public String selectEmailById(Long userId) {
+ SysUser sysUser = baseMapper.selectOne(new LambdaQueryWrapper<SysUser>()
+ .select(SysUser::getEmail).eq(SysUser::getUserId, userId));
+ return ObjectUtils.notNullGetter(sysUser, SysUser::getEmail);
+ }
+
+ /**
+ * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛鍒楄〃
+ *
+ * @param userIds 鐢ㄦ埛ids
+ * @return 鐢ㄦ埛鍒楄〃
+ */
+ @Override
+ public List<UserDTO> selectListByIds(List<Long> userIds) {
+ if (CollUtil.isEmpty(userIds)) {
+ return List.of();
+ }
+ List<SysUserVo> list = baseMapper.selectVoList(new LambdaQueryWrapper<SysUser>()
+ .select(SysUser::getUserId, SysUser::getDeptId, SysUser::getUserName,
+ SysUser::getNickName, SysUser::getUserType, SysUser::getEmail,
+ SysUser::getPhonenumber, SysUser::getSex, SysUser::getStatus,
+ SysUser::getCreateTime)
+ .eq(SysUser::getStatus, SystemConstants.NORMAL)
+ .in(SysUser::getUserId, userIds));
+ return BeanUtil.copyToList(list, UserDTO.class);
+ }
+
+ /**
+ * 閫氳繃瑙掕壊ID鏌ヨ鐢ㄦ埛ID
+ *
+ * @param roleIds 瑙掕壊ids
+ * @return 鐢ㄦ埛ids
+ */
+ @Override
+ public List<Long> selectUserIdsByRoleIds(List<Long> roleIds) {
+ if (CollUtil.isEmpty(roleIds)) {
+ return List.of();
+ }
+ List<SysUserRole> userRoles = userRoleMapper.selectList(
+ new LambdaQueryWrapper<SysUserRole>().in(SysUserRole::getRoleId, roleIds));
+ return StreamUtils.toList(userRoles, SysUserRole::getUserId);
+ }
+
+ /**
+ * 閫氳繃瑙掕壊ID鏌ヨ鐢ㄦ埛
+ *
+ * @param roleIds 瑙掕壊ids
+ * @return 鐢ㄦ埛
+ */
+ @Override
+ public List<UserDTO> selectUsersByRoleIds(List<Long> roleIds) {
+ if (CollUtil.isEmpty(roleIds)) {
+ return List.of();
+ }
+
+ // 閫氳繃瑙掕壊ID鑾峰彇鐢ㄦ埛瑙掕壊淇℃伅
+ List<SysUserRole> userRoles = userRoleMapper.selectList(
+ new LambdaQueryWrapper<SysUserRole>().in(SysUserRole::getRoleId, roleIds));
+
+ // 鑾峰彇鐢ㄦ埛ID鍒楄〃
+ Set<Long> userIds = StreamUtils.toSet(userRoles, SysUserRole::getUserId);
+
+ return this.selectListByIds(new ArrayList<>(userIds));
+ }
+
+ /**
+ * 閫氳繃閮ㄩ棬ID鏌ヨ鐢ㄦ埛
+ *
+ * @param deptIds 閮ㄩ棬ids
+ * @return 鐢ㄦ埛
+ */
+ @Override
+ public List<UserDTO> selectUsersByDeptIds(List<Long> deptIds) {
+ if (CollUtil.isEmpty(deptIds)) {
+ return List.of();
+ }
+ List<SysUserVo> list = baseMapper.selectVoList(new LambdaQueryWrapper<SysUser>()
+ .select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName, SysUser::getEmail, SysUser::getPhonenumber)
+ .eq(SysUser::getStatus, SystemConstants.NORMAL)
+ .in(SysUser::getDeptId, deptIds));
+ return BeanUtil.copyToList(list, UserDTO.class);
+ }
+
+ /**
+ * 閫氳繃宀椾綅ID鏌ヨ鐢ㄦ埛
+ *
+ * @param postIds 宀椾綅ids
+ * @return 鐢ㄦ埛
+ */
+ @Override
+ public List<UserDTO> selectUsersByPostIds(List<Long> postIds) {
+ if (CollUtil.isEmpty(postIds)) {
+ return List.of();
+ }
+
+ // 閫氳繃宀椾綅ID鑾峰彇鐢ㄦ埛宀椾綅淇℃伅
+ List<SysUserPost> userPosts = userPostMapper.selectList(
+ new LambdaQueryWrapper<SysUserPost>().in(SysUserPost::getPostId, postIds));
+
+ // 鑾峰彇鐢ㄦ埛ID鍒楄〃
+ Set<Long> userIds = StreamUtils.toSet(userPosts, SysUserPost::getUserId);
+
+ return this.selectListByIds(new ArrayList<>(userIds));
+ }
+
+ /**
+ * 鏍规嵁鐢ㄦ埛 ID 鍒楄〃鏌ヨ鐢ㄦ埛鍚嶇О鏄犲皠鍏崇郴
+ *
+ * @param userIds 鐢ㄦ埛 ID 鍒楄〃
+ * @return Map锛屽叾涓� key 涓虹敤鎴� ID锛寁alue 涓哄搴旂殑鐢ㄦ埛鍚嶇О
+ */
+ @Override
+ public Map<Long, String> selectUserNamesByIds(List<Long> userIds) {
+ if (CollUtil.isEmpty(userIds)) {
+ return Collections.emptyMap();
+ }
+ List<SysUser> list = baseMapper.selectList(
+ new LambdaQueryWrapper<SysUser>()
+ .select(SysUser::getUserId, SysUser::getNickName)
+ .in(SysUser::getUserId, userIds)
+ );
+ return StreamUtils.toMap(list, SysUser::getUserId, SysUser::getNickName);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/package-info.md b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/package-info.md
new file mode 100755
index 0000000..c938b1e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/package-info.md
@@ -0,0 +1,3 @@
+java鍖呬娇鐢� `.` 鍒嗗壊 resource 鐩綍浣跨敤 `/` 鍒嗗壊
+<br>
+姝ゆ枃浠剁洰鐨� 闃叉鏂囦欢澶圭矘杩炴壘涓嶅埌 `xml` 鏂囦欢
\ No newline at end of file
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysClientMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysClientMapper.xml
new file mode 100755
index 0000000..fd150ad
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysClientMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysClientMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml
new file mode 100755
index 0000000..e542a10
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysConfigMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml
new file mode 100755
index 0000000..928ad27
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysDeptMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml
new file mode 100755
index 0000000..6bcce51
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysDictDataMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml
new file mode 100755
index 0000000..6975da4
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysDictTypeMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml
new file mode 100755
index 0000000..c64b551
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysLogininforMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml
new file mode 100755
index 0000000..9e78302
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysMenuMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml
new file mode 100755
index 0000000..43f494d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysNoticeMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml
new file mode 100755
index 0000000..5ef14ee
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysOperLogMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssConfigMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssConfigMapper.xml
new file mode 100755
index 0000000..8c2c080
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssConfigMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysOssConfigMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssMapper.xml
new file mode 100755
index 0000000..d9b25bd
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssMapper.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysOssMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml
new file mode 100755
index 0000000..a753500
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysPostMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml
new file mode 100755
index 0000000..1705bb2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysRoleDeptMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml
new file mode 100755
index 0000000..7f69e01
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysRoleMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml
new file mode 100755
index 0000000..f01dc5e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysRoleMenuMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysSocialMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysSocialMapper.xml
new file mode 100755
index 0000000..baa4b59
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysSocialMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysSocialMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysTenantMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysTenantMapper.xml
new file mode 100755
index 0000000..0d96e13
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysTenantMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysTenantMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysTenantPackageMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysTenantPackageMapper.xml
new file mode 100755
index 0000000..79cf4c5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysTenantPackageMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysTenantPackageMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
new file mode 100755
index 0000000..475c659
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysUserMapper">
+
+ <resultMap type="org.dromara.system.domain.vo.SysUserVo" id="SysUserResult">
+ <id property="userId" column="user_id"/>
+ </resultMap>
+ <resultMap type="org.dromara.system.domain.vo.SysUserExportVo" id="SysUserExportResult">
+ <id property="userId" column="user_id"/>
+ </resultMap>
+
+ <select id="selectUserExportList" resultMap="SysUserExportResult">
+ select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex,
+ u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
+ d.dept_name, d.leader, u1.user_name as leaderName
+ from sys_user u
+ left join sys_dept d on u.dept_id = d.dept_id
+ left join sys_user u1 on u1.user_id = d.leader
+ ${ew.getCustomSqlSegment}
+ </select>
+
+ <select id="selectAllocatedList" resultMap="SysUserResult">
+ select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time
+ from sys_user u
+ left join sys_dept d on u.dept_id = d.dept_id
+ left join sys_user_role sur on u.user_id = sur.user_id
+ left join sys_role r on r.role_id = sur.role_id
+ ${ew.getCustomSqlSegment}
+ </select>
+
+ <select id="selectUnallocatedList" resultMap="SysUserResult">
+ select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time
+ from sys_user u
+ left join sys_dept d on u.dept_id = d.dept_id
+ left join sys_user_role sur on u.user_id = sur.user_id
+ left join sys_role r on r.role_id = sur.role_id
+ ${ew.getCustomSqlSegment}
+ </select>
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml
new file mode 100755
index 0000000..e9f2496
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysUserPostMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml
new file mode 100755
index 0000000..6f7cedf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysUserRoleMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/.flattened-pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/.flattened-pom.xml
new file mode 100644
index 0000000..e4d995a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/.flattened-pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-modules</artifactId>
+ <version>5.5.3</version>
+ </parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-workflow</artifactId>
+ <version>5.5.3</version>
+ <description>宸ヤ綔娴佹ā鍧�</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sse</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mail</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sms</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-log</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-idempotent</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-excel</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-translation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-tenant</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-security</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara.warm</groupId>
+ <artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara.warm</groupId>
+ <artifactId>warm-flow-plugin-ui-sb-web</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/pom.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/pom.xml
new file mode 100755
index 0000000..d195faf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-modules</artifactId>
+ <version>${revision}</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>jar</packaging>
+ <artifactId>ruoyi-workflow</artifactId>
+
+ <description>
+ 宸ヤ綔娴佹ā鍧�
+ </description>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sse</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-doc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mail</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sms</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-log</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-idempotent</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-excel</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-translation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-tenant</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-security</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara.warm</groupId>
+ <artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dromara.warm</groupId>
+ <artifactId>warm-flow-plugin-ui-sb-web</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
+
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/ConditionalOnEnable.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/ConditionalOnEnable.java
new file mode 100755
index 0000000..e844398
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/ConditionalOnEnable.java
@@ -0,0 +1,29 @@
+package org.dromara.workflow.common;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 鑷畾涔夋潯浠舵敞瑙o紝鐢ㄤ簬鍩轰簬閰嶇疆鍚敤鎴栫鐢ㄧ壒瀹氬姛鑳�
+ * <p>
+ * 璇ユ敞瑙e彧浼氬湪閰嶇疆鏂囦欢涓� `warm-flow.enabled=true` 鏃讹紝鏍囨敞浜嗘娉ㄨВ鐨勭被鎴栨柟娉曟墠浼氳 Spring 瀹瑰櫒鍔犺浇
+ * <p>
+ * 绀轰緥閰嶇疆锛�
+ * <pre>
+ * warm-flow:
+ * enabled: true # 璁剧疆涓� true 鏃讹紝鍚敤宸ヤ綔娴佸姛鑳�
+ * </pre>
+ * <p>
+ * 浣跨敤姝ゆ敞瑙f椂锛屽彲浠ュ姩鎬佹帶鍒跺伐浣滄祦鍔熻兘鏄惁鍚敤锛岃�屼笉闇�瑕佷慨鏀逛唬鐮侀�昏緫
+ *
+ * @author Lion Li
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@ConditionalOnProperty(value = "warm-flow.enabled", havingValue = "true")
+public @interface ConditionalOnEnable {
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java
new file mode 100755
index 0000000..88372f0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java
@@ -0,0 +1,111 @@
+package org.dromara.workflow.common.constant;
+
+
+/**
+ * 宸ヤ綔娴佸父閲�
+ *
+ * @author may
+ */
+public interface FlowConstant {
+
+ /**
+ * 娴佺▼鍙戣捣浜�
+ */
+ String INITIATOR = "initiator";
+
+ /**
+ * 涓氬姟id
+ */
+ String BUSINESS_ID = "businessId";
+
+ /**
+ * 閮ㄩ棬id
+ */
+ String INITIATOR_DEPT_ID = "initiatorDeptId";
+
+ /**
+ * 濮旀墭
+ */
+ String DELEGATE_TASK = "delegateTask";
+
+ /**
+ * 杞姙
+ */
+ String TRANSFER_TASK = "transferTask";
+
+ /**
+ * 鍔犵
+ */
+ String ADD_SIGNATURE = "addSignature";
+
+ /**
+ * 鍑忕
+ */
+ String REDUCTION_SIGNATURE = "reductionSignature";
+
+ /**
+ * 娴佺▼鍒嗙被Id杞悕绉�
+ */
+ String CATEGORY_ID_TO_NAME = "category_id_to_name";
+
+ /**
+ * 娴佺▼鍒嗙被鍚嶇О
+ */
+ String FLOW_CATEGORY_NAME = "flow_category_name#30d";
+
+ /**
+ * 榛樿绉熸埛OA鐢宠鍒嗙被id
+ */
+ Long FLOW_CATEGORY_ID = 100L;
+
+ /**
+ * 鏄惁涓虹敵璇蜂汉鎻愪氦甯搁噺
+ */
+ String SUBMIT = "submit";
+
+ /**
+ * 鎶勯�佸父閲�
+ */
+ String FLOW_COPY_LIST = "flowCopyList";
+
+ /**
+ * 娑堟伅绫诲瀷甯搁噺
+ */
+ String MESSAGE_TYPE = "messageType";
+
+ /**
+ * 娑堟伅閫氱煡甯搁噺
+ */
+ String MESSAGE_NOTICE = "messageNotice";
+
+ /**
+ * 浠诲姟鐘舵��
+ */
+ String WF_TASK_STATUS = "wf_task_status";
+
+ /**
+ * 鑷姩閫氳繃
+ */
+ String AUTO_PASS = "autoPass";
+
+ /**
+ * 涓氬姟缂栫爜
+ */
+ String BUSINESS_CODE = "businessCode";
+
+ /**
+ * 蹇界暐-鍔炵悊鏉冮檺鏍¢獙锛坱rue锛氬拷鐣ワ紝false锛氫笉蹇界暐锛�
+ */
+ String VAR_IGNORE = "ignore";
+
+ /**
+ * 蹇界暐-濮旀淳澶勭悊锛坱rue锛氬拷鐣ワ紝false锛氫笉蹇界暐锛�
+ */
+ String VAR_IGNORE_DEPUTE = "ignoreDepute";
+
+ /**
+ * 蹇界暐-浼氱绁ㄧ澶勭悊锛坱rue锛氬拷鐣ワ紝false锛氫笉蹇界暐锛�
+ */
+ String VAR_IGNORE_COOPERATE = "ignoreCooperate";
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java
new file mode 100755
index 0000000..3ad9cf9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java
@@ -0,0 +1,65 @@
+package org.dromara.workflow.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鎸夐挳鏉冮檺鏋氫妇
+ *
+ * @author AprilWind
+ */
+@Getter
+@AllArgsConstructor
+public enum ButtonPermissionEnum implements NodeExtEnum {
+
+ /**
+ * 鏄惁寮圭獥閫変汉
+ */
+ POP("鏄惁寮圭獥閫変汉", "pop", false),
+
+ /**
+ * 鏄惁鑳藉鎵�
+ */
+ TRUST("鏄惁鑳藉鎵�", "trust", false),
+
+ /**
+ * 鏄惁鑳借浆鍔�
+ */
+ TRANSFER("鏄惁鑳借浆鍔�", "transfer", false),
+
+ /**
+ * 鏄惁鑳芥妱閫�
+ */
+ COPY("鏄惁鑳芥妱閫�", "copy", true),
+
+ /**
+ * 鏄惁鏄剧ず閫�鍥�
+ */
+ BACK("鏄惁鏄剧ず閫�鍥�", "back", true),
+
+ /**
+ * 鏄惁鑳藉姞绛�
+ */
+ ADD_SIGN("鏄惁鑳藉姞绛�", "addSign", false),
+
+ /**
+ * 鏄惁鑳藉噺绛�
+ */
+ SUB_SIGN("鏄惁鑳藉噺绛�", "subSign", false),
+
+ /**
+ * 鏄惁鑳界粓姝�
+ */
+ TERMINATION("鏄惁鑳界粓姝�", "termination", true),
+
+ /**
+ * 鏄惁鑳戒笂浼犻檮浠�
+ */
+ FILE("鏄惁鑳戒笂浼犻檮浠�", "file", true);
+
+ private final String label;
+ private final String value;
+ private final boolean selected;
+
+}
+
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/CopySettingEnum.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/CopySettingEnum.java
new file mode 100755
index 0000000..a74af3b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/CopySettingEnum.java
@@ -0,0 +1,20 @@
+package org.dromara.workflow.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鎶勯�佽缃灇涓�
+ *
+ * @author AprilWind
+ */
+@Getter
+@AllArgsConstructor
+public enum CopySettingEnum implements NodeExtEnum {
+ ;
+ private final String label;
+ private final String value;
+ private final boolean selected;
+
+}
+
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java
new file mode 100755
index 0000000..0fe5cfe
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java
@@ -0,0 +1,53 @@
+package org.dromara.workflow.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 娑堟伅绫诲瀷鏋氫妇
+ *
+ * @author may
+ */
+@Getter
+@AllArgsConstructor
+public enum MessageTypeEnum {
+
+ /**
+ * 绔欏唴淇�
+ */
+ SYSTEM_MESSAGE("1", "绔欏唴淇�"),
+
+ /**
+ * 閭
+ */
+ EMAIL_MESSAGE("2", "閭"),
+
+ /**
+ * 鐭俊
+ */
+ SMS_MESSAGE("3", "鐭俊");
+
+ private final String code;
+
+ private final String desc;
+
+ private static final Map<String, MessageTypeEnum> MESSAGE_TYPE_ENUM_MAP = Arrays.stream(values())
+ .collect(Collectors.toConcurrentMap(MessageTypeEnum::getCode, Function.identity()));
+
+ /**
+ * 鏍规嵁娑堟伅绫诲瀷 code 鑾峰彇 MessageTypeEnum
+ *
+ * @param code 娑堟伅绫诲瀷code
+ * @return MessageTypeEnum
+ */
+ public static MessageTypeEnum getByCode(String code) {
+ return MESSAGE_TYPE_ENUM_MAP.getOrDefault(code, null);
+ }
+
+}
+
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java
new file mode 100755
index 0000000..9926a8e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java
@@ -0,0 +1,32 @@
+package org.dromara.workflow.common.enums;
+
+/**
+ * 鑺傜偣鎵╁睍灞炴�ф灇涓�
+ *
+ * @author AprilWind
+ */
+public interface NodeExtEnum {
+
+ /**
+ * 閫夐」label
+ *
+ * @return 閫夐」label
+ */
+ String getLabel();
+
+ /**
+ * 閫夐」鍊�
+ *
+ * @return 閫夐」鍊�
+ */
+ String getValue();
+
+ /**
+ * 鏄惁榛樿閫変腑
+ *
+ * @return 鏄惁榛樿閫変腑
+ */
+ boolean isSelected();
+
+}
+
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeEnum.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeEnum.java
new file mode 100755
index 0000000..fff2688
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeEnum.java
@@ -0,0 +1,140 @@
+package org.dromara.workflow.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 浠诲姟鍒嗛厤浜烘灇涓�
+ *
+ * @author AprilWind
+ */
+@Getter
+@AllArgsConstructor
+public enum TaskAssigneeEnum {
+
+ /**
+ * 鐢ㄦ埛
+ */
+ USER("鐢ㄦ埛", ""),
+
+ /**
+ * 瑙掕壊
+ */
+ ROLE("瑙掕壊", "role:"),
+
+ /**
+ * 閮ㄩ棬
+ */
+ DEPT("閮ㄩ棬", "dept:"),
+
+ /**
+ * 宀椾綅
+ */
+ POST("宀椾綅", "post:"),
+
+ /**
+ * SPEL琛ㄨ揪寮�
+ */
+ SPEL("SpEL琛ㄨ揪寮�", "");
+
+ private final String desc;
+ private final String code;
+
+ /**
+ * 鏍规嵁鎻忚堪鑾峰彇瀵瑰簲鐨勬灇涓剧被鍨�
+ * <p>
+ * 閫氳繃浼犲叆鎻忚堪锛屾煡鎵惧苟杩斿洖鍖归厤鐨勬灇涓鹃」銆傚鏋滄湭鎵惧埌鍖归厤椤癸紝浼氭姏鍑� {@link ServiceException}銆�
+ * </p>
+ *
+ * @param desc 鎻忚堪锛岀敤浜庡尮閰嶅搴旂殑鏋氫妇椤�
+ * @return TaskAssigneeEnum 杩斿洖瀵瑰簲鐨勬灇涓剧被鍨�
+ * @throws ServiceException 濡傛灉鏈壘鍒板尮閰嶇殑鏋氫妇椤�
+ */
+ public static TaskAssigneeEnum fromDesc(String desc) {
+ for (TaskAssigneeEnum type : values()) {
+ if (type.getDesc().equals(desc)) {
+ return type;
+ }
+ }
+ throw new ServiceException("鏈煡鐨勫姙鐞嗕汉绫诲瀷: " + desc);
+ }
+
+ /**
+ * 鏍规嵁浠g爜鑾峰彇瀵瑰簲鐨勬灇涓剧被鍨�
+ * <p>
+ * 閫氳繃浼犲叆浠g爜锛屾煡鎵惧苟杩斿洖鍖归厤鐨勬灇涓鹃」銆傚鏋滄湭鎵惧埌鍖归厤椤癸紝浼氭姏鍑� {@link ServiceException}銆�
+ * </p>
+ *
+ * @param code 浠g爜锛岀敤浜庡尮閰嶅搴旂殑鏋氫妇椤�
+ * @return TaskAssigneeEnum 杩斿洖瀵瑰簲鐨勬灇涓剧被鍨�
+ * @throws IllegalArgumentException 濡傛灉鏈壘鍒板尮閰嶇殑鏋氫妇椤�
+ */
+ public static TaskAssigneeEnum fromCode(String code) {
+ for (TaskAssigneeEnum type : values()) {
+ if (type.getCode().equals(code)) {
+ return type;
+ }
+ }
+ throw new ServiceException("鏈煡鐨勫姙鐞嗕汉绫诲瀷浠g爜: " + code);
+ }
+
+ /**
+ * 鑾峰彇鎵�鏈夊姙鐞嗕汉绫诲瀷鐨勬弿杩板垪琛�
+ * <p>
+ * 鑾峰彇褰撳墠鏋氫妇绫绘墍鏈夐」鐨勬弿杩板瓧娈靛垪琛紝閫氬父鐢ㄤ簬灞曠ず閫夋嫨椤广��
+ * </p>
+ *
+ * @return List<String> 杩斿洖鎵�鏈夊姙鐞嗕汉绫诲瀷鐨勬弿杩板垪琛�
+ */
+ public static List<String> getAssigneeTypeList() {
+ return Arrays.stream(values())
+ .map(TaskAssigneeEnum::getDesc)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 鑾峰彇鎵�鏈夊姙鐞嗕汉绫诲瀷鐨勪唬鐮佸垪琛�
+ * <p>
+ * 鑾峰彇褰撳墠鏋氫妇绫绘墍鏈夐」鐨勪唬鐮佸瓧娈靛垪琛紝閫氬父鐢ㄤ簬绋嬪簭鍐呴儴閫昏緫鐨勫垽鏂��
+ * </p>
+ *
+ * @return List<String> 杩斿洖鎵�鏈夊姙鐞嗕汉绫诲瀷鐨勪唬鐮佸垪琛�
+ */
+ public static List<String> getAssigneeCodeList() {
+ return Arrays.stream(values())
+ .map(TaskAssigneeEnum::getCode)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 鍒ゆ柇褰撳墠鍔炵悊浜虹被鍨嬫槸鍚﹂渶瑕佽皟鐢ㄩ儴闂ㄦ湇鍔★紙deptService锛�
+ *
+ * @return 濡傛灉绫诲瀷鏄� USER銆丏EPT 鎴� POST锛屽垯杩斿洖 true锛涘惁鍒欒繑鍥� false
+ */
+ public boolean needsDeptService() {
+ return this == USER || this == DEPT || this == POST;
+ }
+
+ /**
+ * 鍒ゆ柇缁欏畾瀛楃涓叉槸鍚︾鍚� SPEL 琛ㄨ揪寮忔牸寮忥紙浠� $ 鎴� # 寮�澶达級
+ *
+ * @param value 寰呭垽鏂瓧绗︿覆
+ * @return 鏄惁涓� SPEL 琛ㄨ揪寮�
+ */
+ public static boolean isSpelExpression(String value) {
+ if (value == null) {
+ return false;
+ }
+ // $鍓嶇紑琛ㄧず榛樿鍔炵悊浜哄彉閲忕瓥鐣�
+ // #鍓嶇紑琛ㄧずspel鍔炵悊浜哄彉閲忕瓥鐣�
+ return StringUtils.startsWith(value, "$") || StringUtils.startsWith(value, "#");
+ }
+
+}
+
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeType.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeType.java
new file mode 100755
index 0000000..eed1b91
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeType.java
@@ -0,0 +1,49 @@
+package org.dromara.workflow.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 浜哄憳绫诲瀷
+ *
+ * @author AprilWind
+ */
+@Getter
+@AllArgsConstructor
+public enum TaskAssigneeType {
+
+ /**
+ * 寰呭姙浠诲姟鐨勫鎵逛汉鏉冮檺
+ * <p>璇ユ潈闄愯〃绀虹敤鎴锋槸寰呭姙浠诲姟鐨勫鎵逛汉锛岃礋璐e鏍镐换鍔$殑鎵ц鎯呭喌銆�</p>
+ */
+ APPROVER("1", "寰呭姙浠诲姟鐨勫鎵逛汉鏉冮檺"),
+
+ /**
+ * 寰呭姙浠诲姟鐨勮浆鍔炰汉鏉冮檺
+ * <p>璇ユ潈闄愯〃绀虹敤鎴锋槸寰呭姙浠诲姟鐨勮浆鍔炰汉锛岃礋璐e皢浠诲姟鍒嗛厤缁欏叾浠栦汉鍛樸��</p>
+ */
+ TRANSFER("2", "寰呭姙浠诲姟鐨勮浆鍔炰汉鏉冮檺"),
+
+ /**
+ * 寰呭姙浠诲姟鐨勫鎵樹汉鏉冮檺
+ * <p>璇ユ潈闄愯〃绀虹敤鎴锋槸寰呭姙浠诲姟鐨勫鎵樹汉锛岃兘澶熷鎵樺叾浠栦汉浠d负澶勭悊浠诲姟銆�</p>
+ */
+ DELEGATE("3", "寰呭姙浠诲姟鐨勫鎵樹汉鏉冮檺"),
+
+ /**
+ * 寰呭姙浠诲姟鐨勬妱閫佷汉鏉冮檺
+ * <p>璇ユ潈闄愯〃绀虹敤鎴锋槸寰呭姙浠诲姟鐨勬妱閫佷汉锛屼粎鎺ユ敹浠诲姟淇℃伅鐨勯�氱煡锛屼笉鍙備笌浠诲姟鐨勫鎵规垨澶勭悊銆�</p>
+ */
+ COPY("4", "寰呭姙浠诲姟鐨勬妱閫佷汉鏉冮檺");
+
+ /**
+ * 绫诲瀷
+ */
+ private final String code;
+
+ /**
+ * 鎻忚堪
+ */
+ private final String description;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java
new file mode 100755
index 0000000..c7bced8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java
@@ -0,0 +1,114 @@
+package org.dromara.workflow.common.enums;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 浠诲姟鐘舵�佹灇涓�
+ *
+ * @author may
+ */
+@Getter
+@AllArgsConstructor
+public enum TaskStatusEnum {
+
+ /**
+ * 鎾ら攢
+ */
+ CANCEL("cancel", "鎾ら攢"),
+
+ /**
+ * 閫氳繃
+ */
+ PASS("pass", "閫氳繃"),
+
+ /**
+ * 寰呭鏍�
+ */
+ WAITING("waiting", "寰呭鏍�"),
+
+ /**
+ * 浣滃簾
+ */
+ INVALID("invalid", "浣滃簾"),
+
+ /**
+ * 閫�鍥�
+ */
+ BACK("back", "閫�鍥�"),
+
+ /**
+ * 缁堟
+ */
+ TERMINATION("termination", "缁堟"),
+
+ /**
+ * 杞姙
+ */
+ TRANSFER("transfer", "杞姙"),
+
+ /**
+ * 濮旀墭
+ */
+ DEPUTE("depute", "濮旀墭"),
+
+ /**
+ * 鎶勯��
+ */
+ COPY("copy", "鎶勯��"),
+
+ /**
+ * 鍔犵
+ */
+ SIGN("sign", "鍔犵"),
+
+ /**
+ * 鍑忕
+ */
+ SIGN_OFF("sign_off", "鍑忕"),
+
+ /**
+ * 瓒呮椂
+ */
+ TIMEOUT("timeout", "瓒呮椂");
+
+ /**
+ * 鐘舵��
+ */
+ private final String status;
+
+ /**
+ * 鎻忚堪
+ */
+ private final String desc;
+
+ private static final Map<String, String> STATUS_DESC_MAP = Arrays.stream(values())
+ .collect(Collectors.toConcurrentMap(TaskStatusEnum::getStatus, TaskStatusEnum::getDesc));
+
+ /**
+ * 浠诲姟涓氬姟鐘舵��
+ *
+ * @param status 鐘舵��
+ */
+ public static String findByStatus(String status) {
+ // 浠庣紦瀛樹腑鐩存帴鑾峰彇鎻忚堪
+ return STATUS_DESC_MAP.getOrDefault(status, StrUtil.EMPTY);
+ }
+
+ /**
+ * 鍒ゆ柇鐘舵�佹槸鍚︿负閫氳繃鎴栭��鍥�
+ *
+ * @param status 鐘舵�佸��
+ * @return true 琛ㄧず鏄�氳繃鎴栭��鍥炵姸鎬�
+ */
+ public static boolean isPassOrBack(String status) {
+ return PASS.getStatus().equals(status) || BACK.getStatus().equals(status);
+ }
+
+}
+
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/VariablesEnum.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/VariablesEnum.java
new file mode 100755
index 0000000..dbd54ed
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/VariablesEnum.java
@@ -0,0 +1,20 @@
+package org.dromara.workflow.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鍙橀噺鏋氫妇
+ *
+ * @author AprilWind
+ */
+@Getter
+@AllArgsConstructor
+public enum VariablesEnum implements NodeExtEnum {
+ ;
+ private final String label;
+ private final String value;
+ private final boolean selected;
+
+}
+
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/config/WarmFlowConfig.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/config/WarmFlowConfig.java
new file mode 100755
index 0000000..08f1808
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/config/WarmFlowConfig.java
@@ -0,0 +1,16 @@
+package org.dromara.workflow.config;
+
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * warmFlow閰嶇疆
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@Configuration
+public class WarmFlowConfig {
+
+}
+
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java
new file mode 100755
index 0000000..3007921
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java
@@ -0,0 +1,134 @@
+package org.dromara.workflow.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.lang.tree.Tree;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.domain.bo.FlowCategoryBo;
+import org.dromara.workflow.domain.vo.FlowCategoryVo;
+import org.dromara.workflow.service.IFlwCategoryService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 娴佺▼鍒嗙被
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/workflow/category")
+public class FlwCategoryController extends BaseController {
+
+ private final IFlwCategoryService flwCategoryService;
+
+ /**
+ * 鏌ヨ娴佺▼鍒嗙被鍒楄〃
+ */
+ @SaCheckPermission("workflow:category:list")
+ @GetMapping("/list")
+ public R<List<FlowCategoryVo>> list(FlowCategoryBo bo) {
+ List<FlowCategoryVo> list = flwCategoryService.queryList(bo);
+ return R.ok(list);
+ }
+
+ /**
+ * 瀵煎嚭娴佺▼鍒嗙被鍒楄〃
+ */
+ @SaCheckPermission("workflow:category:export")
+ @Log(title = "娴佺▼鍒嗙被", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(FlowCategoryBo bo, HttpServletResponse response) {
+ List<FlowCategoryVo> list = flwCategoryService.queryList(bo);
+ ExcelUtil.exportExcel(list, "娴佺▼鍒嗙被", FlowCategoryVo.class, response);
+ }
+
+ /**
+ * 鑾峰彇娴佺▼鍒嗙被璇︾粏淇℃伅
+ *
+ * @param categoryId 涓婚敭
+ */
+ @SaCheckPermission("workflow:category:query")
+ @GetMapping("/{categoryId}")
+ public R<FlowCategoryVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖") @PathVariable Long categoryId) {
+ return R.ok(flwCategoryService.queryById(categoryId));
+ }
+
+ /**
+ * 鏂板娴佺▼鍒嗙被
+ */
+ @SaCheckPermission("workflow:category:add")
+ @Log(title = "娴佺▼鍒嗙被", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R<Void> add(@Validated(AddGroup.class) @RequestBody FlowCategoryBo category) {
+ if (!flwCategoryService.checkCategoryNameUnique(category)) {
+ return R.fail("鏂板娴佺▼鍒嗙被'" + category.getCategoryName() + "'澶辫触锛屾祦绋嬪垎绫诲悕绉板凡瀛樺湪");
+ }
+ return toAjax(flwCategoryService.insertByBo(category));
+ }
+
+ /**
+ * 淇敼娴佺▼鍒嗙被
+ */
+ @SaCheckPermission("workflow:category:edit")
+ @Log(title = "娴佺▼鍒嗙被", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R<Void> edit(@Validated(EditGroup.class) @RequestBody FlowCategoryBo category) {
+ Long categoryId = category.getCategoryId();
+ if (!flwCategoryService.checkCategoryNameUnique(category)) {
+ return R.fail("淇敼娴佺▼鍒嗙被'" + category.getCategoryName() + "'澶辫触锛屾祦绋嬪垎绫诲悕绉板凡瀛樺湪");
+ } else if (category.getParentId().equals(categoryId)) {
+ return R.fail("淇敼娴佺▼鍒嗙被'" + category.getCategoryName() + "'澶辫触锛屼笂绾ф祦绋嬪垎绫讳笉鑳芥槸鑷繁");
+ }
+ return toAjax(flwCategoryService.updateByBo(category));
+ }
+
+ /**
+ * 鍒犻櫎娴佺▼鍒嗙被
+ *
+ * @param categoryId 涓婚敭
+ */
+ @SaCheckPermission("workflow:category:remove")
+ @Log(title = "娴佺▼鍒嗙被", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{categoryId}")
+ public R<Void> remove(@PathVariable Long categoryId) {
+ if (FlowConstant.FLOW_CATEGORY_ID.equals(categoryId)) {
+ return R.warn("榛樿娴佺▼鍒嗙被,涓嶅厑璁稿垹闄�");
+ }
+ if (flwCategoryService.hasChildByCategoryId(categoryId)) {
+ return R.warn("瀛樺湪涓嬬骇娴佺▼鍒嗙被,涓嶅厑璁稿垹闄�");
+ }
+ if (flwCategoryService.checkCategoryExistDefinition(categoryId)) {
+ return R.warn("娴佺▼鍒嗙被瀛樺湪娴佺▼瀹氫箟,涓嶅厑璁稿垹闄�");
+ }
+ return toAjax(flwCategoryService.deleteWithValidById(categoryId));
+ }
+
+ /**
+ * 鑾峰彇娴佺▼鍒嗙被鏍戝垪琛�
+ *
+ * @param categoryBo 娴佺▼鍒嗙被
+ */
+ @GetMapping("/categoryTree")
+ public R<List<Tree<String>>> categoryTree(FlowCategoryBo categoryBo) {
+ return R.ok(flwCategoryService.selectCategoryTreeList(categoryBo));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwDefinitionController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwDefinitionController.java
new file mode 100755
index 0000000..54e412a
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwDefinitionController.java
@@ -0,0 +1,195 @@
+package org.dromara.workflow.controller;
+
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.warm.flow.core.entity.Definition;
+import org.dromara.warm.flow.core.service.DefService;
+import org.dromara.warm.flow.orm.entity.FlowDefinition;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.vo.FlowDefinitionVo;
+import org.dromara.workflow.service.IFlwDefinitionService;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 娴佺▼瀹氫箟绠$悊 鎺у埗灞�
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/workflow/definition")
+public class FlwDefinitionController extends BaseController {
+
+ private final DefService defService;
+ private final IFlwDefinitionService flwDefinitionService;
+
+ /**
+ * 鏌ヨ娴佺▼瀹氫箟鍒楄〃
+ *
+ * @param flowDefinition 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ */
+ @GetMapping("/list")
+ public TableDataInfo<FlowDefinitionVo> list(FlowDefinition flowDefinition, PageQuery pageQuery) {
+ return flwDefinitionService.queryList(flowDefinition, pageQuery);
+ }
+
+ /**
+ * 鏌ヨ鏈彂甯冪殑娴佺▼瀹氫箟鍒楄〃
+ *
+ * @param flowDefinition 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ */
+ @GetMapping("/unPublishList")
+ public TableDataInfo<FlowDefinitionVo> unPublishList(FlowDefinition flowDefinition, PageQuery pageQuery) {
+ return flwDefinitionService.unPublishList(flowDefinition, pageQuery);
+ }
+
+ /**
+ * 鑾峰彇娴佺▼瀹氫箟璇︾粏淇℃伅
+ *
+ * @param id 娴佺▼瀹氫箟id
+ */
+ @GetMapping(value = "/{id}")
+ public R<Definition> getInfo(@PathVariable Long id) {
+ return R.ok(defService.getById(id));
+ }
+
+ /**
+ * 鏂板娴佺▼瀹氫箟
+ *
+ * @param flowDefinition 鍙傛暟
+ */
+ @Log(title = "娴佺▼瀹氫箟", businessType = BusinessType.INSERT)
+ @PostMapping
+ @RepeatSubmit()
+ @Transactional(rollbackFor = Exception.class)
+ public R<Boolean> add(@RequestBody FlowDefinition flowDefinition) {
+ return R.ok(defService.checkAndSave(flowDefinition));
+ }
+
+ /**
+ * 淇敼娴佺▼瀹氫箟
+ *
+ * @param flowDefinition 鍙傛暟
+ */
+ @Log(title = "娴佺▼瀹氫箟", businessType = BusinessType.UPDATE)
+ @PutMapping
+ @RepeatSubmit()
+ @Transactional(rollbackFor = Exception.class)
+ public R<Boolean> edit(@RequestBody FlowDefinition flowDefinition) {
+ return R.ok(defService.updateById(flowDefinition));
+ }
+
+ /**
+ * 鍙戝竷娴佺▼瀹氫箟
+ *
+ * @param id 娴佺▼瀹氫箟id
+ */
+ @Log(title = "娴佺▼瀹氫箟", businessType = BusinessType.INSERT)
+ @PutMapping("/publish/{id}")
+ @RepeatSubmit()
+ public R<Boolean> publish(@PathVariable Long id) {
+ return R.ok(flwDefinitionService.publish(id));
+ }
+
+ /**
+ * 鍙栨秷鍙戝竷娴佺▼瀹氫箟
+ *
+ * @param id 娴佺▼瀹氫箟id
+ */
+ @Log(title = "娴佺▼瀹氫箟", businessType = BusinessType.INSERT)
+ @PutMapping("/unPublish/{id}")
+ @RepeatSubmit()
+ @Transactional(rollbackFor = Exception.class)
+ public R<Boolean> unPublish(@PathVariable Long id) {
+ return R.ok(defService.unPublish(id));
+ }
+
+ /**
+ * 鍒犻櫎娴佺▼瀹氫箟
+ */
+ @Log(title = "娴佺▼瀹氫箟", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ public R<Void> remove(@PathVariable List<Long> ids) {
+ return toAjax(flwDefinitionService.removeDef(ids));
+ }
+
+ /**
+ * 澶嶅埗娴佺▼瀹氫箟
+ *
+ * @param id 娴佺▼瀹氫箟id
+ */
+ @Log(title = "娴佺▼瀹氫箟", businessType = BusinessType.INSERT)
+ @PostMapping("/copy/{id}")
+ @RepeatSubmit()
+ @Transactional(rollbackFor = Exception.class)
+ public R<Boolean> copy(@PathVariable Long id) {
+ return R.ok(defService.copyDef(id));
+ }
+
+ /**
+ * 瀵煎叆娴佺▼瀹氫箟
+ *
+ * @param file 鏂囦欢
+ * @param category 鍒嗙被
+ */
+ @Log(title = "娴佺▼瀹氫箟", businessType = BusinessType.IMPORT)
+ @PostMapping("/importDef")
+ public R<Boolean> importDef(MultipartFile file, String category) {
+ return R.ok(flwDefinitionService.importJson(file, category));
+ }
+
+ /**
+ * 瀵煎嚭娴佺▼瀹氫箟
+ *
+ * @param id 娴佺▼瀹氫箟id
+ * @param response 鍝嶅簲
+ * @throws IOException 寮傚父
+ */
+ @Log(title = "娴佺▼瀹氫箟", businessType = BusinessType.EXPORT)
+ @PostMapping("/exportDef/{id}")
+ public void exportDef(@PathVariable Long id, HttpServletResponse response) throws IOException {
+ flwDefinitionService.exportDef(id, response);
+ }
+
+ /**
+ * 鑾峰彇娴佺▼瀹氫箟JSON瀛楃涓�
+ *
+ * @param id 娴佺▼瀹氫箟id
+ */
+ @GetMapping("/xmlString/{id}")
+ public R<String> xmlString(@PathVariable Long id) {
+ return R.ok("鎿嶄綔鎴愬姛", defService.exportJson(id));
+ }
+
+ /**
+ * 婵�娲�/鎸傝捣娴佺▼瀹氫箟
+ *
+ * @param id 娴佺▼瀹氫箟id
+ * @param active 婵�娲�/鎸傝捣
+ */
+ @RepeatSubmit()
+ @PutMapping("/active/{id}")
+ @Transactional(rollbackFor = Exception.class)
+ @Log(title = "娴佺▼瀹氫箟", businessType = BusinessType.UPDATE)
+ public R<Boolean> active(@PathVariable Long id, @RequestParam boolean active) {
+ return R.ok(active ? defService.active(id) : defService.unActive(id));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java
new file mode 100755
index 0000000..a8a5fc6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java
@@ -0,0 +1,187 @@
+package org.dromara.workflow.controller;
+
+import cn.hutool.core.convert.Convert;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.warm.flow.core.service.InsService;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.bo.FlowCancelBo;
+import org.dromara.workflow.domain.bo.FlowInstanceBo;
+import org.dromara.workflow.domain.bo.FlowInvalidBo;
+import org.dromara.workflow.domain.bo.FlowVariableBo;
+import org.dromara.workflow.domain.vo.FlowInstanceVo;
+import org.dromara.workflow.service.IFlwInstanceService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 娴佺▼瀹炰緥绠$悊 鎺у埗灞�
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/workflow/instance")
+public class FlwInstanceController extends BaseController {
+
+ private final InsService insService;
+ private final IFlwInstanceService flwInstanceService;
+
+ /**
+ * 鏌ヨ姝e湪杩愯鐨勬祦绋嬪疄渚嬪垪琛�
+ *
+ * @param flowInstanceBo 娴佺▼瀹炰緥
+ * @param pageQuery 鍒嗛〉
+ */
+ @GetMapping("/pageByRunning")
+ public TableDataInfo<FlowInstanceVo> selectRunningInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
+ return flwInstanceService.selectRunningInstanceList(flowInstanceBo, pageQuery);
+ }
+
+ /**
+ * 鏌ヨ宸茬粨鏉熺殑娴佺▼瀹炰緥鍒楄〃
+ *
+ * @param flowInstanceBo 娴佺▼瀹炰緥
+ * @param pageQuery 鍒嗛〉
+ */
+ @GetMapping("/pageByFinish")
+ public TableDataInfo<FlowInstanceVo> selectFinishInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
+ return flwInstanceService.selectFinishInstanceList(flowInstanceBo, pageQuery);
+ }
+
+ /**
+ * 鏍规嵁涓氬姟id鏌ヨ娴佺▼瀹炰緥璇︾粏淇℃伅
+ *
+ * @param businessId 涓氬姟id
+ */
+ @GetMapping("/getInfo/{businessId}")
+ public R<FlowInstanceVo> getInfo(@PathVariable Long businessId) {
+ return R.ok(flwInstanceService.queryByBusinessId(businessId));
+ }
+
+ /**
+ * 鎸夌収涓氬姟id鍒犻櫎娴佺▼瀹炰緥
+ *
+ * @param businessIds 涓氬姟id
+ */
+ @DeleteMapping("/deleteByBusinessIds/{businessIds}")
+ @Log(title = "娴佺▼瀹炰緥绠$悊", businessType = BusinessType.DELETE)
+ public R<Void> deleteByBusinessIds(@PathVariable List<Long> businessIds) {
+ return toAjax(flwInstanceService.deleteByBusinessIds(StreamUtils.toList(businessIds, Convert::toStr)));
+ }
+
+ /**
+ * 鎸夌収瀹炰緥id鍒犻櫎娴佺▼瀹炰緥
+ *
+ * @param instanceIds 瀹炰緥id
+ */
+ @DeleteMapping("/deleteByInstanceIds/{instanceIds}")
+ @Log(title = "娴佺▼瀹炰緥绠$悊", businessType = BusinessType.DELETE)
+ public R<Void> deleteByInstanceIds(@PathVariable List<Long> instanceIds) {
+ return toAjax(flwInstanceService.deleteByInstanceIds(instanceIds));
+ }
+
+ /**
+ * 鎸夌収瀹炰緥id鍒犻櫎宸插畬鎴愬緱娴佺▼瀹炰緥
+ *
+ * @param instanceIds 瀹炰緥id
+ */
+ @DeleteMapping("/deleteHisByInstanceIds/{instanceIds}")
+ @Log(title = "娴佺▼瀹炰緥绠$悊", businessType = BusinessType.DELETE)
+ public R<Void> deleteHisByInstanceIds(@PathVariable List<Long> instanceIds) {
+ return toAjax(flwInstanceService.deleteHisByInstanceIds(instanceIds));
+ }
+
+ /**
+ * 鎾ら攢娴佺▼
+ *
+ * @param bo 鍙傛暟
+ */
+ @RepeatSubmit()
+ @PutMapping("/cancelProcessApply")
+ @Log(title = "娴佺▼瀹炰緥绠$悊", businessType = BusinessType.UPDATE)
+ public R<Void> cancelProcessApply(@RequestBody FlowCancelBo bo) {
+ return toAjax(flwInstanceService.cancelProcessApply(bo));
+ }
+
+ /**
+ * 婵�娲�/鎸傝捣娴佺▼瀹炰緥
+ *
+ * @param id 娴佺▼瀹炰緥id
+ * @param active 婵�娲�/鎸傝捣
+ */
+ @RepeatSubmit()
+ @PutMapping("/active/{id}")
+ @Log(title = "娴佺▼瀹炰緥绠$悊", businessType = BusinessType.UPDATE)
+ public R<Boolean> active(@PathVariable Long id, @RequestParam boolean active) {
+ return R.ok(active ? insService.active(id) : insService.unActive(id));
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鐧婚檰浜哄彂璧风殑娴佺▼瀹炰緥
+ *
+ * @param flowInstanceBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ */
+ @GetMapping("/pageByCurrent")
+ public TableDataInfo<FlowInstanceVo> selectCurrentInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
+ return flwInstanceService.selectCurrentInstanceList(flowInstanceBo, pageQuery);
+ }
+
+ /**
+ * 鑾峰彇娴佺▼鍥撅紝娴佺▼璁板綍
+ *
+ * @param businessId 涓氬姟id
+ */
+ @GetMapping("/flowHisTaskList/{businessId}")
+ public R<Map<String, Object>> flowHisTaskList(@PathVariable String businessId) {
+ return R.ok(flwInstanceService.flowHisTaskList(businessId));
+ }
+
+ /**
+ * 鑾峰彇娴佺▼鍙橀噺
+ *
+ * @param instanceId 娴佺▼瀹炰緥id
+ */
+ @GetMapping("/instanceVariable/{instanceId}")
+ public R<Map<String, Object>> instanceVariable(@PathVariable Long instanceId) {
+ return R.ok(flwInstanceService.instanceVariable(instanceId));
+ }
+
+ /**
+ * 淇敼娴佺▼鍙橀噺
+ *
+ * @param bo 鍙傛暟
+ */
+ @RepeatSubmit()
+ @PutMapping("/updateVariable")
+ @Log(title = "娴佺▼瀹炰緥绠$悊", businessType = BusinessType.UPDATE)
+ public R<Void> updateVariable(@Validated @RequestBody FlowVariableBo bo) {
+ return toAjax(flwInstanceService.updateVariable(bo));
+ }
+
+ /**
+ * 浣滃簾娴佺▼
+ *
+ * @param bo 鍙傛暟
+ */
+ @Log(title = "娴佺▼瀹炰緥绠$悊", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping("/invalid")
+ public R<Boolean> invalid(@Validated @RequestBody FlowInvalidBo bo) {
+ return R.ok(flwInstanceService.processInvalid(bo));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwSpelController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwSpelController.java
new file mode 100755
index 0000000..7d3932e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwSpelController.java
@@ -0,0 +1,93 @@
+package org.dromara.workflow.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.bo.FlowSpelBo;
+import org.dromara.workflow.domain.vo.FlowSpelVo;
+import org.dromara.workflow.service.IFlwSpelService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 娴佺▼spel琛ㄨ揪寮忓畾涔�
+ *
+ * @author Michelle.Chung
+ * @date 2025-07-04
+ */
+@ConditionalOnEnable
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/workflow/spel")
+public class FlwSpelController extends BaseController {
+
+ private final IFlwSpelService flwSpelService;
+
+ /**
+ * 鏌ヨ娴佺▼spel琛ㄨ揪寮忓畾涔夊垪琛�
+ */
+ @SaCheckPermission("workflow:spel:list")
+ @GetMapping("/list")
+ public TableDataInfo<FlowSpelVo> list(FlowSpelBo bo, PageQuery pageQuery) {
+ return flwSpelService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 鑾峰彇娴佺▼spel琛ㄨ揪寮忓畾涔夎缁嗕俊鎭�
+ *
+ * @param id 涓婚敭
+ */
+ @SaCheckPermission("workflow:spel:query")
+ @GetMapping("/{id}")
+ public R<FlowSpelVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖") @PathVariable Long id) {
+ return R.ok(flwSpelService.queryById(id));
+ }
+
+ /**
+ * 鏂板娴佺▼spel琛ㄨ揪寮忓畾涔�
+ */
+ @SaCheckPermission("workflow:spel:add")
+ @Log(title = "娴佺▼spel琛ㄨ揪寮忓畾涔�", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R<Void> add(@Validated(AddGroup.class) @RequestBody FlowSpelBo bo) {
+ return toAjax(flwSpelService.insertByBo(bo));
+ }
+
+ /**
+ * 淇敼娴佺▼spel琛ㄨ揪寮忓畾涔�
+ */
+ @SaCheckPermission("workflow:spel:edit")
+ @Log(title = "娴佺▼spel琛ㄨ揪寮忓畾涔�", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R<Void> edit(@Validated(EditGroup.class) @RequestBody FlowSpelBo bo) {
+ return toAjax(flwSpelService.updateByBo(bo));
+ }
+
+ /**
+ * 鍒犻櫎娴佺▼spel琛ㄨ揪寮忓畾涔�
+ *
+ * @param ids 涓婚敭涓�
+ */
+ @SaCheckPermission("workflow:spel:remove")
+ @Log(title = "娴佺▼spel琛ㄨ揪寮忓畾涔�", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖") @PathVariable Long[] ids) {
+ return toAjax(flwSpelService.deleteWithValidByIds(List.of(ids), true));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java
new file mode 100755
index 0000000..f372fbb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java
@@ -0,0 +1,225 @@
+package org.dromara.workflow.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.domain.dto.StartProcessReturnDTO;
+import org.dromara.common.core.domain.dto.UserDTO;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.warm.flow.core.entity.Node;
+import org.dromara.warm.flow.orm.entity.FlowNode;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.bo.*;
+import org.dromara.workflow.domain.vo.FlowHisTaskVo;
+import org.dromara.workflow.domain.vo.FlowTaskVo;
+import org.dromara.workflow.service.IFlwTaskService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 浠诲姟绠$悊 鎺у埗灞�
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/workflow/task")
+public class FlwTaskController extends BaseController {
+
+ private final IFlwTaskService flwTaskService;
+
+ /**
+ * 鍚姩浠诲姟
+ *
+ * @param startProcessBo 鍚姩娴佺▼鍙傛暟
+ */
+ @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping("/startWorkFlow")
+ public R<StartProcessReturnDTO> startWorkFlow(@Validated(AddGroup.class) @RequestBody StartProcessBo startProcessBo) {
+ StartProcessReturnDTO startProcessReturn = flwTaskService.startWorkFlow(startProcessBo);
+ return R.ok("鎻愪氦鎴愬姛", startProcessReturn);
+ }
+
+ /**
+ * 鍔炵悊浠诲姟
+ *
+ * @param completeTaskBo 鍔炵悊浠诲姟鍙傛暟
+ */
+ @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping("/completeTask")
+ public R<Void> completeTask(@Validated(AddGroup.class) @RequestBody CompleteTaskBo completeTaskBo) {
+ return toAjax(flwTaskService.completeTask(completeTaskBo));
+ }
+
+ /**
+ * 鏌ヨ褰撳墠鐢ㄦ埛鐨勫緟鍔炰换鍔�
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ */
+ @GetMapping("/pageByTaskWait")
+ public TableDataInfo<FlowTaskVo> pageByTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
+ return flwTaskService.pageByTaskWait(flowTaskBo, pageQuery);
+ }
+
+ /**
+ * 鏌ヨ褰撳墠鐢ㄦ埛鐨勫凡鍔炰换鍔�
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ */
+
+ @GetMapping("/pageByTaskFinish")
+ public TableDataInfo<FlowHisTaskVo> pageByTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
+ return flwTaskService.pageByTaskFinish(flowTaskBo, pageQuery);
+ }
+
+ /**
+ * 鏌ヨ寰呭姙浠诲姟
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ */
+ @GetMapping("/pageByAllTaskWait")
+ public TableDataInfo<FlowTaskVo> pageByAllTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
+ return flwTaskService.pageByAllTaskWait(flowTaskBo, pageQuery);
+ }
+
+ /**
+ * 鏌ヨ宸插姙浠诲姟
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ */
+ @GetMapping("/pageByAllTaskFinish")
+ public TableDataInfo<FlowHisTaskVo> pageByAllTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
+ return flwTaskService.pageByAllTaskFinish(flowTaskBo, pageQuery);
+ }
+
+ /**
+ * 鏌ヨ褰撳墠鐢ㄦ埛鐨勬妱閫�
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ */
+ @GetMapping("/pageByTaskCopy")
+ public TableDataInfo<FlowTaskVo> pageByTaskCopy(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
+ return flwTaskService.pageByTaskCopy(flowTaskBo, pageQuery);
+ }
+
+ /**
+ * 鏍规嵁taskId鏌ヨ浠h〃浠诲姟
+ *
+ * @param taskId 浠诲姟id
+ */
+ @GetMapping("/getTask/{taskId}")
+ public R<FlowTaskVo> getTask(@PathVariable Long taskId) {
+ return R.ok(flwTaskService.selectById(taskId));
+ }
+
+ /**
+ * 鑾峰彇涓嬩竴鑺傜偣淇℃伅
+ *
+ * @param bo 鍙傛暟
+ */
+ @PostMapping("/getNextNodeList")
+ public R<List<FlowNode>> getNextNodeList(@RequestBody FlowNextNodeBo bo) {
+ return R.ok(flwTaskService.getNextNodeList(bo));
+ }
+
+ /**
+ * 缁堟浠诲姟
+ *
+ * @param bo 鍙傛暟
+ */
+ @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping("/terminationTask")
+ public R<Boolean> terminationTask(@RequestBody FlowTerminationBo bo) {
+ return R.ok(flwTaskService.terminationTask(bo));
+ }
+
+ /**
+ * 浠诲姟鎿嶄綔
+ *
+ * @param bo 鍙傛暟
+ * @param taskOperation 鎿嶄綔绫诲瀷锛屽娲� delegateTask銆佽浆鍔� transferTask銆佸姞绛� addSignature銆佸噺绛� reductionSignature
+ */
+ @Log(title = "浠诲姟绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit
+ @PostMapping("/taskOperation/{taskOperation}")
+ public R<Void> taskOperation(@Validated @RequestBody TaskOperationBo bo, @PathVariable String taskOperation) {
+ return toAjax(flwTaskService.taskOperation(bo, taskOperation));
+ }
+
+ /**
+ * 淇敼浠诲姟鍔炵悊浜�
+ *
+ * @param taskIdList 浠诲姟id
+ * @param userId 鍔炵悊浜篿d
+ */
+ @Log(title = "浠诲姟绠$悊", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping("/updateAssignee/{userId}")
+ public R<Void> updateAssignee(@RequestBody List<Long> taskIdList, @PathVariable String userId) {
+ return toAjax(flwTaskService.updateAssignee(taskIdList, userId));
+ }
+
+ /**
+ * 椹冲洖瀹℃壒
+ *
+ * @param bo 鍙傛暟
+ */
+ @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping("/backProcess")
+ public R<Void> backProcess(@Validated({AddGroup.class}) @RequestBody BackProcessBo bo) {
+ return toAjax(flwTaskService.backProcess(bo));
+ }
+
+ /**
+ * 鑾峰彇鍙┏鍥炵殑鍓嶇疆鑺傜偣
+ *
+ * @param taskId 浠诲姟id
+ * @param nowNodeCode 褰撳墠鑺傜偣
+ */
+ @GetMapping("/getBackTaskNode/{taskId}/{nowNodeCode}")
+ public R<List<Node>> getBackTaskNode(@PathVariable Long taskId, @PathVariable String nowNodeCode) {
+ return R.ok(flwTaskService.getBackTaskNode(taskId, nowNodeCode));
+ }
+
+ /**
+ * 鑾峰彇褰撳墠浠诲姟鐨勬墍鏈夊姙鐞嗕汉
+ *
+ * @param taskId 浠诲姟id
+ */
+ @GetMapping("/currentTaskAllUser/{taskId}")
+ public R<List<UserDTO>> currentTaskAllUser(@PathVariable Long taskId) {
+ return R.ok(flwTaskService.currentTaskAllUser(List.of(taskId)));
+ }
+
+ /**
+ * 鍌姙浠诲姟
+ *
+ * @param bo 鍙傛暟
+ * @return 缁撴灉
+ */
+ @PostMapping("/urgeTask")
+ @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+ public R<Void> urgeTask(@RequestBody FlowUrgeTaskBo bo) {
+ return toAjax(flwTaskService.urgeTask(bo));
+ }
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java
new file mode 100755
index 0000000..39bb41f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java
@@ -0,0 +1,119 @@
+package org.dromara.workflow.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.bo.TestLeaveBo;
+import org.dromara.workflow.domain.vo.TestLeaveVo;
+import org.dromara.workflow.service.ITestLeaveService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 璇峰亣
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+@ConditionalOnEnable
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/workflow/leave")
+public class TestLeaveController extends BaseController {
+
+ private final ITestLeaveService testLeaveService;
+
+ /**
+ * 鏌ヨ璇峰亣鍒楄〃
+ */
+ @SaCheckPermission("workflow:leave:list")
+ @GetMapping("/list")
+ public TableDataInfo<TestLeaveVo> list(TestLeaveBo bo, PageQuery pageQuery) {
+ return testLeaveService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 瀵煎嚭璇峰亣鍒楄〃
+ */
+ @SaCheckPermission("workflow:leave:export")
+ @Log(title = "璇峰亣", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(TestLeaveBo bo, HttpServletResponse response) {
+ List<TestLeaveVo> list = testLeaveService.queryList(bo);
+ ExcelUtil.exportExcel(list, "璇峰亣", TestLeaveVo.class, response);
+ }
+
+ /**
+ * 鑾峰彇璇峰亣璇︾粏淇℃伅
+ *
+ * @param id 涓婚敭
+ */
+ @SaCheckPermission("workflow:leave:query")
+ @GetMapping("/{id}")
+ public R<TestLeaveVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long id) {
+ return R.ok(testLeaveService.queryById(id));
+ }
+
+ /**
+ * 鏂板璇峰亣
+ */
+ @SaCheckPermission("workflow:leave:add")
+ @Log(title = "璇峰亣", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R<TestLeaveVo> add(@Validated(AddGroup.class) @RequestBody TestLeaveBo bo) {
+ return R.ok(testLeaveService.insertByBo(bo));
+ }
+
+ /**
+ * 鎻愪氦璇峰亣骞舵彁浜ゆ祦绋�
+ */
+ @SaCheckPermission("workflow:leave:add")
+ @Log(title = "璇峰亣", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping("/submitAndFlowStart")
+ public R<TestLeaveVo> submitAndFlowStart(@Validated(AddGroup.class) @RequestBody TestLeaveBo bo) {
+ return R.ok(testLeaveService.submitAndFlowStart(bo));
+ }
+
+ /**
+ * 淇敼璇峰亣
+ */
+ @SaCheckPermission("workflow:leave:edit")
+ @Log(title = "璇峰亣", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R<TestLeaveVo> edit(@Validated(EditGroup.class) @RequestBody TestLeaveBo bo) {
+ return R.ok(testLeaveService.updateByBo(bo));
+ }
+
+ /**
+ * 鍒犻櫎璇峰亣
+ *
+ * @param ids 涓婚敭涓�
+ */
+ @SaCheckPermission("workflow:leave:remove")
+ @Log(title = "璇峰亣", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+ @PathVariable Long[] ids) {
+ return toAjax(testLeaveService.deleteWithValidByIds(List.of(ids)));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowCategory.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowCategory.java
new file mode 100755
index 0000000..28918f1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowCategory.java
@@ -0,0 +1,67 @@
+package org.dromara.workflow.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 娴佺▼鍒嗙被瀵硅薄 wf_category
+ *
+ * @author may
+ * @date 2023-06-27
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("flow_category")
+public class FlowCategory extends TenantEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 娴佺▼鍒嗙被ID
+ */
+ @TableId(value = "category_id")
+ private Long categoryId;
+
+ /**
+ * 鐖舵祦绋嬪垎绫籭d
+ */
+ private Long parentId;
+
+ /**
+ * 绁栫骇鍒楄〃
+ */
+ private String ancestors;
+
+ /**
+ * 娴佺▼鍒嗙被鍚嶇О
+ */
+ private String categoryName;
+
+ /**
+ * 鏄剧ず椤哄簭
+ */
+ private Long orderNum;
+
+ /**
+ * 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�
+ */
+ @TableLogic
+ private String delFlag;
+
+ /**
+ * 瀛愯彍鍗�
+ */
+ @TableField(exist = false)
+ private List<FlowCategory> children = new ArrayList<>();
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowInstanceBizExt.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowInstanceBizExt.java
new file mode 100755
index 0000000..932b038
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowInstanceBizExt.java
@@ -0,0 +1,59 @@
+package org.dromara.workflow.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+
+/**
+ * 娴佺▼瀹炰緥涓氬姟鎵╁睍瀵硅薄 flow_instance_biz_ext
+ *
+ * @author may
+ * @date 2025-08-05
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("flow_instance_biz_ext")
+public class FlowInstanceBizExt extends TenantEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭
+ */
+ @TableId(value = "id")
+ private Long id;
+
+ /**
+ * 娴佺▼瀹炰緥ID
+ */
+ private Long instanceId;
+
+ /**
+ * 涓氬姟ID
+ */
+ private String businessId;
+
+ /**
+ * 涓氬姟缂栫爜
+ */
+ private String businessCode;
+
+ /**
+ * 涓氬姟鏍囬
+ */
+ private String businessTitle;
+
+ /**
+ * 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�
+ */
+ @TableLogic
+ private String delFlag;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowSpel.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowSpel.java
new file mode 100755
index 0000000..89a7ae9
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowSpel.java
@@ -0,0 +1,69 @@
+package org.dromara.workflow.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.io.Serial;
+
+/**
+ * 娴佺▼spel琛ㄨ揪寮忓畾涔夊璞� flow_spel
+ *
+ * @author Michelle.Chung
+ * @date 2025-07-04
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("flow_spel")
+public class FlowSpel extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭id
+ */
+ @TableId(value = "id")
+ private Long id;
+
+ /**
+ * 缁勪欢鍚嶇О
+ */
+ private String componentName;
+
+ /**
+ * 鏂规硶鍚�
+ */
+ private String methodName;
+
+ /**
+ * 鍙傛暟
+ */
+ private String methodParams;
+
+ /**
+ * 棰勮spel琛ㄨ揪寮�
+ */
+ private String viewSpel;
+
+ /**
+ * 鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ private String status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鍒犻櫎鏍囧織
+ */
+ @TableLogic
+ private String delFlag;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java
new file mode 100755
index 0000000..b54873c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java
@@ -0,0 +1,68 @@
+package org.dromara.workflow.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * 璇峰亣瀵硅薄 test_leave
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("test_leave")
+public class TestLeave extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭
+ */
+ @TableId(value = "id")
+ private Long id;
+
+ /**
+ * 鐢宠缂栧彿
+ */
+ private String applyCode;
+
+ /**
+ * 璇峰亣绫诲瀷
+ */
+ private String leaveType;
+
+ /**
+ * 寮�濮嬫椂闂�
+ */
+ private Date startDate;
+
+ /**
+ * 缁撴潫鏃堕棿
+ */
+ private Date endDate;
+
+ /**
+ * 璇峰亣澶╂暟
+ */
+ private Integer leaveDays;
+
+ /**
+ * 璇峰亣鍘熷洜
+ */
+ private String remark;
+
+ /**
+ * 鐘舵��
+ */
+ private String status;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java
new file mode 100755
index 0000000..a67a1f7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java
@@ -0,0 +1,69 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+
+/**
+ * 椹冲洖鍙傛暟璇锋眰
+ *
+ * @author may
+ */
+@Data
+public class BackProcessBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 浠诲姟ID
+ */
+ @NotNull(message = "浠诲姟ID涓嶈兘涓虹┖", groups = AddGroup.class)
+ private Long taskId;
+
+ /**
+ * 闄勪欢id
+ */
+ private String fileId;
+
+ /**
+ * 娑堟伅绫诲瀷
+ */
+ private List<String> messageType;
+
+ /**
+ * 椹冲洖鐨勮妭鐐筰d(鐩墠鏈娇鐢紝鐩存帴椹冲洖鍒扮敵璇蜂汉)
+ */
+ private String nodeCode;
+
+ /**
+ * 鍔炵悊鎰忚
+ */
+ private String message;
+
+ /**
+ * 閫氱煡
+ */
+ private String notice;
+
+ /**
+ * 娴佺▼鍙橀噺
+ */
+ private Map<String, Object> variables;
+
+ public Map<String, Object> getVariables() {
+ if (variables == null) {
+ return new HashMap<>(16);
+ }
+ variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+ return variables;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java
new file mode 100755
index 0000000..e246293
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java
@@ -0,0 +1,85 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 鍔炵悊浠诲姟璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class CompleteTaskBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 浠诲姟id
+ */
+ @NotNull(message = "浠诲姟id涓嶈兘涓虹┖", groups = {AddGroup.class})
+ private Long taskId;
+
+ /**
+ * 闄勪欢id
+ */
+ private String fileId;
+
+ /**
+ * 鎶勯�佷汉鍛�
+ */
+ private List<FlowCopyBo> flowCopyList;
+
+ /**
+ * 娑堟伅绫诲瀷
+ */
+ private List<String> messageType;
+
+ /**
+ * 鍔炵悊鎰忚
+ */
+ private String message;
+
+ /**
+ * 娑堟伅閫氱煡
+ */
+ private String notice;
+
+ /**
+ * 鍔炵悊浜�(鍙笉濉� 鐢ㄤ簬瑕嗙洊褰撳墠鑺傜偣鍔炵悊浜�)
+ */
+ private String handler;
+
+ /**
+ * 娴佺▼鍙橀噺
+ */
+ private Map<String, Object> variables;
+
+ /**
+ * 寮圭獥閫夋嫨鐨勫姙鐞嗕汉
+ */
+ private Map<String, Object> assigneeMap;
+
+ /**
+ * 鎵╁睍鍙橀噺(姝ゅ涓洪�楀彿鍒嗛殧鐨刼ssId)
+ */
+ private String ext;
+
+ public Map<String, Object> getVariables() {
+ if (variables == null) {
+ variables = new HashMap<>(16);
+ return variables;
+ }
+ variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+ return variables;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCancelBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCancelBo.java
new file mode 100755
index 0000000..31742ea
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCancelBo.java
@@ -0,0 +1,31 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 鎾ら攢浠诲姟璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class FlowCancelBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 浠诲姟ID
+ */
+ @NotBlank(message = "涓氬姟ID涓嶈兘涓虹┖", groups = AddGroup.class)
+ private String businessId;
+
+ /**
+ * 鍔炵悊鎰忚
+ */
+ private String message;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCategoryBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCategoryBo.java
new file mode 100755
index 0000000..fd626eb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCategoryBo.java
@@ -0,0 +1,47 @@
+package org.dromara.workflow.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.workflow.domain.FlowCategory;
+
+/**
+ * 娴佺▼鍒嗙被涓氬姟瀵硅薄 wf_category
+ *
+ * @author may
+ * @date 2023-06-27
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = FlowCategory.class, reverseConvertGenerate = false)
+public class FlowCategoryBo extends BaseEntity {
+
+ /**
+ * 娴佺▼鍒嗙被ID
+ */
+ @NotNull(message = "娴佺▼鍒嗙被ID涓嶈兘涓虹┖", groups = { EditGroup.class })
+ private Long categoryId;
+
+ /**
+ * 鐖舵祦绋嬪垎绫籭d
+ */
+ @NotNull(message = "鐖舵祦绋嬪垎绫籭d涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+ private Long parentId;
+
+ /**
+ * 娴佺▼鍒嗙被鍚嶇О
+ */
+ @NotBlank(message = "娴佺▼鍒嗙被鍚嶇О涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+ private String categoryName;
+
+ /**
+ * 鏄剧ず椤哄簭
+ */
+ private Long orderNum;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCopyBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCopyBo.java
new file mode 100755
index 0000000..a45e521
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowCopyBo.java
@@ -0,0 +1,30 @@
+package org.dromara.workflow.domain.bo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 鎶勯��
+ *
+ * @author may
+ */
+@Data
+public class FlowCopyBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鐢ㄦ埛id
+ */
+ private Long userId;
+
+ /**
+ * 鐢ㄦ埛鍚嶇О
+ */
+ private String userName;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowInstanceBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowInstanceBo.java
new file mode 100755
index 0000000..bb1621f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowInstanceBo.java
@@ -0,0 +1,55 @@
+package org.dromara.workflow.domain.bo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 娴佺▼瀹炰緥璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class FlowInstanceBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 娴佺▼瀹氫箟鍚嶇О
+ */
+ private String flowName;
+
+ /**
+ * 娴佺▼瀹氫箟缂栫爜
+ */
+ private String flowCode;
+
+ /**
+ * 浠诲姟鍙戣捣浜�
+ */
+ private String startUserId;
+
+ /**
+ * 涓氬姟id
+ */
+ private String businessId;
+
+ /**
+ * 娴佺▼鍒嗙被id
+ */
+ private String category;
+
+ /**
+ * 浠诲姟鍚嶇О
+ */
+ private String nodeName;
+
+ /**
+ * 鐢宠浜篒ds
+ */
+ private List<String> createByIds;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowInvalidBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowInvalidBo.java
new file mode 100755
index 0000000..297bd00
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowInvalidBo.java
@@ -0,0 +1,31 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 浣滃簾璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class FlowInvalidBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 娴佺▼瀹炰緥id
+ */
+ @NotNull(message = "娴佺▼瀹炰緥id涓虹┖", groups = AddGroup.class)
+ private Long id;
+
+ /**
+ * 瀹℃壒鎰忚
+ */
+ private String comment;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java
new file mode 100755
index 0000000..12f0653
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java
@@ -0,0 +1,38 @@
+package org.dromara.workflow.domain.bo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 涓嬩竴鑺傜偣淇℃伅
+ *
+ * @author may
+ */
+@Data
+public class FlowNextNodeBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+ /**
+ * 浠诲姟id
+ */
+ private Long taskId;
+
+ /**
+ * 娴佺▼鍙橀噺
+ */
+ private Map<String, Object> variables;
+
+ public Map<String, Object> getVariables() {
+ if (variables == null) {
+ return new HashMap<>(16);
+ }
+ variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+ return variables;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowSpelBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowSpelBo.java
new file mode 100755
index 0000000..da78935
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowSpelBo.java
@@ -0,0 +1,60 @@
+package org.dromara.workflow.domain.bo;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import org.dromara.workflow.domain.FlowSpel;
+
+/**
+ * 娴佺▼spel琛ㄨ揪寮忓畾涔変笟鍔″璞� flow_spel
+ *
+ * @author Michelle.Chung
+ * @date 2025-07-04
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = FlowSpel.class, reverseConvertGenerate = false)
+public class FlowSpelBo extends BaseEntity {
+
+ /**
+ * 涓婚敭id
+ */
+ private Long id;
+
+ /**
+ * 缁勪欢鍚嶇О
+ */
+ private String componentName;
+
+ /**
+ * 鏂规硶鍚�
+ */
+ private String methodName;
+
+ /**
+ * 鍙傛暟
+ */
+ private String methodParams;
+
+ /**
+ * 棰勮spel鍊�
+ */
+ @NotBlank(message = "棰勮spel鍊间笉鑳戒负绌�", groups = { AddGroup.class, EditGroup.class })
+ private String viewSpel;
+
+ /**
+ * 鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ @NotBlank(message = "鐘舵�侊紙0姝e父 1鍋滅敤锛変笉鑳戒负绌�", groups = { AddGroup.class, EditGroup.class })
+ private String status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTaskBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTaskBo.java
new file mode 100755
index 0000000..7540564
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTaskBo.java
@@ -0,0 +1,69 @@
+package org.dromara.workflow.domain.bo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 浠诲姟璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class FlowTaskBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 浠诲姟鍚嶇О
+ */
+ private String nodeName;
+
+ /**
+ * 娴佺▼瀹氫箟鍚嶇О
+ */
+ private String flowName;
+
+ /**
+ * 娴佺▼瀹氫箟缂栫爜
+ */
+ private String flowCode;
+
+ /**
+ * 娴佺▼鍒嗙被id
+ */
+ private String category;
+
+ /**
+ * 娴佺▼瀹炰緥id
+ */
+ private Long instanceId;
+
+ /**
+ * 娴佺▼鐘舵��
+ */
+ private String flowStatus;
+
+ /**
+ * 鏉冮檺鍒楄〃
+ */
+ private List<String> permissionList;
+
+ /**
+ * 鐢宠浜篒ds
+ */
+ private List<Long> createByIds;
+
+ /**
+ * 璇锋眰鍙傛暟
+ */
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ private Map<String, Object> params = new HashMap<>();
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTerminationBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTerminationBo.java
new file mode 100755
index 0000000..897fc21
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTerminationBo.java
@@ -0,0 +1,31 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 缁堟浠诲姟璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class FlowTerminationBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 浠诲姟id
+ */
+ @NotNull(message = "浠诲姟id涓虹┖", groups = AddGroup.class)
+ private Long taskId;
+
+ /**
+ * 瀹℃壒鎰忚
+ */
+ private String comment;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowUrgeTaskBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowUrgeTaskBo.java
new file mode 100755
index 0000000..8e51b12
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowUrgeTaskBo.java
@@ -0,0 +1,38 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 娴佺▼鍙橀噺鍙傛暟
+ *
+ * @author may
+ */
+@Data
+public class FlowUrgeTaskBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 浠诲姟id
+ */
+ @NotNull(message = "浠诲姟id涓虹┖", groups = AddGroup.class)
+ private List<Long> taskIdList;
+
+ /**
+ * 娑堟伅绫诲瀷
+ */
+ private List<String> messageType;
+
+ /**
+ * 鍌姙鍐呭
+ */
+ @NotNull(message = "鍌姙鍐呭涓虹┖", groups = AddGroup.class)
+ private String message;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowVariableBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowVariableBo.java
new file mode 100755
index 0000000..9361299
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowVariableBo.java
@@ -0,0 +1,39 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 娴佺▼鍙橀噺鍙傛暟
+ *
+ * @author may
+ */
+@Data
+public class FlowVariableBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 娴佺▼瀹炰緥id
+ */
+ @NotNull(message = "娴佺▼瀹炰緥id涓虹┖", groups = AddGroup.class)
+ private Long instanceId;
+
+ /**
+ * 娴佺▼鍙橀噺key
+ */
+ @NotNull(message = "娴佺▼鍙橀噺key涓虹┖", groups = AddGroup.class)
+ private String key;
+
+ /**
+ * 娴佺▼鍙橀噺value
+ */
+ @NotNull(message = "娴佺▼鍙橀噺value涓虹┖", groups = AddGroup.class)
+ private String value;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java
new file mode 100755
index 0000000..b31f4fa
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java
@@ -0,0 +1,68 @@
+package org.dromara.workflow.domain.bo;
+
+
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.workflow.domain.FlowInstanceBizExt;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 鍚姩娴佺▼瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class StartProcessBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓氬姟鍞竴鍊糹d
+ */
+ @NotBlank(message = "涓氬姟ID涓嶈兘涓虹┖", groups = {AddGroup.class})
+ private String businessId;
+
+ /**
+ * 娴佺▼瀹氫箟缂栫爜
+ */
+ @NotBlank(message = "娴佺▼瀹氫箟缂栫爜涓嶈兘涓虹┖", groups = {AddGroup.class})
+ private String flowCode;
+
+ /**
+ * 鍔炵悊浜�(鍙笉濉� 鐢ㄤ簬瑕嗙洊褰撳墠鑺傜偣鍔炵悊浜�)
+ */
+ private String handler;
+
+ /**
+ * 娴佺▼鍙橀噺锛屽墠绔細鎻愪氦涓�涓厓绱爗'entity': {涓氬姟璇︽儏鏁版嵁瀵硅薄}}
+ */
+ private Map<String, Object> variables;
+
+ /**
+ * 娴佺▼涓氬姟鎵╁睍淇℃伅
+ */
+ private FlowInstanceBizExt bizExt;
+
+ public Map<String, Object> getVariables() {
+ if (variables == null) {
+ return new HashMap<>(16);
+ }
+ variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+ return variables;
+ }
+
+ public FlowInstanceBizExt getBizExt() {
+ if (ObjectUtil.isNull(bizExt)) {
+ bizExt = new FlowInstanceBizExt();
+ }
+ return bizExt;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskOperationBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskOperationBo.java
new file mode 100755
index 0000000..4348e31
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskOperationBo.java
@@ -0,0 +1,48 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+
+/**
+ * 浠诲姟鎿嶄綔涓氬姟瀵硅薄锛岀敤浜庢弿杩颁换鍔″娲俱�佽浆鍔炪�佸姞绛剧瓑鎿嶄綔鐨勫繀瑕佸弬鏁�
+ * 鍖呭惈浜嗙敤鎴稩D銆佷换鍔D銆佷换鍔$浉鍏崇殑娑堟伅銆佷互鍙婂姞绛�/鍑忕鐨勭敤鎴稩D
+ *
+ * @author AprilWind
+ */
+@Data
+public class TaskOperationBo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 濮旀淳/杞姙浜虹殑鐢ㄦ埛ID锛堝繀濉紝鍑嗗濮旀淳/杞姙浜烘搷浣滐級
+ */
+ @NotNull(message = "濮旀淳/杞姙浜篿d涓嶈兘涓虹┖", groups = {AddGroup.class})
+ private String userId;
+
+ /**
+ * 鍔犵/鍑忕浜虹殑鐢ㄦ埛ID鍒楄〃锛堝繀濉紝閽堝鍔犵/鍑忕鎿嶄綔锛�
+ */
+ @NotNull(message = "鍔犵/鍑忕id涓嶈兘涓虹┖", groups = {EditGroup.class})
+ private List<String> userIds;
+
+ /**
+ * 浠诲姟ID锛堝繀濉級
+ */
+ @NotNull(message = "浠诲姟id涓嶈兘涓虹┖")
+ private Long taskId;
+
+ /**
+ * 鎰忚鎴栧娉ㄤ俊鎭紙鍙�夛級
+ */
+ private String message;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java
new file mode 100755
index 0000000..2463569
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java
@@ -0,0 +1,92 @@
+package org.dromara.workflow.domain.bo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.workflow.domain.TestLeave;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * 璇峰亣涓氬姟瀵硅薄 test_leave
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = TestLeave.class, reverseConvertGenerate = false)
+public class TestLeaveBo extends BaseEntity {
+
+ /**
+ * 涓婚敭
+ */
+ @NotNull(message = "涓婚敭涓嶈兘涓虹┖", groups = {EditGroup.class})
+ private Long id;
+
+ /**
+ * 娴佺▼code
+ */
+ private String flowCode;
+
+ /**
+ * 鐢宠缂栧彿
+ */
+ private String applyCode;
+
+ /**
+ * 璇峰亣绫诲瀷
+ */
+ @NotBlank(message = "璇峰亣绫诲瀷涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+ private String leaveType;
+
+ /**
+ * 寮�濮嬫椂闂�
+ */
+ @NotNull(message = "寮�濮嬫椂闂翠笉鑳戒负绌�", groups = {AddGroup.class, EditGroup.class})
+ @DateTimeFormat(pattern = "yyyy-MM-dd")
+ @JsonFormat(pattern = "yyyy-MM-dd")
+ private Date startDate;
+
+ /**
+ * 缁撴潫鏃堕棿
+ */
+ @NotNull(message = "缁撴潫鏃堕棿涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+ @DateTimeFormat(pattern = "yyyy-MM-dd")
+ @JsonFormat(pattern = "yyyy-MM-dd")
+ private Date endDate;
+
+ /**
+ * 璇峰亣澶╂暟
+ */
+ private Integer leaveDays;
+
+ /**
+ * 寮�濮嬫椂闂�
+ */
+ private Integer startLeaveDays;
+
+ /**
+ * 缁撴潫鏃堕棿
+ */
+ private Integer endLeaveDays;
+
+ /**
+ * 璇峰亣鍘熷洜
+ */
+ private String remark;
+
+ /**
+ * 鐘舵��
+ */
+ private String status;
+
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermissionVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermissionVo.java
new file mode 100755
index 0000000..7175e5e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermissionVo.java
@@ -0,0 +1,43 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 鎸夐挳鏉冮檺
+ *
+ * @author may
+ * @date 2025-02-28
+ */
+@Data
+public class ButtonPermissionVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鍞竴缂栫爜
+ */
+ private String code;
+
+ /**
+ * 閫夐」鍊�
+ */
+ private String value;
+
+ /**
+ * 鏄惁鏄剧ず
+ */
+ private Boolean show;
+
+ public ButtonPermissionVo() {
+ }
+
+ public ButtonPermissionVo(String code, Boolean show) {
+ this.code = code;
+ this.show = show;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCategoryVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCategoryVo.java
new file mode 100755
index 0000000..c5427a5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCategoryVo.java
@@ -0,0 +1,69 @@
+package org.dromara.workflow.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.domain.FlowCategory;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 娴佺▼鍒嗙被瑙嗗浘瀵硅薄 wf_category
+ *
+ * @author may
+ * @date 2023-06-27
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = FlowCategory.class)
+public class FlowCategoryVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 娴佺▼鍒嗙被ID
+ */
+ @ExcelProperty(value = "娴佺▼鍒嗙被ID")
+ private Long categoryId;
+
+ /**
+ * 鐖剁骇鍒嗙被id
+ */
+ private Long parentId;
+
+ /**
+ * 鐖剁骇鍒嗙被鍚嶇О
+ */
+ @Translation(type = FlowConstant.CATEGORY_ID_TO_NAME, mapper = "parentId")
+ private String parentName;
+
+ /**
+ * 绁栫骇鍒楄〃
+ */
+ private String ancestors;
+
+ /**
+ * 娴佺▼鍒嗙被鍚嶇О
+ */
+ @ExcelProperty(value = "娴佺▼鍒嗙被鍚嶇О")
+ private String categoryName;
+
+ /**
+ * 鏄剧ず椤哄簭
+ */
+ @ExcelProperty(value = "鏄剧ず椤哄簭")
+ private Long orderNum;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ @ExcelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCopyVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCopyVo.java
new file mode 100755
index 0000000..67ef9e2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCopyVo.java
@@ -0,0 +1,36 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 鎶勯�佸璞�
+ *
+ * @author AprilWind
+ */
+@Data
+public class FlowCopyVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鐢ㄦ埛id
+ */
+ private Long userId;
+
+ /**
+ * 鐢ㄦ埛鍚嶇О
+ */
+ @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "userId")
+ private String userName;
+
+ public FlowCopyVo(Long userId) {
+ this.userId = userId;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowDefinitionVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowDefinitionVo.java
new file mode 100755
index 0000000..aef7573
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowDefinitionVo.java
@@ -0,0 +1,104 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.workflow.common.constant.FlowConstant;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 娴佺▼瀹氫箟瑙嗗浘
+ *
+ * @author may
+ */
+@Data
+public class FlowDefinitionVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private Long id;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ private Date createTime;
+
+ /**
+ * 鏇存柊鏃堕棿
+ */
+ private Date updateTime;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 鍒犻櫎鏍囪
+ */
+ private String delFlag;
+
+ /**
+ * 娴佺▼瀹氫箟缂栫爜
+ */
+ private String flowCode;
+
+ /**
+ * 娴佺▼瀹氫箟鍚嶇О
+ */
+ private String flowName;
+
+ /**
+ * 娴佺▼鍒嗙被id
+ */
+ private String category;
+
+ /**
+ * 娴佺▼鍒嗙被鍚嶇О
+ */
+ @Translation(type = FlowConstant.CATEGORY_ID_TO_NAME, mapper = "category")
+ private String categoryName;
+
+ /**
+ * 娴佺▼鐗堟湰
+ */
+ private String version;
+
+ /**
+ * 鏄惁鍙戝竷锛�0鏈彂甯� 1宸插彂甯� 9澶辨晥锛�
+ */
+ private Integer isPublish;
+
+ /**
+ * 瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級
+ */
+ private String formCustom;
+
+ /**
+ * 瀹℃壒琛ㄥ崟璺緞
+ */
+ private String formPath;
+
+ /**
+ * 娴佺▼婵�娲荤姸鎬侊紙0鎸傝捣 1婵�娲伙級
+ */
+ private Integer activityStatus;
+
+ /**
+ * 鐩戝惉鍣ㄧ被鍨�
+ */
+ private String listenerType;
+
+ /**
+ * 鐩戝惉鍣ㄨ矾寰�
+ */
+ private String listenerPath;
+
+ /**
+ * 鎵╁睍瀛楁锛岄鐣欑粰涓氬姟绯荤粺浣跨敤
+ */
+ private String ext;
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowHisTaskVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowHisTaskVo.java
new file mode 100755
index 0000000..a9f6a60
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowHisTaskVo.java
@@ -0,0 +1,260 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.warm.flow.core.enums.CooperateType;
+import org.dromara.workflow.common.constant.FlowConstant;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 鍘嗗彶浠诲姟瑙嗗浘
+ *
+ * @author may
+ */
+@Data
+public class FlowHisTaskVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private Long id;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ private Date createTime;
+
+ /**
+ * 鏇存柊鏃堕棿
+ */
+ private Date updateTime;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 鍒犻櫎鏍囪
+ */
+ private String delFlag;
+
+ /**
+ * 瀵瑰簲flow_definition琛ㄧ殑id
+ */
+ private Long definitionId;
+
+ /**
+ * 娴佺▼瀹氫箟鍚嶇О
+ */
+ private String flowName;
+
+ /**
+ * 娴佺▼瀹炰緥琛╥d
+ */
+ private Long instanceId;
+
+ /**
+ * 浠诲姟琛╥d
+ */
+ private Long taskId;
+
+ /**
+ * 鍗忎綔鏂瑰紡(1瀹℃壒 2杞姙 3濮旀淳 4浼氱 5绁ㄧ 6鍔犵 7鍑忕)
+ */
+ private Integer cooperateType;
+
+ /**
+ * 鍗忎綔鏂瑰紡(1瀹℃壒 2杞姙 3濮旀淳 4浼氱 5绁ㄧ 6鍔犵 7鍑忕)
+ */
+ private String cooperateTypeName;
+
+ /**
+ * 涓氬姟id
+ */
+ private String businessId;
+
+ /**
+ * 寮�濮嬭妭鐐圭紪鐮�
+ */
+ private String nodeCode;
+
+ /**
+ * 寮�濮嬭妭鐐瑰悕绉�
+ */
+ private String nodeName;
+
+ /**
+ * 寮�濮嬭妭鐐圭被鍨嬶紙0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�
+ */
+ private Integer nodeType;
+
+ /**
+ * 鐩爣鑺傜偣缂栫爜
+ */
+ private String targetNodeCode;
+
+ /**
+ * 缁撴潫鑺傜偣鍚嶇О
+ */
+ private String targetNodeName;
+
+ /**
+ * 瀹℃壒鑰�
+ */
+ private String approver;
+
+ /**
+ * 瀹℃壒鑰�
+ */
+ @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "approver")
+ private String approveName;
+
+ /**
+ * 鍗忎綔浜�(鍙湁杞姙銆佷細绛俱�佺エ绛俱�佸娲�)
+ */
+ private String collaborator;
+
+ /**
+ * 鏉冮檺鏍囪瘑 permissionFlag鐨刲ist褰㈠紡
+ */
+ private List<String> permissionList;
+
+ /**
+ * 璺宠浆绫诲瀷锛圥ASS閫氳繃 REJECT閫�鍥� NONE鏃犲姩浣滐級
+ */
+ private String skipType;
+
+ /**
+ * 娴佺▼鐘舵��
+ */
+ private String flowStatus;
+
+ /**
+ * 浠诲姟鐘舵��
+ */
+ private String flowTaskStatus;
+
+ /**
+ * 娴佺▼鐘舵��
+ */
+ private String flowStatusName;
+
+ /**
+ * 瀹℃壒鎰忚
+ */
+ private String message;
+
+ /**
+ * 涓氬姟璇︽儏 瀛樹笟鍔$被鐨刯son
+ */
+ private String ext;
+
+ /**
+ * 鍒涘缓鑰�
+ */
+ private String createBy;
+
+ /**
+ * 鐢宠浜�
+ */
+ @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "createBy")
+ private String createByName;
+
+ /**
+ * 娴佺▼鍒嗙被id
+ */
+ private String category;
+
+ /**
+ * 娴佺▼鍒嗙被鍚嶇О
+ */
+ @Translation(type = FlowConstant.CATEGORY_ID_TO_NAME, mapper = "category")
+ private String categoryName;
+
+ /**
+ * 瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級
+ */
+ private String formCustom;
+
+ /**
+ * 瀹℃壒琛ㄥ崟璺緞
+ */
+ private String formPath;
+
+ /**
+ * 娴佺▼瀹氫箟缂栫爜
+ */
+ private String flowCode;
+
+ /**
+ * 娴佺▼鐗堟湰鍙�
+ */
+ private String version;
+
+ /**
+ * 杩愯鏃堕暱
+ */
+ private String runDuration;
+
+ //涓氬姟鎵╁睍淇℃伅寮�濮�
+ /**
+ * 涓氬姟缂栫爜
+ */
+ private String businessCode;
+
+ /**
+ * 涓氬姟鏍囬
+ */
+ private String businessTitle;
+ //涓氬姟鎵╁睍淇℃伅缁撴潫
+
+ /**
+ * 璁剧疆鍒涘缓鏃堕棿骞惰绠椾换鍔¤繍琛屾椂闀�
+ *
+ * @param createTime 鍒涘缓鏃堕棿
+ */
+ public void setCreateTime(Date createTime) {
+ this.createTime = createTime;
+ updateRunDuration();
+ }
+
+ /**
+ * 璁剧疆鏇存柊鏃堕棿骞惰绠椾换鍔¤繍琛屾椂闀�
+ *
+ * @param updateTime 鏇存柊鏃堕棿
+ */
+ public void setUpdateTime(Date updateTime) {
+ this.updateTime = updateTime;
+ updateRunDuration();
+ }
+
+ /**
+ * 鏇存柊杩愯鏃堕暱
+ */
+ private void updateRunDuration() {
+ // 濡傛灉鍒涘缓鏃堕棿鍜屾洿鏂版椂闂村潎涓嶄负绌猴紝璁$畻瀹冧滑涔嬮棿鐨勬椂闀�
+ if (this.updateTime != null && this.createTime != null) {
+ this.runDuration = DateUtils.getTimeDifference(this.updateTime, this.createTime);
+ }
+ }
+
+ /**
+ * 璁剧疆鍗忎綔鏂瑰紡锛屽苟閫氳繃鍗忎綔鏂瑰紡鑾峰彇鍚嶇О
+ */
+ public void setCooperateType(Integer cooperateType) {
+ this.cooperateType = cooperateType;
+ this.cooperateTypeName = CooperateType.getValueByKey(cooperateType);
+ }
+
+ public String getCreateTime() {
+ return DateUtils.formatFriendlyTime(createTime);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowInstanceVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowInstanceVo.java
new file mode 100755
index 0000000..b03f73b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowInstanceVo.java
@@ -0,0 +1,149 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.workflow.common.constant.FlowConstant;
+
+import java.util.Date;
+
+/**
+ * 娴佺▼瀹炰緥瑙嗗浘
+ *
+ * @author may
+ */
+@Data
+public class FlowInstanceVo {
+
+ private Long id;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ private Date createTime;
+
+ /**
+ * 鏇存柊鏃堕棿
+ */
+ private Date updateTime;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 鍒犻櫎鏍囪
+ */
+ private String delFlag;
+
+ /**
+ * 瀵瑰簲flow_definition琛ㄧ殑id
+ */
+ private Long definitionId;
+
+ /**
+ * 娴佺▼瀹氫箟鍚嶇О
+ */
+ private String flowName;
+
+ /**
+ * 娴佺▼瀹氫箟缂栫爜
+ */
+ private String flowCode;
+
+ /**
+ * 涓氬姟id
+ */
+ private String businessId;
+
+ /**
+ * 鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�
+ */
+ private Integer nodeType;
+
+ /**
+ * 娴佺▼鑺傜偣缂栫爜 姣忎釜娴佺▼鐨刵odeCode鏄敮涓�鐨�,鍗砫efinitionId+nodeCode鍞竴,鍦ㄦ暟鎹簱灞傞潰鍋氫簡鎺у埗
+ */
+ private String nodeCode;
+
+ /**
+ * 娴佺▼鑺傜偣鍚嶇О
+ */
+ private String nodeName;
+
+ /**
+ * 娴佺▼鍙橀噺
+ */
+ private String variable;
+
+ /**
+ * 娴佺▼鐘舵��
+ */
+ private String flowStatus;
+
+ /**
+ * 娴佺▼鐘舵��
+ */
+ private String flowStatusName;
+
+ /**
+ * 娴佺▼婵�娲荤姸鎬侊紙0鎸傝捣 1婵�娲伙級
+ */
+ private Integer activityStatus;
+
+ /**
+ * 瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級
+ */
+ private String formCustom;
+
+ /**
+ * 瀹℃壒琛ㄥ崟璺緞
+ */
+ private String formPath;
+
+ /**
+ * 鎵╁睍瀛楁锛岄鐣欑粰涓氬姟绯荤粺浣跨敤
+ */
+ private String ext;
+
+ /**
+ * 娴佺▼瀹氫箟鐗堟湰
+ */
+ private String version;
+
+ /**
+ * 鍒涘缓鑰�
+ */
+ private String createBy;
+
+ /**
+ * 鐢宠浜�
+ */
+ @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "createBy")
+ private String createByName;
+
+ /**
+ * 娴佺▼鍒嗙被id
+ */
+ private String category;
+
+ /**
+ * 娴佺▼鍒嗙被鍚嶇О
+ */
+ @Translation(type = FlowConstant.CATEGORY_ID_TO_NAME, mapper = "category")
+ private String categoryName;
+
+ //涓氬姟鎵╁睍淇℃伅寮�濮�
+ /**
+ * 涓氬姟缂栫爜
+ */
+ private String businessCode;
+
+ /**
+ * 涓氬姟鏍囬
+ */
+ private String businessTitle;
+ //涓氬姟鎵╁睍淇℃伅缁撴潫
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowSpelVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowSpelVo.java
new file mode 100755
index 0000000..76ead83
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowSpelVo.java
@@ -0,0 +1,79 @@
+package org.dromara.workflow.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.workflow.domain.FlowSpel;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 娴佺▼spel琛ㄨ揪寮忓畾涔夎鍥惧璞� flow_spel
+ *
+ * @author Michelle.Chung
+ * @date 2025-07-04
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = FlowSpel.class)
+public class FlowSpelVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭id
+ */
+ @ExcelProperty(value = "涓婚敭id")
+ private Long id;
+
+ /**
+ * 缁勪欢鍚嶇О
+ */
+ @ExcelProperty(value = "缁勪欢鍚嶇О")
+ private String componentName;
+
+ /**
+ * 鏂规硶鍚�
+ */
+ @ExcelProperty(value = "鏂规硶鍚�")
+ private String methodName;
+
+ /**
+ * 鍙傛暟
+ */
+ @ExcelProperty(value = "鍙傛暟")
+ private String methodParams;
+
+ /**
+ * 棰勮spel鍊�
+ */
+ @ExcelProperty(value = "棰勮spel鍊�")
+ private String viewSpel;
+
+ /**
+ * 鐘舵�侊紙0姝e父 1鍋滅敤锛�
+ */
+ @ExcelProperty(value = "鐘舵��", converter = ExcelDictConvert.class)
+ @ExcelDictFormat(readConverterExp = "0=姝e父,1=鍋滅敤")
+ private String status;
+
+ /**
+ * 澶囨敞
+ */
+ @ExcelProperty(value = "澶囨敞")
+ private String remark;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ @ExcelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java
new file mode 100755
index 0000000..ffe8049
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java
@@ -0,0 +1,219 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.warm.flow.core.entity.User;
+import org.dromara.workflow.common.constant.FlowConstant;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 浠诲姟瑙嗗浘
+ *
+ * @author may
+ */
+@Data
+public class FlowTaskVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private Long id;
+
+ /**
+ * 鍒涘缓鏃堕棿
+ */
+ private Date createTime;
+
+ /**
+ * 鏇存柊鏃堕棿
+ */
+ private Date updateTime;
+
+ /**
+ * 绉熸埛ID
+ */
+ private String tenantId;
+
+ /**
+ * 鍒犻櫎鏍囪
+ */
+ private String delFlag;
+
+ /**
+ * 瀵瑰簲flow_definition琛ㄧ殑id
+ */
+ private Long definitionId;
+
+ /**
+ * 娴佺▼瀹炰緥琛╥d
+ */
+ private Long instanceId;
+
+ /**
+ * 娴佺▼瀹氫箟鍚嶇О
+ */
+ private String flowName;
+
+ /**
+ * 涓氬姟id
+ */
+ private String businessId;
+
+ /**
+ * 鑺傜偣缂栫爜
+ */
+ private String nodeCode;
+
+ /**
+ * 鑺傜偣鍚嶇О
+ */
+ private String nodeName;
+
+ /**
+ * 鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�
+ */
+ private Integer nodeType;
+
+ /**
+ * 鏉冮檺鏍囪瘑 permissionFlag鐨刲ist褰㈠紡
+ */
+ private List<String> permissionList;
+
+ /**
+ * 娴佺▼鐢ㄦ埛鍒楄〃
+ */
+ private List<User> userList;
+
+ /**
+ * 瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級
+ */
+ private String formCustom;
+
+ /**
+ * 瀹℃壒琛ㄥ崟
+ */
+ private String formPath;
+
+ /**
+ * 娴佺▼瀹氫箟缂栫爜
+ */
+ private String flowCode;
+
+ /**
+ * 娴佺▼鐗堟湰鍙�
+ */
+ private String version;
+
+ /**
+ * 娴佺▼鐘舵��
+ */
+ private String flowStatus;
+
+ /**
+ * 娴佺▼鍒嗙被id
+ */
+ private String category;
+
+ /**
+ * 娴佺▼鍒嗙被鍚嶇О
+ */
+ @Translation(type = FlowConstant.CATEGORY_ID_TO_NAME, mapper = "category")
+ private String categoryName;
+
+ /**
+ * 娴佺▼鐘舵��
+ */
+ @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "flowStatus", other = "wf_business_status")
+ private String flowStatusName;
+
+ /**
+ * 鍔炵悊浜虹被鍨�
+ */
+ private String type;
+
+ /**
+ * 鍔炵悊浜篿ds
+ */
+ private String assigneeIds;
+
+ /**
+ * 鍔炵悊浜哄悕绉�
+ */
+ @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "assigneeIds")
+ private String assigneeNames;
+
+ /**
+ * 鎶勯�佷汉id
+ */
+ private String processedBy;
+
+ /**
+ * 鎶勯�佷汉鍚嶇О
+ */
+ @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "processedBy")
+ private String processedByName;
+
+ /**
+ * 娴佺▼绛剧讲姣斾緥鍊� 澶т簬0涓虹エ绛撅紝浼氱
+ */
+ private String nodeRatio;
+
+ /**
+ * 鐢宠浜篿d
+ */
+ private String createBy;
+
+ /**
+ * 鐢宠浜哄悕绉�
+ */
+ @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "createBy")
+ private String createByName;
+
+ /**
+ * 鏄惁涓虹敵璇蜂汉鑺傜偣
+ */
+ private Boolean applyNode;
+
+ /**
+ * 鎸夐挳鏉冮檺
+ */
+ private List<ButtonPermissionVo> buttonList;
+
+ /**
+ * 鎶勯�佸璞� ID 闆嗗悎
+ * <p>
+ * 鏍规嵁鎵╁睍灞炴�т腑 CopySettingEnum 绫诲瀷鐨勬暟鎹敓鎴愶紝瀛樺偍闇�瑕佹妱閫佺殑瀵硅薄 ID
+ */
+ private List<FlowCopyVo> copyList;
+
+ /**
+ * 鑷畾涔夊弬鏁� Map
+ * <p>
+ * 鏍规嵁鎵╁睍灞炴�т腑 VariablesEnum 绫诲瀷鐨勬暟鎹敓鎴愶紝瀛樺偍 key=value 鏍煎紡鐨勮嚜瀹氫箟鍙傛暟
+ */
+ private Map<String, String> varList;
+
+ //涓氬姟鎵╁睍淇℃伅寮�濮�
+ /**
+ * 涓氬姟缂栫爜
+ */
+ private String businessCode;
+
+ /**
+ * 涓氬姟鏍囬
+ */
+ private String businessTitle;
+ //涓氬姟鎵╁睍淇℃伅缁撴潫
+
+ public String getCreateTime() {
+ return DateUtils.formatFriendlyTime(createTime);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/NodeExtVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/NodeExtVo.java
new file mode 100755
index 0000000..5fb3380
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/NodeExtVo.java
@@ -0,0 +1,45 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Node 鎵╁睍灞炴�цВ鏋愮粨鏋� VO
+ * <p>
+ * 鐢ㄤ簬灏佽浠庢墿灞曞睘鎬� JSON 涓В鏋愬嚭鐨勫悇绫讳俊鎭紝鍖呮嫭鎸夐挳鏉冮檺銆佹妱閫佸璞″拰鑷畾涔夊弬鏁般��
+ *
+ * @author AprilWind
+ */
+@Data
+public class NodeExtVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鎸夐挳鏉冮檺鍒楄〃
+ * <p>
+ * 鏍规嵁鎵╁睍灞炴�т腑 ButtonPermissionEnum 绫诲瀷鐨勬暟鎹敓鎴愶紝姣忎釜鍏冪礌琛ㄧず涓�涓寜閽強鍏舵槸鍚﹀嬀閫夈��
+ */
+ private List<ButtonPermissionVo> buttonPermissions;
+
+ /**
+ * 鎶勯�佸璞� ID 闆嗗悎
+ * <p>
+ * 鏍规嵁鎵╁睍灞炴�т腑 CopySettingEnum 绫诲瀷鐨勬暟鎹敓鎴愶紝瀛樺偍闇�瑕佹妱閫佺殑瀵硅薄 ID
+ */
+ private Set<String> copySettings;
+
+ /**
+ * 鑷畾涔夊弬鏁� Map
+ * <p>
+ * 鏍规嵁鎵╁睍灞炴�т腑 VariablesEnum 绫诲瀷鐨勬暟鎹敓鎴愶紝瀛樺偍 key=value 鏍煎紡鐨勮嚜瀹氫箟鍙傛暟
+ */
+ private Map<String, String> variables;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java
new file mode 100755
index 0000000..2f34158
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java
@@ -0,0 +1,77 @@
+package org.dromara.workflow.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.workflow.domain.TestLeave;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 璇峰亣瑙嗗浘瀵硅薄 test_leave
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = TestLeave.class)
+public class TestLeaveVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 涓婚敭
+ */
+ @ExcelProperty(value = "涓婚敭")
+ private Long id;
+
+ /**
+ * 鐢宠缂栧彿
+ */
+ @ExcelProperty(value = "鐢宠缂栧彿")
+ private String applyCode;
+
+ /**
+ * 璇峰亣绫诲瀷
+ */
+ @ExcelProperty(value = "璇峰亣绫诲瀷")
+ private String leaveType;
+
+ /**
+ * 寮�濮嬫椂闂�
+ */
+ @ExcelProperty(value = "寮�濮嬫椂闂�")
+ private Date startDate;
+
+ /**
+ * 缁撴潫鏃堕棿
+ */
+ @ExcelProperty(value = "缁撴潫鏃堕棿")
+ private Date endDate;
+
+ /**
+ * 璇峰亣澶╂暟
+ */
+ @ExcelProperty(value = "璇峰亣澶╂暟")
+ private Integer leaveDays;
+
+ /**
+ * 澶囨敞
+ */
+ @ExcelProperty(value = "璇峰亣鍘熷洜")
+ private String remark;
+
+ /**
+ * 鐘舵��
+ */
+ @ExcelProperty(value = "鐘舵��")
+ private String status;
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java
new file mode 100755
index 0000000..1965f67
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java
@@ -0,0 +1,96 @@
+package org.dromara.workflow.handler;
+
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.event.ProcessTaskEvent;
+import org.dromara.common.core.domain.event.ProcessDeleteEvent;
+import org.dromara.common.core.domain.event.ProcessEvent;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.warm.flow.core.entity.Instance;
+import org.dromara.warm.flow.core.entity.Task;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * 娴佺▼鐩戝惉鏈嶅姟
+ *
+ * @author may
+ * @date 2024-06-02
+ */
+@ConditionalOnEnable
+@Slf4j
+@Component
+public class FlowProcessEventHandler {
+
+ /**
+ * 鎬讳綋娴佺▼鐩戝惉(渚嬪: 鑽夌锛屾挙閿�锛岄��鍥烇紝浣滃簾锛岀粓姝紝宸插畬鎴愮瓑)
+ *
+ * @param flowCode 娴佺▼瀹氫箟缂栫爜
+ * @param instance 瀹炰緥鏁版嵁
+ * @param status 娴佺▼鐘舵��
+ * @param params 鍔炵悊鍙傛暟
+ * @param submit 褰撲负true鏃朵负鐢宠浜鸿妭鐐瑰姙鐞�
+ */
+ public void processHandler(String flowCode, Instance instance, String status, Map<String, Object> params, boolean submit) {
+ String tenantId = TenantHelper.getTenantId();
+ log.info("銆愭祦绋嬩簨浠跺彂甯冦�戠鎴稩D: {}, 娴佺▼缂栫爜: {}, 涓氬姟ID: {}, 娴佺▼鐘舵��: {}, 鑺傜偣绫诲瀷: {}, 鑺傜偣缂栫爜: {}, 鑺傜偣鍚嶇О: {}, 鏄惁鐢宠浜鸿妭鐐�: {}, 鍙傛暟: {}",
+ tenantId, flowCode, instance.getBusinessId(), status, instance.getNodeType(), instance.getNodeCode(), instance.getNodeName(), submit, params);
+ ProcessEvent processEvent = new ProcessEvent();
+ processEvent.setTenantId(tenantId);
+ processEvent.setFlowCode(flowCode);
+ processEvent.setInstanceId(instance.getId());
+ processEvent.setBusinessId(instance.getBusinessId());
+ processEvent.setNodeType(instance.getNodeType());
+ processEvent.setNodeCode(instance.getNodeCode());
+ processEvent.setNodeName(instance.getNodeName());
+ processEvent.setStatus(status);
+ processEvent.setParams(params);
+ processEvent.setSubmit(submit);
+ SpringUtils.context().publishEvent(processEvent);
+ }
+
+ /**
+ * 鎵ц鍒涘缓浠诲姟鐩戝惉
+ *
+ * @param flowCode 娴佺▼瀹氫箟缂栫爜
+ * @param instance 瀹炰緥鏁版嵁
+ * @param nextTask 浠诲姟
+ * @param params 涓婁竴涓换鍔$殑鍔炵悊鍙傛暟
+ */
+ public void processTaskHandler(String flowCode, Instance instance, Task nextTask, Map<String, Object> params) {
+ String tenantId = TenantHelper.getTenantId();
+ log.info("銆愭祦绋嬩换鍔′簨浠跺彂甯冦�戠鎴稩D: {}, 娴佺▼缂栫爜: {}, 涓氬姟ID: {}, 鑺傜偣绫诲瀷: {}, 鑺傜偣缂栫爜: {}, 鑺傜偣鍚嶇О: {}, 浠诲姟ID: {}",
+ tenantId, flowCode, instance.getBusinessId(), nextTask.getNodeType(), nextTask.getNodeCode(), nextTask.getNodeName(), nextTask.getId());
+ ProcessTaskEvent processTaskEvent = new ProcessTaskEvent();
+ processTaskEvent.setTenantId(tenantId);
+ processTaskEvent.setFlowCode(flowCode);
+ processTaskEvent.setInstanceId(instance.getId());
+ processTaskEvent.setBusinessId(instance.getBusinessId());
+ processTaskEvent.setNodeType(nextTask.getNodeType());
+ processTaskEvent.setNodeCode(nextTask.getNodeCode());
+ processTaskEvent.setNodeName(nextTask.getNodeName());
+ processTaskEvent.setTaskId(nextTask.getId());
+ processTaskEvent.setStatus(instance.getFlowStatus());
+ processTaskEvent.setParams(params);
+ SpringUtils.context().publishEvent(processTaskEvent);
+ }
+
+ /**
+ * 鍒犻櫎娴佺▼鐩戝惉
+ *
+ * @param flowCode 娴佺▼瀹氫箟缂栫爜
+ * @param businessId 涓氬姟ID
+ */
+ public void processDeleteHandler(String flowCode, String businessId) {
+ String tenantId = TenantHelper.getTenantId();
+ log.info("銆愭祦绋嬪垹闄や簨浠跺彂甯冦�戠鎴稩D: {}, 娴佺▼缂栫爜: {}, 涓氬姟ID: {}", tenantId, flowCode, businessId);
+ ProcessDeleteEvent processDeleteEvent = new ProcessDeleteEvent();
+ processDeleteEvent.setTenantId(tenantId);
+ processDeleteEvent.setFlowCode(flowCode);
+ processDeleteEvent.setBusinessId(businessId);
+ SpringUtils.context().publishEvent(processDeleteEvent);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java
new file mode 100755
index 0000000..e908a20
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java
@@ -0,0 +1,65 @@
+package org.dromara.workflow.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.dto.UserDTO;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.warm.flow.core.dto.FlowParams;
+import org.dromara.warm.flow.core.handler.PermissionHandler;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.service.IFlwTaskAssigneeService;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 鍔炵悊浜烘潈闄愬鐞嗗櫒
+ *
+ * @author AprilWind
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@Component
+@Slf4j
+public class WorkflowPermissionHandler implements PermissionHandler {
+
+ private final IFlwTaskAssigneeService flwTaskAssigneeService;
+
+ /**
+ * 鍔炵悊浜烘潈闄愭爣璇嗭紝姣斿鐢ㄦ埛锛岃鑹诧紝閮ㄩ棬绛夛紝鐢ㄤ簬鏍¢獙鏄惁鏈夋潈闄愬姙鐞嗕换鍔�
+ * 鍚庣画鍦▄@link FlowParams#getPermissionFlag} 涓幏鍙�
+ * 杩斿洖褰撳墠鐢ㄦ埛鏉冮檺闆嗗悎
+ */
+ @Override
+ public List<String> permissions() {
+ return Collections.singletonList(LoginHelper.getUserIdStr());
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鍔炵悊浜�
+ *
+ * @return 褰撳墠鍔炵悊浜�
+ */
+ @Override
+ public String getHandler() {
+ return LoginHelper.getUserIdStr();
+ }
+
+ /**
+ * 杞崲鍔炵悊浜猴紝姣斿璁捐鍣ㄤ腑棰勮浜嗚兘鍔炵悊鐨勪汉锛屽鏋滃叾涓寘鍚鑹叉垨鑰呴儴闂╥d绛夛紝鍙互閫氳繃姝ゆ帴鍙h繘琛岃浆鎹㈡垚鐢ㄦ埛id
+ */
+ @Override
+ public List<String> convertPermissions(List<String> permissions) {
+ if (CollUtil.isEmpty(permissions)) {
+ return permissions;
+ }
+ String storageIds = CollUtil.join(permissions, StringUtils.SEPARATOR);
+ List<UserDTO> users = flwTaskAssigneeService.fetchUsersByStorageIds(storageIds);
+ return StreamUtils.toList(users, userDTO -> Convert.toStr(userDTO.getUserId()));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java
new file mode 100755
index 0000000..efd24ff
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java
@@ -0,0 +1,272 @@
+package org.dromara.workflow.listener;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.TypeReference;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.enums.BusinessStatusEnum;
+import org.dromara.common.core.service.UserService;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.warm.flow.core.FlowEngine;
+import org.dromara.warm.flow.core.dto.FlowParams;
+import org.dromara.warm.flow.core.entity.Definition;
+import org.dromara.warm.flow.core.entity.Instance;
+import org.dromara.warm.flow.core.entity.Task;
+import org.dromara.warm.flow.core.listener.GlobalListener;
+import org.dromara.warm.flow.core.listener.ListenerVariable;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.common.enums.TaskStatusEnum;
+import org.dromara.workflow.domain.bo.FlowCopyBo;
+import org.dromara.workflow.domain.vo.NodeExtVo;
+import org.dromara.workflow.handler.FlowProcessEventHandler;
+import org.dromara.workflow.service.IFlwCommonService;
+import org.dromara.workflow.service.IFlwInstanceService;
+import org.dromara.workflow.service.IFlwNodeExtService;
+import org.dromara.workflow.service.IFlwTaskService;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 鍏ㄥ眬浠诲姟鍔炵悊鐩戝惉
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class WorkflowGlobalListener implements GlobalListener {
+
+ private final IFlwTaskService flwTaskService;
+ private final IFlwInstanceService flwInstanceService;
+ private final FlowProcessEventHandler flowProcessEventHandler;
+ private final IFlwCommonService flwCommonService;
+ private final IFlwNodeExtService nodeExtService;
+ private final UserService userService;
+
+ /**
+ * 鍒涘缓鐩戝惉鍣紝浠诲姟鍒涘缓鏃舵墽琛�
+ *
+ * @param listenerVariable 鐩戝惉鍣ㄥ彉閲�
+ */
+ @Override
+ public void create(ListenerVariable listenerVariable) {
+
+ }
+
+ /**
+ * 寮�濮嬬洃鍚櫒锛屼换鍔″紑濮嬪姙鐞嗘椂鎵ц
+ *
+ * @param listenerVariable 鐩戝惉鍣ㄥ彉閲�
+ */
+ @Override
+ public void start(ListenerVariable listenerVariable) {
+ String ext = listenerVariable.getNode().getExt();
+ if (StringUtils.isNotBlank(ext)) {
+ Map<String, Object> variable = listenerVariable.getVariable();
+ if (CollUtil.isEmpty(variable)) {
+ variable = new HashMap<>();
+ }
+ NodeExtVo nodeExt = nodeExtService.parseNodeExt(ext, variable);
+ Set<String> copyList = nodeExt.getCopySettings();
+ if (CollUtil.isNotEmpty(copyList)) {
+ List<FlowCopyBo> list = StreamUtils.toList(copyList, x -> {
+ FlowCopyBo bo = new FlowCopyBo();
+ Long id = Convert.toLong(x);
+ bo.setUserId(id);
+ bo.setUserName(userService.selectUserNameById(id));
+ return bo;
+ });
+ variable.put(FlowConstant.FLOW_COPY_LIST, list);
+ }
+ if (CollUtil.isNotEmpty(nodeExt.getVariables())) {
+ variable.putAll(nodeExt.getVariables());
+ }
+ }
+ }
+
+ /**
+ * 鍒嗘淳鐩戝惉鍣紝鍔ㄦ�佷慨鏀逛唬鍔炰换鍔′俊鎭�
+ *
+ * @param listenerVariable 鐩戝惉鍣ㄥ彉閲�
+ */
+ @Override
+ public void assignment(ListenerVariable listenerVariable) {
+ Map<String, Object> variable = listenerVariable.getVariable();
+ List<Task> nextTasks = listenerVariable.getNextTasks();
+ FlowParams flowParams = listenerVariable.getFlowParams();
+ Definition definition = listenerVariable.getDefinition();
+ Instance instance = listenerVariable.getInstance();
+ String applyNodeCode = flwCommonService.applyNodeCode(definition.getId());
+ String hisStatus = flowParams != null ? flowParams.getHisStatus() : null;
+
+ for (Task flowTask : nextTasks) {
+ String nodeCode = flowTask.getNodeCode();
+
+ // 澶勭悊鍔炵悊鎴栭��鍥炴椂鎸囧畾鍔炵悊浜虹殑鎯呭喌
+ if (TaskStatusEnum.PASS.getStatus().equals(hisStatus)) {
+ processTaskPermission(variable, flowTask, hisStatus);
+ } else if (TaskStatusEnum.BACK.getStatus().equals(hisStatus)) {
+ processTaskPermission(variable, flowTask, hisStatus);
+ }
+
+ // 濡傛灉鏄敵璇疯妭鐐癸紝鍒欐妸鍚姩浜烘坊鍔犲埌鍔炵悊浜�
+ if (nodeCode.equals(applyNodeCode) && StringUtils.isNotBlank(instance.getCreateBy())) {
+ flowTask.setPermissionList(List.of(instance.getCreateBy()));
+ }
+ }
+ }
+
+ /**
+ * 澶勭悊浠诲姟鏉冮檺璁剧疆
+ *
+ * @param variable 鍙橀噺闆嗗悎
+ * @param flowTask 娴佺▼浠诲姟
+ * @param taskStatus 浠诲姟鐘舵��
+ */
+ private void processTaskPermission(Map<String, Object> variable, Task flowTask, String taskStatus) {
+ String nodeKey = taskStatus + StrUtil.COLON + flowTask.getNodeCode();
+
+ // 妫�鏌ユ槸鍚﹀瓨鍦ㄧ姸鎬佺浉鍏崇殑鍙橀噺
+ if (!variable.containsKey(nodeKey)) {
+ return;
+ }
+
+ // 鑾峰彇鐢ㄦ埛ID瀛楃涓�
+ Object userIdsObj = variable.get(nodeKey);
+ if (userIdsObj == null) {
+ return;
+ }
+
+ String userIds = userIdsObj.toString();
+ if (StringUtils.isBlank(userIds)) {
+ return;
+ }
+
+ // 鍒嗗壊鐢ㄦ埛ID骞惰缃潈闄愬垪琛�
+ String[] userIdArray = userIds.split(StringUtils.SEPARATOR);
+ if (userIdArray.length > 0) {
+ flowTask.setPermissionList(List.of(userIdArray));
+ // 绉婚櫎宸插鐞嗙殑鐘舵�佸彉閲�
+ variable.remove(nodeKey);
+ FlowEngine.insService().removeVariables(flowTask.getInstanceId(),nodeKey);
+ }
+ }
+
+ /**
+ * 瀹屾垚鐩戝惉鍣紝褰撳墠浠诲姟瀹屾垚鍚庢墽琛�
+ *
+ * @param listenerVariable 鐩戝惉鍣ㄥ彉閲�
+ */
+ @Override
+ public void finish(ListenerVariable listenerVariable) {
+ Instance instance = listenerVariable.getInstance();
+ Definition definition = listenerVariable.getDefinition();
+ Task task = listenerVariable.getTask();
+ List<Task> nextTasks = listenerVariable.getNextTasks();
+ Map<String, Object> params = new HashMap<>();
+ FlowParams flowParams = listenerVariable.getFlowParams();
+ Map<String, Object> variable = new HashMap<>();
+ if (ObjectUtil.isNotNull(flowParams)) {
+ // 鍘嗗彶浠诲姟鎵╁睍(閫氬父涓洪檮浠�)
+ params.put("hisTaskExt", flowParams.getHisTaskExt());
+ // 鍔炵悊浜�
+ params.put("handler", flowParams.getHandler());
+ // 鍔炵悊鎰忚
+ params.put("message", flowParams.getMessage());
+ variable = flowParams.getVariable();
+ }
+ //鐢宠浜烘彁浜や簨浠�
+ Boolean submit = MapUtil.getBool(variable, FlowConstant.SUBMIT);
+ if (submit != null && submit) {
+ String status = determineFlowStatus(instance);
+ flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, status, variable, true);
+ } else {
+ // 鍒ゆ柇娴佺▼鐘舵�侊紙鍙戝竷锛氭挙閿�锛岄��鍥烇紝浣滃簾锛岀粓姝紝宸插畬鎴愪簨浠讹級
+ String status = determineFlowStatus(instance);
+ if (StringUtils.isNotBlank(status)) {
+ flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, status, params, false);
+ }
+ if (!BusinessStatusEnum.initialState(instance.getFlowStatus())) {
+ if (task != null && CollUtil.isNotEmpty(nextTasks) && nextTasks.size() == 1
+ && flwCommonService.applyNodeCode(definition.getId()).equals(nextTasks.get(0).getNodeCode())) {
+ // 濡傛灉涓虹敾绾挎寚瀹氶┏鍥� 绾挎潯鎸囧畾涓洪┏鍥� 椹冲洖寰楄妭鐐逛负鐢宠浜鸿妭鐐� 鍒欎慨鏀规祦绋嬬姸鎬佷负閫�鍥�
+ flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, BusinessStatusEnum.BACK.getStatus(), params, false);
+ // 淇敼娴佺▼瀹炰緥鐘舵��
+ instance.setFlowStatus(BusinessStatusEnum.BACK.getStatus());
+ FlowEngine.insService().updateById(instance);
+ }
+ }
+ }
+ //鍙戝竷浠诲姟浜嬩欢
+ if (CollUtil.isNotEmpty(nextTasks)) {
+ for (Task nextTask : nextTasks) {
+ flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, nextTask, params);
+ }
+ }
+ if (ObjectUtil.isNull(flowParams)) {
+ return;
+ }
+ // 鍙湁鍔炵悊鎴栬�呴��鍥炵殑鏃跺�欐墠鎵ц娑堟伅閫氱煡鍜屾妱閫�
+ if (!TaskStatusEnum.isPassOrBack(flowParams.getHisStatus())) {
+ return;
+ }
+ if (ObjectUtil.isNull(variable)) {
+ return;
+ }
+
+ if (variable.containsKey(FlowConstant.FLOW_COPY_LIST)) {
+ List<FlowCopyBo> flowCopyList = MapUtil.get(variable, FlowConstant.FLOW_COPY_LIST, new TypeReference<>() {
+ });
+ // 娣诲姞鎶勯�佷汉
+ flwTaskService.setCopy(task, flowCopyList);
+ }
+ if (variable.containsKey(FlowConstant.MESSAGE_TYPE)) {
+ List<String> messageType = MapUtil.get(variable, FlowConstant.MESSAGE_TYPE, new TypeReference<>() {
+ });
+ String notice = MapUtil.getStr(variable, FlowConstant.MESSAGE_NOTICE);
+ flwCommonService.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice);
+ }
+ FlowEngine.insService().removeVariables(instance.getId(),
+ FlowConstant.FLOW_COPY_LIST,
+ FlowConstant.MESSAGE_TYPE,
+ FlowConstant.MESSAGE_NOTICE,
+ FlowConstant.SUBMIT
+ );
+ }
+
+ /**
+ * 鏍规嵁娴佺▼瀹炰緥纭畾鏈�缁堢姸鎬�
+ *
+ * @param instance 娴佺▼瀹炰緥
+ * @return 娴佺▼鏈�缁堢姸鎬�
+ */
+ private String determineFlowStatus(Instance instance) {
+ String flowStatus = instance.getFlowStatus();
+ if (StringUtils.isNotBlank(flowStatus) && BusinessStatusEnum.initialState(flowStatus)) {
+ log.info("娴佺▼瀹炰緥褰撳墠鐘舵��: {}", flowStatus);
+ return flowStatus;
+ } else {
+ Long instanceId = instance.getId();
+ if (flwTaskService.isTaskEnd(instanceId)) {
+ String status = BusinessStatusEnum.FINISH.getStatus();
+ // 鏇存柊娴佺▼鐘舵�佷负宸插畬鎴�
+ flwInstanceService.updateStatus(instanceId, status);
+ log.info("娴佺▼宸茬粨鏉燂紝鐘舵�佹洿鏂颁负: {}", status);
+ return status;
+ }
+ return null;
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwCategoryMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwCategoryMapper.java
new file mode 100755
index 0000000..7199d5c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwCategoryMapper.java
@@ -0,0 +1,49 @@
+package org.dromara.workflow.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.dromara.common.mybatis.annotation.DataColumn;
+import org.dromara.common.mybatis.annotation.DataPermission;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.common.mybatis.helper.DataBaseHelper;
+import org.dromara.workflow.domain.FlowCategory;
+import org.dromara.workflow.domain.vo.FlowCategoryVo;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 娴佺▼鍒嗙被Mapper鎺ュ彛
+ *
+ * @author may
+ * @date 2023-06-27
+ */
+public interface FlwCategoryMapper extends BaseMapperPlus<FlowCategory, FlowCategoryVo> {
+
+ /**
+ * 鏍规嵁鐖舵祦绋嬪垎绫籌D鏌ヨ鍏舵墍鏈夊瓙娴佺▼鍒嗙被鐨勫垪琛�
+ *
+ * @param parentId 鐖舵祦绋嬪垎绫籌D
+ * @return 鍖呭惈瀛愭祦绋嬪垎绫荤殑鍒楄〃
+ */
+ default List<FlowCategory> selectListByParentId(Long parentId) {
+ return this.selectList(new LambdaQueryWrapper<FlowCategory>()
+ .select(FlowCategory::getCategoryId)
+ .apply(DataBaseHelper.findInSet(parentId, "ancestors")));
+ }
+
+ /**
+ * 鏍规嵁鐖舵祦绋嬪垎绫籌D鏌ヨ鍖呮嫭鐖禝D鍙婂叾鎵�鏈夊瓙娴佺▼鍒嗙被ID鐨勫垪琛�
+ *
+ * @param parentId 鐖舵祦绋嬪垎绫籌D
+ * @return 鍖呭惈鐖禝D鍜屽瓙娴佺▼鍒嗙被ID鐨勫垪琛�
+ */
+ default List<Long> selectCategoryIdsByParentId(Long parentId) {
+ return Stream.concat(
+ this.selectListByParentId(parentId).stream()
+ .map(FlowCategory::getCategoryId),
+ Stream.of(parentId)
+ ).collect(Collectors.toList());
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceBizExtMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceBizExtMapper.java
new file mode 100755
index 0000000..e11613c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceBizExtMapper.java
@@ -0,0 +1,61 @@
+package org.dromara.workflow.mapper;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.workflow.domain.FlowInstanceBizExt;
+
+import java.util.List;
+
+/**
+ * 娴佺▼瀹炰緥涓氬姟鎵╁睍Mapper鎺ュ彛
+ *
+ * @author may
+ * @date 2025-08-05
+ */
+public interface FlwInstanceBizExtMapper extends BaseMapperPlus<FlowInstanceBizExt, FlowInstanceBizExt> {
+
+ /**
+ * 鏍规嵁 instanceId 淇濆瓨鎴栨洿鏂版祦绋嬪疄渚嬩笟鍔℃墿灞�
+ *
+ * @param entity 娴佺▼瀹炰緥涓氬姟鎵╁睍瀹炰綋
+ * @return 鎿嶄綔鏄惁鎴愬姛
+ */
+ default int saveOrUpdateByInstanceId(FlowInstanceBizExt entity) {
+ // 鏌ヨ鏄惁瀛樺湪
+ FlowInstanceBizExt exist = this.selectOne(new LambdaQueryWrapper<FlowInstanceBizExt>()
+ .eq(FlowInstanceBizExt::getInstanceId, entity.getInstanceId()));
+
+ if (ObjectUtil.isNotNull(exist)) {
+ // 瀛樺湪灏卞甫涓婁富閿洿鏂�
+ entity.setId(exist.getId());
+ return updateById(entity);
+ } else {
+ // 涓嶅瓨鍦ㄥ氨鎻掑叆
+ return insert(entity);
+ }
+ }
+
+ /**
+ * 鎸夌収娴佺▼瀹炰緥ID鍒犻櫎鍗曚釜娴佺▼瀹炰緥涓氬姟鎵╁睍
+ *
+ * @param instanceId 娴佺▼瀹炰緥ID
+ * @return 鍒犻櫎鐨勮鏁�
+ */
+ default int deleteByInstId(Long instanceId) {
+ return this.delete(new LambdaQueryWrapper<FlowInstanceBizExt>()
+ .eq(FlowInstanceBizExt::getInstanceId, instanceId));
+ }
+
+ /**
+ * 鎸夌収娴佺▼瀹炰緥ID鎵归噺鍒犻櫎娴佺▼瀹炰緥涓氬姟鎵╁睍
+ *
+ * @param instanceIds 娴佺▼瀹炰緥ID鍒楄〃
+ * @return 鍒犻櫎鐨勮鏁�
+ */
+ default int deleteByInstIds(List<Long> instanceIds) {
+ return this.delete(new LambdaQueryWrapper<FlowInstanceBizExt>()
+ .in(FlowInstanceBizExt::getInstanceId, instanceIds));
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceMapper.java
new file mode 100755
index 0000000..92809c8
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceMapper.java
@@ -0,0 +1,27 @@
+package org.dromara.workflow.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+import org.dromara.workflow.domain.bo.FlowInstanceBo;
+import org.dromara.workflow.domain.vo.FlowInstanceVo;
+
+/**
+ * 瀹炰緥淇℃伅Mapper鎺ュ彛
+ *
+ * @author may
+ * @date 2024-03-02
+ */
+public interface FlwInstanceMapper {
+
+ /**
+ * 娴佺▼瀹炰緥淇℃伅
+ *
+ * @param page 鍒嗛〉
+ * @param queryWrapper 鏉′欢
+ * @return 缁撴灉
+ */
+ Page<FlowInstanceVo> selectInstanceList(@Param("page") Page<FlowInstanceVo> page, @Param(Constants.WRAPPER) Wrapper<FlowInstanceBo> queryWrapper);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwSpelMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwSpelMapper.java
new file mode 100755
index 0000000..12dff9f
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwSpelMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.workflow.mapper;
+
+import org.dromara.workflow.domain.FlowSpel;
+import org.dromara.workflow.domain.vo.FlowSpelVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 娴佺▼spel琛ㄨ揪寮忓畾涔塎apper鎺ュ彛
+ *
+ * @author Michelle.Chung
+ * @date 2025-07-04
+ */
+public interface FlwSpelMapper extends BaseMapperPlus<FlowSpel, FlowSpelVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwTaskMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwTaskMapper.java
new file mode 100755
index 0000000..0d0422d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwTaskMapper.java
@@ -0,0 +1,47 @@
+package org.dromara.workflow.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+import org.dromara.workflow.domain.bo.FlowTaskBo;
+import org.dromara.workflow.domain.vo.FlowHisTaskVo;
+import org.dromara.workflow.domain.vo.FlowTaskVo;
+
+
+/**
+ * 浠诲姟淇℃伅Mapper鎺ュ彛
+ *
+ * @author may
+ * @date 2024-03-02
+ */
+public interface FlwTaskMapper {
+
+ /**
+ * 鑾峰彇寰呭姙淇℃伅
+ *
+ * @param page 鍒嗛〉
+ * @param queryWrapper 鏉′欢
+ * @return 缁撴灉
+ */
+ Page<FlowTaskVo> getListRunTask(@Param("page") Page<FlowTaskVo> page, @Param(Constants.WRAPPER) Wrapper<FlowTaskBo> queryWrapper);
+
+ /**
+ * 鑾峰彇宸插姙
+ *
+ * @param page 鍒嗛〉
+ * @param queryWrapper 鏉′欢
+ * @return 缁撴灉
+ */
+ Page<FlowHisTaskVo> getListFinishTask(@Param("page") Page<FlowTaskVo> page, @Param(Constants.WRAPPER) Wrapper<FlowTaskBo> queryWrapper);
+
+ /**
+ * 鏌ヨ褰撳墠鐢ㄦ埛鐨勬妱閫�
+ *
+ * @param page 鍒嗛〉
+ * @param queryWrapper 鏉′欢
+ * @return 缁撴灉
+ */
+ Page<FlowTaskVo> getTaskCopyByPage(@Param("page") Page<FlowTaskVo> page, @Param(Constants.WRAPPER) QueryWrapper<FlowTaskBo> queryWrapper);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/TestLeaveMapper.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/TestLeaveMapper.java
new file mode 100755
index 0000000..cd1edba
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/TestLeaveMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.workflow.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.workflow.domain.TestLeave;
+import org.dromara.workflow.domain.vo.TestLeaveVo;
+
+/**
+ * 璇峰亣Mapper鎺ュ彛
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+public interface TestLeaveMapper extends BaseMapperPlus<TestLeave, TestLeaveVo> {
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/rule/SpelRuleComponent.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/rule/SpelRuleComponent.java
new file mode 100755
index 0000000..7498db5
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/rule/SpelRuleComponent.java
@@ -0,0 +1,38 @@
+package org.dromara.workflow.rule;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.DeptService;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.springframework.stereotype.Component;
+
+/**
+ * spel琛ㄨ揪寮忚鍒欑粍浠�
+ * <p>
+ * 閫氳繃璇ョ粍浠剁粺涓�绠$悊娴佺▼瀹氫箟涓殑spel琛ㄨ揪寮�
+ * </p>
+ *
+ * @author Michelle.Chung
+ */
+@ConditionalOnEnable
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SpelRuleComponent {
+
+ private final DeptService deptService;
+
+ /**
+ * 閫氳繃鍙戣捣浜洪儴闂╥d鑾峰彇閮ㄩ棬璐熻矗浜�
+ */
+ public Long selectDeptLeaderById(Long initiatorDeptId) {
+ Long leaderId = deptService.selectDeptLeaderById(initiatorDeptId);
+ if (ObjectUtil.isNull(leaderId)) {
+ throw new ServiceException("褰撳墠閮ㄩ棬鏈缃礋璐d汉锛岃鑱旂郴绠$悊鍛樻搷浣溿��");
+ }
+ return leaderId;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCategoryService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCategoryService.java
new file mode 100755
index 0000000..f66882b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCategoryService.java
@@ -0,0 +1,95 @@
+package org.dromara.workflow.service;
+
+import cn.hutool.core.lang.tree.Tree;
+import org.dromara.workflow.domain.bo.FlowCategoryBo;
+import org.dromara.workflow.domain.vo.FlowCategoryVo;
+
+import java.util.List;
+
+/**
+ * 娴佺▼鍒嗙被Service鎺ュ彛
+ *
+ * @author may
+ */
+public interface IFlwCategoryService {
+
+ /**
+ * 鏌ヨ娴佺▼鍒嗙被
+ *
+ * @param categoryId 涓婚敭
+ * @return 娴佺▼鍒嗙被
+ */
+ FlowCategoryVo queryById(Long categoryId);
+
+ /**
+ * 鏍规嵁娴佺▼鍒嗙被ID鏌ヨ娴佺▼鍒嗙被鍚嶇О
+ *
+ * @param categoryId 娴佺▼鍒嗙被ID
+ * @return 娴佺▼鍒嗙被鍚嶇О
+ */
+ String selectCategoryNameById(Long categoryId);
+
+ /**
+ * 鏌ヨ绗﹀悎鏉′欢鐨勬祦绋嬪垎绫诲垪琛�
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return 娴佺▼鍒嗙被鍒楄〃
+ */
+ List<FlowCategoryVo> queryList(FlowCategoryBo bo);
+
+ /**
+ * 鏌ヨ娴佺▼鍒嗙被鏍戠粨鏋勪俊鎭�
+ *
+ * @param category 娴佺▼鍒嗙被淇℃伅
+ * @return 娴佺▼鍒嗙被鏍戜俊鎭泦鍚�
+ */
+ List<Tree<String>> selectCategoryTreeList(FlowCategoryBo category);
+
+ /**
+ * 鏍¢獙娴佺▼鍒嗙被鍚嶇О鏄惁鍞竴
+ *
+ * @param category 娴佺▼鍒嗙被淇℃伅
+ * @return 缁撴灉
+ */
+ boolean checkCategoryNameUnique(FlowCategoryBo category);
+
+ /**
+ * 鏌ヨ娴佺▼鍒嗙被鏄惁瀛樺湪娴佺▼瀹氫箟
+ *
+ * @param categoryId 娴佺▼鍒嗙被ID
+ * @return 缁撴灉 true 瀛樺湪 false 涓嶅瓨鍦�
+ */
+ boolean checkCategoryExistDefinition(Long categoryId);
+
+ /**
+ * 鏄惁瀛樺湪娴佺▼鍒嗙被瀛愯妭鐐�
+ *
+ * @param categoryId 娴佺▼鍒嗙被ID
+ * @return 缁撴灉
+ */
+ boolean hasChildByCategoryId(Long categoryId);
+
+ /**
+ * 鏂板娴佺▼鍒嗙被
+ *
+ * @param bo 娴佺▼鍒嗙被
+ * @return 鏄惁鏂板鎴愬姛
+ */
+ int insertByBo(FlowCategoryBo bo);
+
+ /**
+ * 淇敼娴佺▼鍒嗙被
+ *
+ * @param bo 娴佺▼鍒嗙被
+ * @return 鏄惁淇敼鎴愬姛
+ */
+ int updateByBo(FlowCategoryBo bo);
+
+ /**
+ * 鍒犻櫎娴佺▼鍒嗙被淇℃伅
+ *
+ * @param categoryId 涓婚敭
+ * @return 鏄惁鍒犻櫎鎴愬姛
+ */
+ int deleteWithValidById(Long categoryId);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java
new file mode 100755
index 0000000..f809f47
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java
@@ -0,0 +1,41 @@
+package org.dromara.workflow.service;
+
+import org.dromara.common.core.domain.dto.UserDTO;
+
+import java.util.List;
+
+/**
+ * 閫氱敤 宸ヤ綔娴佹湇鍔�
+ *
+ * @author LionLi
+ */
+public interface IFlwCommonService {
+
+ /**
+ * 鍙戦�佹秷鎭�
+ *
+ * @param flowName 娴佺▼瀹氫箟鍚嶇О
+ * @param instId 瀹炰緥id
+ * @param messageType 娑堟伅绫诲瀷
+ * @param message 娑堟伅鍐呭锛屼负绌哄垯鍙戦�侀粯璁ら厤缃殑娑堟伅鍐呭
+ */
+ void sendMessage(String flowName, Long instId, List<String> messageType, String message);
+
+ /**
+ * 鍙戦�佹秷鎭�
+ *
+ * @param messageType 娑堟伅绫诲瀷
+ * @param message 娑堟伅鍐呭
+ * @param subject 閭欢鏍囬
+ * @param userList 鎺ユ敹鐢ㄦ埛
+ */
+ void sendMessage(List<String> messageType, String message, String subject, List<UserDTO> userList);
+
+ /**
+ * 鐢宠浜鸿妭鐐圭紪鐮�
+ *
+ * @param definitionId 娴佺▼瀹氫箟id
+ * @return 鐢宠浜鸿妭鐐圭紪鐮�
+ */
+ String applyNodeCode(Long definitionId);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwDefinitionService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwDefinitionService.java
new file mode 100755
index 0000000..54743b7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwDefinitionService.java
@@ -0,0 +1,78 @@
+package org.dromara.workflow.service;
+
+import jakarta.servlet.http.HttpServletResponse;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.warm.flow.orm.entity.FlowDefinition;
+import org.dromara.workflow.domain.vo.FlowDefinitionVo;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 娴佺▼瀹氫箟 鏈嶅姟灞�
+ *
+ * @author may
+ */
+public interface IFlwDefinitionService {
+
+ /**
+ * 鏌ヨ娴佺▼瀹氫箟鍒楄〃
+ *
+ * @param flowDefinition 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ * @return 杩斿洖鍒嗛〉鍒楄〃
+ */
+ TableDataInfo<FlowDefinitionVo> queryList(FlowDefinition flowDefinition, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ鏈彂甯冪殑娴佺▼瀹氫箟鍒楄〃
+ *
+ * @param flowDefinition 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ * @return 杩斿洖鍒嗛〉鍒楄〃
+ */
+ TableDataInfo<FlowDefinitionVo> unPublishList(FlowDefinition flowDefinition, PageQuery pageQuery);
+
+ /**
+ * 鍙戝竷娴佺▼瀹氫箟
+ *
+ * @param id 娴佺▼瀹氫箟id
+ * @return 缁撴灉
+ */
+ boolean publish(Long id);
+
+ /**
+ * 瀵煎嚭娴佺▼瀹氫箟
+ *
+ * @param id 娴佺▼瀹氫箟id
+ * @param response 鍝嶅簲
+ * @throws IOException 寮傚父
+ */
+ void exportDef(Long id, HttpServletResponse response) throws IOException;
+
+ /**
+ * 瀵煎叆娴佺▼瀹氫箟
+ *
+ * @param file 鏂囦欢
+ * @param category 鍒嗙被
+ * @return 缁撴灉
+ */
+ boolean importJson(MultipartFile file, String category);
+
+ /**
+ * 鍒犻櫎娴佺▼瀹氫箟
+ *
+ * @param ids 娴佺▼瀹氫箟id
+ * @return 缁撴灉
+ */
+ boolean removeDef(List<Long> ids);
+
+ /**
+ * 鏂板绉熸埛娴佺▼瀹氫箟
+ *
+ * @param tenantId 绉熸埛id
+ */
+ void syncDef(String tenantId);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java
new file mode 100755
index 0000000..814b89d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java
@@ -0,0 +1,168 @@
+package org.dromara.workflow.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.warm.flow.orm.entity.FlowInstance;
+import org.dromara.workflow.domain.bo.FlowCancelBo;
+import org.dromara.workflow.domain.bo.FlowInstanceBo;
+import org.dromara.workflow.domain.bo.FlowInvalidBo;
+import org.dromara.workflow.domain.bo.FlowVariableBo;
+import org.dromara.workflow.domain.vo.FlowInstanceVo;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 娴佺▼瀹炰緥 鏈嶅姟灞�
+ *
+ * @author may
+ */
+public interface IFlwInstanceService {
+
+ /**
+ * 鍒嗛〉鏌ヨ姝e湪杩愯鐨勬祦绋嬪疄渚�
+ *
+ * @param flowInstanceBo 娴佺▼瀹炰緥
+ * @param pageQuery 鍒嗛〉
+ * @return 缁撴灉
+ */
+ TableDataInfo<FlowInstanceVo> selectRunningInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery);
+
+ /**
+ * 鍒嗛〉鏌ヨ宸茬粨鏉熺殑娴佺▼瀹炰緥
+ *
+ * @param flowInstanceBo 娴佺▼瀹炰緥
+ * @param pageQuery 鍒嗛〉
+ * @return 缁撴灉
+ */
+ TableDataInfo<FlowInstanceVo> selectFinishInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery);
+
+ /**
+ * 鏍规嵁涓氬姟id鏌ヨ娴佺▼瀹炰緥璇︾粏淇℃伅
+ *
+ * @param businessId 涓氬姟id
+ * @return 缁撴灉
+ */
+ FlowInstanceVo queryByBusinessId(Long businessId);
+
+ /**
+ * 鎸夌収涓氬姟id鏌ヨ娴佺▼瀹炰緥
+ *
+ * @param businessId 涓氬姟id
+ * @return 缁撴灉
+ */
+ FlowInstance selectInstByBusinessId(String businessId);
+
+ /**
+ * 鎸夌収瀹炰緥id鏌ヨ娴佺▼瀹炰緥
+ *
+ * @param instanceId 瀹炰緥id
+ * @return 缁撴灉
+ */
+ FlowInstance selectInstById(Long instanceId);
+
+ /**
+ * 鎸夌収瀹炰緥id鏌ヨ娴佺▼瀹炰緥
+ *
+ * @param instanceIds 瀹炰緥id
+ * @return 缁撴灉
+ */
+ List<FlowInstance> selectInstListByIdList(List<Long> instanceIds);
+
+ /**
+ * 鎸夌収涓氬姟id鍒犻櫎娴佺▼瀹炰緥
+ *
+ * @param businessIds 涓氬姟id
+ * @return 缁撴灉
+ */
+ boolean deleteByBusinessIds(List<String> businessIds);
+
+ /**
+ * 鎸夌収瀹炰緥id鍒犻櫎娴佺▼瀹炰緥
+ *
+ * @param instanceIds 瀹炰緥id
+ * @return 缁撴灉
+ */
+ boolean deleteByInstanceIds(List<Long> instanceIds);
+
+ /**
+ * 鎸夌収瀹炰緥id鍒犻櫎宸插畬鎴愬緱娴佺▼瀹炰緥
+ *
+ * @param instanceIds 鍒犻櫎鐨勫疄渚媔d
+ * @return 鍒犻櫎缁撴灉
+ */
+ boolean deleteHisByInstanceIds(List<Long> instanceIds);
+
+ /**
+ * 鎾ら攢娴佺▼
+ *
+ * @param bo 鍙傛暟
+ * @return 缁撴灉
+ */
+ boolean cancelProcessApply(FlowCancelBo bo);
+
+ /**
+ * 鑾峰彇褰撳墠鐧婚檰浜哄彂璧风殑娴佺▼瀹炰緥
+ *
+ * @param instanceBo 娴佺▼瀹炰緥
+ * @param pageQuery 鍒嗛〉
+ * @return 缁撴灉
+ */
+ TableDataInfo<FlowInstanceVo> selectCurrentInstanceList(FlowInstanceBo instanceBo, PageQuery pageQuery);
+
+ /**
+ * 鑾峰彇娴佺▼鍥�,娴佺▼璁板綍
+ *
+ * @param businessId 涓氬姟id
+ * @return 缁撴灉
+ */
+ Map<String, Object> flowHisTaskList(String businessId);
+
+ /**
+ * 鎸夌収瀹炰緥id鏇存柊鐘舵��
+ *
+ * @param instanceId 瀹炰緥id
+ * @param status 鐘舵��
+ */
+ void updateStatus(Long instanceId, String status);
+
+ /**
+ * 鑾峰彇娴佺▼鍙橀噺
+ *
+ * @param instanceId 瀹炰緥id
+ * @return 缁撴灉
+ */
+ Map<String, Object> instanceVariable(Long instanceId);
+
+ /**
+ * 鏇存柊娴佺▼鍙橀噺
+ *
+ * @param bo 鍙傛暟
+ * @return 缁撴灉
+ */
+ boolean updateVariable(FlowVariableBo bo);
+
+ /**
+ * 璁剧疆娴佺▼鍙橀噺
+ *
+ * @param instanceId 瀹炰緥id
+ * @param variable 娴佺▼鍙橀噺
+ */
+ void setVariable(Long instanceId, Map<String, Object> variable);
+
+ /**
+ * 鎸変换鍔d鏌ヨ瀹炰緥
+ *
+ * @param taskId 浠诲姟id
+ * @return 缁撴灉
+ */
+ FlowInstance selectByTaskId(Long taskId);
+
+ /**
+ * 浣滃簾娴佺▼
+ *
+ * @param bo 娴佺▼瀹炰緥
+ * @return 缁撴灉
+ */
+ boolean processInvalid(FlowInvalidBo bo);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java
new file mode 100755
index 0000000..a94a225
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java
@@ -0,0 +1,35 @@
+package org.dromara.workflow.service;
+
+import org.dromara.workflow.domain.vo.NodeExtVo;
+
+import java.util.Map;
+
+/**
+ * 娴佺▼鑺傜偣鎵╁睍灞炴�� 鏈嶅姟灞�
+ *
+ * @author AprilWind
+ */
+public interface IFlwNodeExtService {
+
+ /**
+ * 瑙f瀽鎵╁睍灞炴�� JSON 骞舵瀯寤� Node 鎵╁睍灞炴�у璞�
+ * <p>
+ * 鏍规嵁浼犲叆鐨� JSON 瀛楃涓诧紝灏嗘墿灞曞睘鎬у垎涓轰笁绫伙細
+ * 1. ButtonPermissionEnum锛氳В鏋愪负鎸夐挳鏉冮檺鍒楄〃锛屾爣璁版瘡涓寜閽槸鍚﹀嬀閫�
+ * 2. CopySettingEnum锛氳В鏋愪负鎶勯�佸璞� ID 闆嗗悎
+ * 3. VariablesEnum锛氳В鏋愪负鑷畾涔夊弬鏁� Map
+ *
+ * <p>绀轰緥 JSON锛�
+ * [
+ * {"code": "ButtonPermissionEnum", "value": "back,termination"},
+ * {"code": "CopySettingEnum", "value": "1"},
+ * {"code": "VariablesEnum", "value": "key1=value1,key2=value2"}
+ * ]
+ *
+ * @param ext 鎵╁睍灞炴�� JSON 瀛楃涓�
+ * @param variable 娴佺▼鍙橀噺
+ * @return NodeExtVo 瀵硅薄锛屽皝瑁呮寜閽潈闄愬垪琛ㄣ�佹妱閫佸璞¢泦鍚堝拰鑷畾涔夊弬鏁� Map
+ */
+ NodeExtVo parseNodeExt(String ext, Map<String, Object> variable);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwSpelService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwSpelService.java
new file mode 100755
index 0000000..7c44534
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwSpelService.java
@@ -0,0 +1,88 @@
+package org.dromara.workflow.service;
+
+import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
+import org.dromara.common.core.domain.model.TaskAssigneeBody;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.workflow.domain.bo.FlowSpelBo;
+import org.dromara.workflow.domain.vo.FlowSpelVo;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 娴佺▼spel琛ㄨ揪寮忓畾涔塖ervice鎺ュ彛
+ *
+ * @author Michelle.Chung
+ * @date 2025-07-04
+ */
+public interface IFlwSpelService {
+
+ /**
+ * 鏌ヨ娴佺▼spel琛ㄨ揪寮忓畾涔�
+ *
+ * @param id 涓婚敭
+ * @return 娴佺▼spel琛ㄨ揪寮忓畾涔�
+ */
+ FlowSpelVo queryById(Long id);
+
+ /**
+ * 鍒嗛〉鏌ヨ娴佺▼spel琛ㄨ揪寮忓畾涔夊垪琛�
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 娴佺▼spel琛ㄨ揪寮忓畾涔夊垎椤靛垪琛�
+ */
+ TableDataInfo<FlowSpelVo> queryPageList(FlowSpelBo bo, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ绗﹀悎鏉′欢鐨勬祦绋媠pel琛ㄨ揪寮忓畾涔夊垪琛�
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return 娴佺▼spel琛ㄨ揪寮忓畾涔夊垪琛�
+ */
+ List<FlowSpelVo> queryList(FlowSpelBo bo);
+
+ /**
+ * 鏂板娴佺▼spel琛ㄨ揪寮忓畾涔�
+ *
+ * @param bo 娴佺▼spel琛ㄨ揪寮忓畾涔�
+ * @return 鏄惁鏂板鎴愬姛
+ */
+ Boolean insertByBo(FlowSpelBo bo);
+
+ /**
+ * 淇敼娴佺▼spel琛ㄨ揪寮忓畾涔�
+ *
+ * @param bo 娴佺▼spel琛ㄨ揪寮忓畾涔�
+ * @return 鏄惁淇敼鎴愬姛
+ */
+ Boolean updateByBo(FlowSpelBo bo);
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄ゆ祦绋媠pel琛ㄨ揪寮忓畾涔変俊鎭�
+ *
+ * @param ids 寰呭垹闄ょ殑涓婚敭闆嗗悎
+ * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+ * @return 鏄惁鍒犻櫎鎴愬姛
+ */
+ Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+ /**
+ * 鏌ヨspel骞惰繑鍥炰换鍔℃寚娲剧殑鍒楄〃锛屾敮鎸佸垎椤�
+ *
+ * @param taskQuery 鏌ヨ鏉′欢
+ * @return 鍔炵悊浜�
+ */
+ TaskAssigneeDTO selectSpelByTaskAssigneeList(TaskAssigneeBody taskQuery);
+
+ /**
+ * 鏍规嵁瑙嗗浘 SpEL 琛ㄨ揪寮忓垪琛紝鏌ヨ瀵瑰簲鐨勫娉ㄤ俊鎭�
+ *
+ * @param viewSpels SpEL 琛ㄨ揪寮忓垪琛�
+ * @return 鏄犲皠琛細key 涓� SpEL 琛ㄨ揪寮忥紝value 涓哄搴斿娉紱鑻ヤ负绌哄垯杩斿洖绌� Map
+ */
+ Map<String, String> selectRemarksBySpels(List<String> viewSpels);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskAssigneeService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskAssigneeService.java
new file mode 100755
index 0000000..830abaf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskAssigneeService.java
@@ -0,0 +1,24 @@
+package org.dromara.workflow.service;
+
+import org.dromara.common.core.domain.dto.UserDTO;
+
+import java.util.List;
+
+/**
+ * 娴佺▼璁捐鍣�-鑾峰彇鍔炵悊浜�
+ *
+ * @author AprilWind
+ */
+public interface IFlwTaskAssigneeService {
+
+ /**
+ * 鎵归噺瑙f瀽澶氫釜瀛樺偍鏍囪瘑绗︼紙storageIds锛夛紝鎸夌被鍨嬪垎绫诲苟鍚堝苟鏌ヨ鐢ㄦ埛鍒楄〃
+ * 杈撳叆鏍煎紡鏀寔澶氫釜浠ラ�楀彿鍒嗛殧鐨勬爣璇嗭紙濡� "user:123,role:456,789"锛�
+ * 浼氳嚜鍔ㄥ幓閲嶈繑鍥炵粨鏋滐紝闈炴硶鏍煎紡鐨勬爣璇嗗皢琚拷鐣�
+ *
+ * @param storageIds 澶氫釜瀛樺偍鏍囪瘑绗﹀瓧绗︿覆锛堥�楀彿鍒嗛殧锛�
+ * @return 鍚堝苟鍚庣殑鐢ㄦ埛鍒楄〃锛屽幓閲嶅悗杩斿洖锛岄潪娉曟牸寮忕殑鏍囪瘑灏嗚璺宠繃
+ */
+ List<UserDTO> fetchUsersByStorageIds(String storageIds);
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java
new file mode 100755
index 0000000..f9dc45e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java
@@ -0,0 +1,217 @@
+package org.dromara.workflow.service;
+
+import org.dromara.common.core.domain.dto.StartProcessReturnDTO;
+import org.dromara.common.core.domain.dto.UserDTO;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.warm.flow.core.entity.Node;
+import org.dromara.warm.flow.core.entity.Task;
+import org.dromara.warm.flow.orm.entity.FlowHisTask;
+import org.dromara.warm.flow.orm.entity.FlowNode;
+import org.dromara.warm.flow.orm.entity.FlowTask;
+import org.dromara.workflow.domain.bo.*;
+import org.dromara.workflow.domain.vo.FlowHisTaskVo;
+import org.dromara.workflow.domain.vo.FlowTaskVo;
+
+import java.util.List;
+
+/**
+ * 浠诲姟 鏈嶅姟灞�
+ *
+ * @author may
+ */
+public interface IFlwTaskService {
+
+ /**
+ * 鍚姩浠诲姟
+ *
+ * @param startProcessBo 鍚姩娴佺▼鍙傛暟
+ * @return 缁撴灉
+ */
+ StartProcessReturnDTO startWorkFlow(StartProcessBo startProcessBo);
+
+ /**
+ * 鍔炵悊浠诲姟
+ *
+ * @param completeTaskBo 鍔炵悊浠诲姟鍙傛暟
+ * @return 缁撴灉
+ */
+ boolean completeTask(CompleteTaskBo completeTaskBo);
+
+ /**
+ * 娣诲姞鎶勯�佷汉
+ *
+ * @param task 浠诲姟淇℃伅
+ * @param flowCopyList 鎶勯�佷汉
+ */
+ void setCopy(Task task, List<FlowCopyBo> flowCopyList);
+
+ /**
+ * 鏌ヨ褰撳墠鐢ㄦ埛鐨勫緟鍔炰换鍔�
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ * @return 缁撴灉
+ */
+ TableDataInfo<FlowTaskVo> pageByTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ褰撳墠绉熸埛鎵�鏈夊緟鍔炰换鍔�
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ * @return 缁撴灉
+ */
+ TableDataInfo<FlowHisTaskVo> pageByTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ寰呭姙浠诲姟
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ * @return 缁撴灉
+ */
+ TableDataInfo<FlowTaskVo> pageByAllTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ宸插姙浠诲姟
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ * @return 缁撴灉
+ */
+ TableDataInfo<FlowHisTaskVo> pageByAllTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ褰撳墠鐢ㄦ埛鐨勬妱閫�
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ * @return 缁撴灉
+ */
+ TableDataInfo<FlowTaskVo> pageByTaskCopy(FlowTaskBo flowTaskBo, PageQuery pageQuery);
+
+ /**
+ * 淇敼浠诲姟鍔炵悊浜�
+ *
+ * @param taskIdList 浠诲姟id
+ * @param userId 鐢ㄦ埛id
+ * @return 缁撴灉
+ */
+ boolean updateAssignee(List<Long> taskIdList, String userId);
+
+ /**
+ * 椹冲洖瀹℃壒
+ *
+ * @param bo 鍙傛暟
+ * @return 缁撴灉
+ */
+ boolean backProcess(BackProcessBo bo);
+
+ /**
+ * 鑾峰彇鍙┏鍥炵殑鍓嶇疆鑺傜偣
+ *
+ * @param taskId 浠诲姟id
+ * @param nowNodeCode 褰撳墠鑺傜偣
+ * @return 缁撴灉
+ */
+ List<Node> getBackTaskNode(Long taskId, String nowNodeCode);
+
+ /**
+ * 缁堟浠诲姟
+ *
+ * @param bo 鍙傛暟
+ * @return 缁撴灉
+ */
+ boolean terminationTask(FlowTerminationBo bo);
+
+ /**
+ * 鎸夌収浠诲姟id鏌ヨ浠诲姟
+ *
+ * @param taskIdList 浠诲姟id
+ * @return 缁撴灉
+ */
+ List<FlowTask> selectByIdList(List<Long> taskIdList);
+
+ /**
+ * 鎸夌収浠诲姟id鏌ヨ浠诲姟
+ *
+ * @param taskId 浠诲姟id
+ * @return 缁撴灉
+ */
+ FlowTaskVo selectById(Long taskId);
+
+ /**
+ * 鑾峰彇涓嬩竴鑺傜偣淇℃伅
+ *
+ * @param bo 鍙傛暟
+ * @return 缁撴灉
+ */
+ List<FlowNode> getNextNodeList(FlowNextNodeBo bo);
+
+ /**
+ * 鎸夌収浠诲姟id鏌ヨ浠诲姟
+ *
+ * @param taskId 浠诲姟id
+ * @return 缁撴灉
+ */
+ FlowHisTask selectHisTaskById(Long taskId);
+
+ /**
+ * 鎸夌収瀹炰緥id鏌ヨ浠诲姟
+ *
+ * @param instanceId 娴佺▼瀹炰緥id
+ * @return 缁撴灉
+ */
+ List<FlowTask> selectByInstId(Long instanceId);
+
+ /**
+ * 鎸夌収瀹炰緥id鏌ヨ浠诲姟
+ *
+ * @param instanceIds 鍒楄〃
+ * @return 缁撴灉
+ */
+ List<FlowTask> selectByInstIds(List<Long> instanceIds);
+
+ /**
+ * 鍒ゆ柇娴佺▼鏄惁宸茬粨鏉燂紙鍗宠娴佺▼瀹炰緥涓嬫槸鍚﹁繕鏈夋湭瀹屾垚鐨勪换鍔★級
+ *
+ * @param instanceId 娴佺▼瀹炰緥ID
+ * @return true 琛ㄧず浠诲姟宸插叏閮ㄧ粨鏉燂紱false 琛ㄧず浠嶆湁浠诲姟瀛樺湪
+ */
+ boolean isTaskEnd(Long instanceId);
+
+ /**
+ * 浠诲姟鎿嶄綔
+ *
+ * @param bo 鍙傛暟
+ * @param taskOperation 鎿嶄綔绫诲瀷锛屽娲� delegateTask銆佽浆鍔� transferTask銆佸姞绛� addSignature銆佸噺绛� reductionSignature
+ * @return 缁撴灉
+ */
+ boolean taskOperation(TaskOperationBo bo, String taskOperation);
+
+ /**
+ * 鑾峰彇褰撳墠浠诲姟鐨勬墍鏈夊姙鐞嗕汉
+ *
+ * @param taskIds 浠诲姟id
+ * @return 缁撴灉
+ */
+ List<UserDTO> currentTaskAllUser(List<Long> taskIds);
+
+ /**
+ * 鎸夌収鑺傜偣缂栫爜鏌ヨ鑺傜偣
+ *
+ * @param nodeCode 鑺傜偣缂栫爜
+ * @param definitionId 娴佺▼瀹氫箟id
+ * @return 鑺傜偣
+ */
+ FlowNode getByNodeCode(String nodeCode, Long definitionId);
+
+ /**
+ * 鍌姙浠诲姟
+ *
+ * @param bo 鍙傛暟
+ * @return 缁撴灉
+ */
+ boolean urgeTask(FlowUrgeTaskBo bo);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java
new file mode 100755
index 0000000..748a2b1
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java
@@ -0,0 +1,52 @@
+package org.dromara.workflow.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.workflow.domain.bo.TestLeaveBo;
+import org.dromara.workflow.domain.vo.TestLeaveVo;
+
+import java.util.List;
+
+/**
+ * 璇峰亣Service鎺ュ彛
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+public interface ITestLeaveService {
+
+ /**
+ * 鏌ヨ璇峰亣
+ */
+ TestLeaveVo queryById(Long id);
+
+ /**
+ * 鏌ヨ璇峰亣鍒楄〃
+ */
+ TableDataInfo<TestLeaveVo> queryPageList(TestLeaveBo bo, PageQuery pageQuery);
+
+ /**
+ * 鏌ヨ璇峰亣鍒楄〃
+ */
+ List<TestLeaveVo> queryList(TestLeaveBo bo);
+
+ /**
+ * 鏂板璇峰亣
+ */
+ TestLeaveVo insertByBo(TestLeaveBo bo);
+
+ /**
+ * 鎻愪氦璇峰亣骞跺彂璧锋祦绋�
+ */
+ TestLeaveVo submitAndFlowStart(TestLeaveBo bo);
+
+ /**
+ * 淇敼璇峰亣
+ */
+ TestLeaveVo updateByBo(TestLeaveBo bo);
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄よ鍋囦俊鎭�
+ */
+ Boolean deleteWithValidByIds(List<Long> ids);
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java
new file mode 100755
index 0000000..883a967
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java
@@ -0,0 +1,31 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.convert.Convert;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.core.TranslationInterface;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.service.IFlwCategoryService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 娴佺▼鍒嗙被鍚嶇О缈昏瘧瀹炵幇
+ *
+ * @author AprilWind
+ */
+@ConditionalOnEnable
+@Slf4j
+@RequiredArgsConstructor
+@Service
+@TranslationType(type = FlowConstant.CATEGORY_ID_TO_NAME)
+public class CategoryNameTranslationImpl implements TranslationInterface<String> {
+
+ private final IFlwCategoryService flwCategoryService;
+
+ @Override
+ public String translation(Object key, String other) {
+ return flwCategoryService.selectCategoryNameById(Convert.toLong(key));
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java
new file mode 100755
index 0000000..3515ebb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java
@@ -0,0 +1,261 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.*;
+import org.dromara.common.mybatis.helper.DataBaseHelper;
+import org.dromara.warm.flow.core.service.DefService;
+import org.dromara.warm.flow.orm.entity.FlowDefinition;
+import org.dromara.warm.flow.ui.service.CategoryService;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.domain.FlowCategory;
+import org.dromara.workflow.domain.bo.FlowCategoryBo;
+import org.dromara.workflow.domain.vo.FlowCategoryVo;
+import org.dromara.workflow.mapper.FlwCategoryMapper;
+import org.dromara.workflow.service.IFlwCategoryService;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 娴佺▼鍒嗙被Service涓氬姟灞傚鐞�
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@Service
+public class FlwCategoryServiceImpl implements IFlwCategoryService, CategoryService {
+
+ private final DefService defService;
+ private final FlwCategoryMapper baseMapper;
+
+ /**
+ * 鏌ヨ娴佺▼鍒嗙被
+ *
+ * @param categoryId 涓婚敭
+ * @return 娴佺▼鍒嗙被
+ */
+ @Override
+ public FlowCategoryVo queryById(Long categoryId) {
+ return baseMapper.selectVoById(categoryId);
+ }
+
+ /**
+ * 鏍规嵁娴佺▼鍒嗙被ID鏌ヨ娴佺▼鍒嗙被鍚嶇О
+ *
+ * @param categoryId 娴佺▼鍒嗙被ID
+ * @return 娴佺▼鍒嗙被鍚嶇О
+ */
+ @Cacheable(cacheNames = FlowConstant.FLOW_CATEGORY_NAME, key = "#categoryId")
+ @Override
+ public String selectCategoryNameById(Long categoryId) {
+ if (ObjectUtil.isNull(categoryId)) {
+ return null;
+ }
+ FlowCategory category = baseMapper.selectOne(new LambdaQueryWrapper<FlowCategory>()
+ .select(FlowCategory::getCategoryName).eq(FlowCategory::getCategoryId, categoryId));
+ return ObjectUtils.notNullGetter(category, FlowCategory::getCategoryName);
+ }
+
+ /**
+ * 鏌ヨ绗﹀悎鏉′欢鐨勬祦绋嬪垎绫诲垪琛�
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return 娴佺▼鍒嗙被鍒楄〃
+ */
+ @Override
+ public List<FlowCategoryVo> queryList(FlowCategoryBo bo) {
+ LambdaQueryWrapper<FlowCategory> lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ /**
+ * 鏌ヨ娴佺▼鍒嗙被鏍戠粨鏋勪俊鎭�
+ *
+ * @param category 娴佺▼鍒嗙被淇℃伅
+ * @return 娴佺▼鍒嗙被鏍戜俊鎭泦鍚�
+ */
+ @Override
+ public List<Tree<String>> selectCategoryTreeList(FlowCategoryBo category) {
+ List<FlowCategoryVo> categoryList = this.queryList(category);
+ if (CollUtil.isEmpty(categoryList)) {
+ return CollUtil.newArrayList();
+ }
+ return TreeBuildUtils.buildMultiRoot(
+ categoryList,
+ node -> Convert.toStr(node.getCategoryId()),
+ node -> Convert.toStr(node.getParentId()),
+ (node, treeNode) -> treeNode
+ .setId(Convert.toStr(node.getCategoryId()))
+ .setParentId(Convert.toStr(node.getParentId()))
+ .setName(node.getCategoryName())
+ .setWeight(node.getOrderNum())
+ );
+ }
+
+ /**
+ * 宸ヤ綔娴佹煡璇㈠垎绫�
+ *
+ * @return 鍒嗙被鏍戠粨鏋勫垪琛�
+ */
+ @Override
+ public List<org.dromara.warm.flow.core.dto.Tree> queryCategory() {
+ List<FlowCategoryVo> list = this.queryList(new FlowCategoryBo());
+ return StreamUtils.toList(list, category -> new org.dromara.warm.flow.core.dto.Tree()
+ .setId(Convert.toStr(category.getCategoryId()))
+ .setName(category.getCategoryName())
+ .setParentId(Convert.toStr(category.getParentId()))
+ );
+ }
+
+ /**
+ * 鏍¢獙娴佺▼鍒嗙被鍚嶇О鏄惁鍞竴
+ *
+ * @param category 娴佺▼鍒嗙被淇℃伅
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean checkCategoryNameUnique(FlowCategoryBo category) {
+ boolean exist = baseMapper.exists(new LambdaQueryWrapper<FlowCategory>()
+ .eq(FlowCategory::getCategoryName, category.getCategoryName())
+ .eq(FlowCategory::getParentId, category.getParentId())
+ .ne(ObjectUtil.isNotNull(category.getCategoryId()), FlowCategory::getCategoryId, category.getCategoryId()));
+ return !exist;
+ }
+
+ /**
+ * 鏌ヨ娴佺▼鍒嗙被鏄惁瀛樺湪娴佺▼瀹氫箟
+ *
+ * @param categoryId 娴佺▼鍒嗙被ID
+ * @return 缁撴灉 true 瀛樺湪 false 涓嶅瓨鍦�
+ */
+ @Override
+ public boolean checkCategoryExistDefinition(Long categoryId) {
+ FlowDefinition definition = new FlowDefinition();
+ definition.setCategory(categoryId.toString());
+ return defService.exists(definition);
+ }
+
+ /**
+ * 鏄惁瀛樺湪娴佺▼鍒嗙被瀛愯妭鐐�
+ *
+ * @param categoryId 娴佺▼鍒嗙被ID
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean hasChildByCategoryId(Long categoryId) {
+ return baseMapper.exists(new LambdaQueryWrapper<FlowCategory>()
+ .eq(FlowCategory::getParentId, categoryId));
+ }
+
+ private LambdaQueryWrapper<FlowCategory> buildQueryWrapper(FlowCategoryBo bo) {
+ LambdaQueryWrapper<FlowCategory> lqw = Wrappers.lambdaQuery();
+ lqw.eq(FlowCategory::getDelFlag, SystemConstants.NORMAL);
+ lqw.eq(ObjectUtil.isNotNull(bo.getCategoryId()), FlowCategory::getCategoryId, bo.getCategoryId());
+ lqw.eq(ObjectUtil.isNotNull(bo.getParentId()), FlowCategory::getParentId, bo.getParentId());
+ lqw.like(StringUtils.isNotBlank(bo.getCategoryName()), FlowCategory::getCategoryName, bo.getCategoryName());
+ lqw.orderByAsc(FlowCategory::getAncestors);
+ lqw.orderByAsc(FlowCategory::getParentId);
+ lqw.orderByAsc(FlowCategory::getOrderNum);
+ lqw.orderByAsc(FlowCategory::getCategoryId);
+ return lqw;
+ }
+
+ /**
+ * 鏂板娴佺▼鍒嗙被
+ *
+ * @param bo 娴佺▼鍒嗙被
+ * @return 鏄惁鏂板鎴愬姛
+ */
+ @Override
+ public int insertByBo(FlowCategoryBo bo) {
+ FlowCategory info = baseMapper.selectById(bo.getParentId());
+ if (ObjectUtil.isNull(info)) {
+ throw new ServiceException("鐖剁骇娴佺▼鍒嗙被涓嶅瓨鍦�!");
+ }
+ FlowCategory category = MapstructUtils.convert(bo, FlowCategory.class);
+ category.setAncestors(info.getAncestors() + StringUtils.SEPARATOR + category.getParentId());
+ return baseMapper.insert(category);
+ }
+
+ /**
+ * 淇敼娴佺▼鍒嗙被
+ *
+ * @param bo 娴佺▼鍒嗙被
+ * @return 鏄惁淇敼鎴愬姛
+ */
+ @CacheEvict(cacheNames = FlowConstant.FLOW_CATEGORY_NAME, key = "#bo.categoryId")
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int updateByBo(FlowCategoryBo bo) {
+ FlowCategory category = MapstructUtils.convert(bo, FlowCategory.class);
+ FlowCategory oldCategory = baseMapper.selectById(category.getCategoryId());
+ if (ObjectUtil.isNull(oldCategory)) {
+ throw new ServiceException("娴佺▼鍒嗙被涓嶅瓨鍦紝鏃犳硶淇敼");
+ }
+ if (oldCategory.getParentId() == 0L && category.getParentId() != 0L) {
+ throw new ServiceException("涓嶅厑璁镐慨鏀归《绾у垎绫荤殑鐖剁骇鑺傜偣");
+ }
+ if (!oldCategory.getParentId().equals(category.getParentId())) {
+ FlowCategory newParentCategory = baseMapper.selectById(category.getParentId());
+ if (ObjectUtil.isNotNull(newParentCategory)) {
+ String newAncestors = newParentCategory.getAncestors() + StringUtils.SEPARATOR + newParentCategory.getCategoryId();
+ String oldAncestors = oldCategory.getAncestors();
+ category.setAncestors(newAncestors);
+ updateCategoryChildren(category.getCategoryId(), newAncestors, oldAncestors);
+ } else {
+ throw new ServiceException("鐖剁骇娴佺▼鍒嗙被涓嶅瓨鍦�!");
+ }
+ } else {
+ category.setAncestors(oldCategory.getAncestors());
+ }
+ return baseMapper.updateById(category);
+ }
+
+ /**
+ * 淇敼瀛愬厓绱犲叧绯�
+ *
+ * @param categoryId 琚慨鏀圭殑娴佺▼鍒嗙被ID
+ * @param newAncestors 鏂扮殑鐖禝D闆嗗悎
+ * @param oldAncestors 鏃х殑鐖禝D闆嗗悎
+ */
+ private void updateCategoryChildren(Long categoryId, String newAncestors, String oldAncestors) {
+ List<FlowCategory> children = baseMapper.selectList(new LambdaQueryWrapper<FlowCategory>()
+ .apply(DataBaseHelper.findInSet(categoryId, "ancestors")));
+ List<FlowCategory> list = new ArrayList<>();
+ for (FlowCategory child : children) {
+ FlowCategory category = new FlowCategory();
+ category.setCategoryId(child.getCategoryId());
+ category.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors));
+ list.add(category);
+ }
+ if (CollUtil.isNotEmpty(list)) {
+ baseMapper.updateBatchById(list);
+ }
+ }
+
+ /**
+ * 鍒犻櫎娴佺▼鍒嗙被淇℃伅
+ *
+ * @param categoryId 涓婚敭
+ * @return 鏄惁鍒犻櫎鎴愬姛
+ */
+ @CacheEvict(cacheNames = FlowConstant.FLOW_CATEGORY_NAME, key = "#categoryId")
+ @Override
+ public int deleteWithValidById(Long categoryId) {
+ return baseMapper.deleteById(categoryId);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java
new file mode 100755
index 0000000..cb82cbe
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java
@@ -0,0 +1,273 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.dto.UserDTO;
+import org.dromara.common.core.service.DeptService;
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.service.UserService;
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.warm.flow.core.dto.DefJson;
+import org.dromara.warm.flow.core.dto.NodeJson;
+import org.dromara.warm.flow.core.dto.PromptContent;
+import org.dromara.warm.flow.core.enums.NodeType;
+import org.dromara.warm.flow.core.utils.MapUtil;
+import org.dromara.warm.flow.orm.entity.FlowHisTask;
+import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
+import org.dromara.warm.flow.ui.service.ChartExtService;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 娴佺▼鍥炬彁绀轰俊鎭�
+ *
+ * @author AprilWind
+ */
+@ConditionalOnEnable
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class FlwChartExtServiceImpl implements ChartExtService {
+
+ private final UserService userService;
+ private final DeptService deptService;
+ private final FlowHisTaskMapper flowHisTaskMapper;
+ private final DictService dictService;
+ @Value("${warm-flow.node-tooltip:true}")
+ private boolean nodeTooltip;
+
+ /**
+ * 璁剧疆娴佺▼鍥炬彁绀轰俊鎭�
+ *
+ * @param defJson 娴佺▼瀹氫箟json瀵硅薄
+ */
+ @Override
+ public void execute(DefJson defJson) {
+ // 閰嶇疆鍏抽棴锛岀洿鎺ヨ繑鍥烇紝涓嶆覆鏌撴偓娴獥
+ if (!nodeTooltip) {
+ return;
+ }
+
+ // 鏍规嵁娴佺▼瀹炰緥ID鏌ヨ鎵�鏈夌浉鍏崇殑鍘嗗彶浠诲姟鍒楄〃
+ List<FlowHisTask> flowHisTasks = this.getHisTaskGroupedByNode(defJson.getInstance().getId());
+ if (CollUtil.isEmpty(flowHisTasks)) {
+ return;
+ }
+
+ // 鎸夎妭鐐圭紪鍙凤紙nodeCode锛夊鍘嗗彶浠诲姟杩涜鍒嗙粍
+ Map<String, List<FlowHisTask>> groupedByNode = StreamUtils.groupByKey(flowHisTasks, FlowHisTask::getNodeCode);
+
+ // 鎵归噺鏌ヨ鎵�鏈夊鎵逛汉鐨勭敤鎴蜂俊鎭�
+ List<UserDTO> userDTOList = userService.selectListByIds(StreamUtils.toList(flowHisTasks, e -> Convert.toLong(e.getApprover())));
+
+ // 灏嗘煡璇㈠埌鐨勭敤鎴峰垪琛ㄨ浆鎹负浠ョ敤鎴稩D涓簁ey鐨勬槧灏�
+ Map<Long, UserDTO> userMap = StreamUtils.toIdentityMap(userDTOList, UserDTO::getUserId);
+
+ Map<String, String> dictType = dictService.getAllDictByDictType(FlowConstant.WF_TASK_STATUS);
+
+ for (NodeJson nodeJson : defJson.getNodeList()) {
+ List<FlowHisTask> taskList = groupedByNode.get(nodeJson.getNodeCode());
+ if (CollUtil.isEmpty(taskList)) {
+ continue;
+ }
+
+ // 鎸夊鎵逛汉鍒嗙粍鍘婚噸锛屼繚鐣欐渶鏂板鐞嗚褰曪紝鏈�缁堣浆鎹㈡垚 List
+ List<FlowHisTask> latestPerApprover = taskList.stream()
+ .collect(Collectors.collectingAndThen(
+ Collectors.toMap(
+ FlowHisTask::getApprover,
+ Function.identity(),
+ (oldTask, newTask) -> newTask.getUpdateTime().after(oldTask.getUpdateTime()) ? newTask : oldTask,
+ LinkedHashMap::new
+ ),
+ map -> new ArrayList<>(map.values())
+ ));
+
+ // 澶勭悊褰撳墠鑺傜偣鐨勬墿灞曚俊鎭�
+ this.processNodeExtInfo(nodeJson, latestPerApprover, userMap, dictType);
+ }
+ }
+
+ /**
+ * 鍒濆鍖栨祦绋嬪浘鎻愮ず淇℃伅
+ *
+ * @param defJson 娴佺▼瀹氫箟json瀵硅薄
+ */
+ @Override
+ public void initPromptContent(DefJson defJson) {
+ // 閰嶇疆鍏抽棴锛岀洿鎺ヨ繑鍥烇紝涓嶆覆鏌撴偓娴獥
+ if (!nodeTooltip) {
+ return;
+ }
+
+ defJson.setTopText("娴佺▼鍚嶇О: " + defJson.getFlowName());
+ defJson.getNodeList().forEach(nodeJson -> {
+ nodeJson.setPromptContent(
+ new PromptContent()
+ // 鎻愮ず淇℃伅
+ .setInfo(
+ CollUtil.newArrayList(
+ new PromptContent.InfoItem()
+ .setPrefix("浠诲姟鍚嶇О: ")
+ .setContent(nodeJson.getNodeName())
+ .setContentStyle(Map.of(
+ "border", "1px solid #d1e9ff",
+ "backgroundColor", "#e8f4ff",
+ "padding", "4px 8px",
+ "borderRadius", "4px"
+ ))
+ .setRowStyle(Map.of(
+ "fontWeight", "bold",
+ "margin", "0 0 6px 0",
+ "padding", "0 0 8px 0",
+ "borderBottom", "1px solid #ccc"
+ ))
+ )
+ )
+ // 寮圭獥鏍峰紡
+ .setDialogStyle(MapUtil.mergeAll(
+ "position", "absolute",
+ "backgroundColor", "#fff",
+ "border", "1px solid #ccc",
+ "borderRadius", "4px",
+ "boxShadow", "0 2px 8px rgba(0, 0, 0, 0.15)",
+ "padding", "8px 12px",
+ "fontSize", "14px",
+ "zIndex", "1000",
+ "maxWidth", "500px",
+ "maxHeight", "300px",
+ "overflowY", "auto",
+ "overflowX", "hidden",
+ "color", "#333",
+ "pointerEvents", "auto",
+ "scrollbarWidth", "thin"
+ ))
+ );
+ });
+ }
+
+ /**
+ * 澶勭悊鑺傜偣鐨勬墿灞曚俊鎭紝鏋勫缓鐢ㄤ簬娴佺▼鍥炬偓娴彁绀虹殑鍐呭
+ *
+ * @param nodeJson 褰撳墠娴佺▼鑺傜偣瀵硅薄锛屽寘鍚妭鐐瑰熀纭�淇℃伅鍜屾彁绀哄唴瀹瑰鍣�
+ * @param taskList 褰撳墠鑺傜偣鍏宠仈鐨勫巻鍙插鎵逛换鍔″垪琛紝鐢ㄤ簬鐢熸垚鎻愮ず淇℃伅
+ * @param userMap 鐢ㄦ埛淇℃伅鏄犲皠琛紝key 涓虹敤鎴稩D锛寁alue 涓虹敤鎴稤TO瀵硅薄锛岀敤浜庤幏鍙栧鎵逛汉淇℃伅
+ * @param dictType 鏁版嵁瀛楀吀鏄犲皠琛紝key 涓哄瓧鍏搁」缂栫爜锛寁alue 涓哄搴旀樉绀哄�硷紝鐢ㄤ簬缈昏瘧瀹℃壒鐘舵�佺瓑
+ */
+ private void processNodeExtInfo(NodeJson nodeJson, List<FlowHisTask> taskList, Map<Long, UserDTO> userMap, Map<String, String> dictType) {
+
+ // 鑾峰彇鑺傜偣鎻愮ず鍐呭瀵硅薄涓殑 info 鍒楄〃锛岀敤浜庤拷鍔犳彁绀洪」
+ List<PromptContent.InfoItem> info = nodeJson.getPromptContent().getInfo();
+
+ // 閬嶅巻鎵�鏈変换鍔¤褰曪紝鏋勫缓鎻愮ず鍐呭
+ for (FlowHisTask task : taskList) {
+ UserDTO userDTO = userMap.get(Convert.toLong(task.getApprover()));
+ if (ObjectUtil.isEmpty(userDTO)) {
+ continue;
+ }
+
+ // 鏌ヨ鐢ㄦ埛鎵�灞為儴闂ㄥ悕绉�
+ String deptName = deptService.selectDeptNameByIds(Convert.toStr(userDTO.getDeptId()));
+
+ // 娣诲姞鏍囬椤癸紝濡傦細馃懁 寮犱笁锛堝競鍦洪儴锛�
+ info.add(new PromptContent.InfoItem()
+ .setPrefix(StringUtils.format("馃懃 {}锛坽}锛�", userDTO.getNickName(), deptName))
+ .setPrefixStyle(Map.of(
+ "fontWeight", "bold",
+ "fontSize", "15px",
+ "color", "#333"
+ ))
+ .setRowStyle(Map.of(
+ "margin", "8px 0",
+ "borderBottom", "1px dashed #ccc"
+ ))
+ );
+
+ // 娣诲姞鍏蜂綋淇℃伅椤癸細璐﹀彿銆佽�楁椂銆佹椂闂�
+ info.add(buildInfoItem("鐢ㄦ埛璐﹀彿", userDTO.getUserName()));
+ info.add(buildInfoItem("瀹℃壒鐘舵��", dictType.get(task.getFlowStatus())));
+ info.add(buildInfoItem("瀹℃壒鑰楁椂", DateUtils.getTimeDifference(task.getUpdateTime(), task.getCreateTime())));
+ info.add(buildInfoItem("鍔炵悊鏃堕棿", DateUtils.formatDateTime(task.getUpdateTime())));
+ }
+ }
+
+ /**
+ * 鏋勫缓鍗曟潯鎻愮ず鍐呭瀵硅薄 InfoItem锛岀敤浜庢偓娴獥鏄剧ず锛坘ey: value锛�
+ *
+ * @param key 瀛楁鍚嶏紙浣滀负鍓嶇紑锛�
+ * @param value 瀛楁鍊�
+ * @return 鎻愮ず椤瑰璞�
+ */
+ private PromptContent.InfoItem buildInfoItem(String key, String value) {
+ return new PromptContent.InfoItem()
+ // 鍓嶇紑
+ .setPrefix(key + ": ")
+ // 鍓嶇紑鏍峰紡
+ .setPrefixStyle(Map.of(
+ "textAlign", "right",
+ "color", "#444",
+ "userSelect", "none",
+ "display", "inline-block",
+ "width", "100px",
+ "paddingRight", "8px",
+ "fontWeight", "500",
+ "fontSize", "14px",
+ "lineHeight", "24px",
+ "verticalAlign", "middle"
+ ))
+ // 鍐呭
+ .setContent(value)
+ // 鍐呭鏍峰紡
+ .setContentStyle(Map.of(
+ "backgroundColor", "#f7faff",
+ "color", "#005cbf",
+ "padding", "4px 8px",
+ "fontSize", "14px",
+ "borderRadius", "4px",
+ "whiteSpace", "normal",
+ "border", "1px solid #d0e5ff",
+ "userSelect", "text",
+ "lineHeight", "20px"
+ ))
+ // 琛屾牱寮�
+ .setRowStyle(Map.of(
+ "color", "#222",
+ "alignItems", "center",
+ "display", "flex",
+ "marginBottom", "6px",
+ "fontWeight", "400",
+ "fontSize", "14px"
+ ));
+ }
+
+ /**
+ * 鏍规嵁娴佺▼瀹炰緥ID鑾峰彇鍘嗗彶浠诲姟鍒楄〃
+ *
+ * @param instanceId 娴佺▼瀹炰緥ID
+ * @return 鍘嗗彶浠诲姟鍒楄〃
+ */
+ public List<FlowHisTask> getHisTaskGroupedByNode(Long instanceId) {
+ LambdaQueryWrapper<FlowHisTask> wrapper = Wrappers.lambdaQuery();
+ wrapper.eq(FlowHisTask::getInstanceId, instanceId)
+ .eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey())
+ .orderByDesc(FlowHisTask::getUpdateTime);
+ return flowHisTaskMapper.selectList(wrapper);
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
new file mode 100755
index 0000000..8d4708b
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
@@ -0,0 +1,124 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.dto.UserDTO;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mail.utils.MailUtils;
+import org.dromara.common.sse.dto.SseMessageDto;
+import org.dromara.common.sse.utils.SseMessageUtils;
+import org.dromara.warm.flow.core.FlowEngine;
+import org.dromara.warm.flow.core.entity.Node;
+import org.dromara.warm.flow.orm.entity.FlowTask;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.enums.MessageTypeEnum;
+import org.dromara.workflow.service.IFlwCommonService;
+import org.dromara.workflow.service.IFlwTaskService;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+
+/**
+ * 宸ヤ綔娴佸伐鍏�
+ *
+ * @author LionLi
+ */
+@ConditionalOnEnable
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class FlwCommonServiceImpl implements IFlwCommonService {
+
+ private static final String DEFAULT_SUBJECT = "鍗曟嵁瀹℃壒鎻愰啋";
+
+ /**
+ * 鏍规嵁娴佺▼瀹炰緥鍙戦�佹秷鎭粰褰撳墠澶勭悊浜�
+ *
+ * @param flowName 娴佺▼瀹氫箟鍚嶇О
+ * @param instId 娴佺▼瀹炰緥ID
+ * @param messageType 娑堟伅绫诲瀷鍒楄〃
+ * @param message 娑堟伅鍐呭锛屼负绌哄垯浣跨敤榛樿娑堟伅
+ */
+ @Override
+ public void sendMessage(String flowName, Long instId, List<String> messageType, String message) {
+ if (CollUtil.isEmpty(messageType)) {
+ return;
+ }
+ IFlwTaskService flwTaskService = SpringUtils.getBean(IFlwTaskService.class);
+ List<FlowTask> list = flwTaskService.selectByInstId(instId);
+ if (CollUtil.isEmpty(list)) {
+ return;
+ }
+ if (StringUtils.isBlank(message)) {
+ message = "鏈夋柊鐨勩��" + flowName + "銆戝崟鎹凡缁忔彁浜よ嚦鎮紝璇锋偍鍙婃椂澶勭悊銆�";
+ }
+ List<UserDTO> userList = flwTaskService.currentTaskAllUser(StreamUtils.toList(list, FlowTask::getId));
+ if (CollUtil.isEmpty(userList)) {
+ return;
+ }
+ sendMessage(messageType, message, DEFAULT_SUBJECT, userList);
+ }
+
+ /**
+ * 鍙戦�佹秷鎭粰鎸囧畾鐢ㄦ埛鍒楄〃
+ *
+ * @param messageType 娑堟伅绫诲瀷鍒楄〃
+ * @param message 娑堟伅鍐呭
+ * @param subject 閭欢鏍囬
+ * @param userList 鎺ユ敹鐢ㄦ埛鍒楄〃
+ */
+ @Override
+ public void sendMessage(List<String> messageType, String message, String subject, List<UserDTO> userList) {
+ if (CollUtil.isEmpty(messageType) || CollUtil.isEmpty(userList)) {
+ return;
+ }
+ List<Long> userIds = new ArrayList<>(StreamUtils.toSet(userList, UserDTO::getUserId));
+ Set<String> emails = StreamUtils.toSet(userList, UserDTO::getEmail);
+
+ for (String code : messageType) {
+ MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByCode(code);
+ if (ObjectUtil.isEmpty(messageTypeEnum)) {
+ continue;
+ }
+ try {
+ switch (messageTypeEnum) {
+ case SYSTEM_MESSAGE -> {
+ SseMessageDto dto = new SseMessageDto();
+ dto.setUserIds(userIds);
+ dto.setMessage(message);
+ SseMessageUtils.publishMessage(dto);
+ }
+ case EMAIL_MESSAGE -> MailUtils.sendText(emails, subject, message);
+ case SMS_MESSAGE -> {
+ // TODO: 琛ュ厖鐭俊鍙戦�侀�昏緫
+ log.info("銆愮煭淇″彂閫� - TODO銆戠敤鎴锋暟閲�={} 鍐呭={}", userList.size(), message);
+ }
+ default -> log.warn("銆愭秷鎭彂閫併�戞湭澶勭悊鐨勬秷鎭被鍨嬶細{}", messageTypeEnum);
+ }
+ } catch (Exception ex) {
+ // 璁板綍閿欒浣嗕笉鎶涘嚭锛岀‘淇濅富閫昏緫涓嶅彈褰卞搷
+ log.error("銆愭秷鎭彂閫佸け璐ャ�戠被鍨�={}锛屽師鍥�={}", messageTypeEnum, ex.getMessage(), ex);
+ }
+ }
+ }
+
+ /**
+ * 鐢宠浜鸿妭鐐圭紪鐮�
+ *
+ * @param definitionId 娴佺▼瀹氫箟id
+ * @return 鐢宠浜鸿妭鐐圭紪鐮�
+ */
+ @Override
+ public String applyNodeCode(Long definitionId) {
+ List<Node> firstBetweenNode = FlowEngine.nodeService().getFirstBetweenNode(definitionId, new HashMap<>());
+ return firstBetweenNode.get(0).getNodeCode();
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java
new file mode 100755
index 0000000..1cc61fe
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java
@@ -0,0 +1,270 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.io.IoUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.warm.flow.core.dto.DefJson;
+import org.dromara.warm.flow.core.enums.NodeType;
+import org.dromara.warm.flow.core.enums.PublishStatus;
+import org.dromara.warm.flow.core.service.DefService;
+import org.dromara.warm.flow.orm.entity.FlowDefinition;
+import org.dromara.warm.flow.orm.entity.FlowHisTask;
+import org.dromara.warm.flow.orm.entity.FlowNode;
+import org.dromara.warm.flow.orm.entity.FlowSkip;
+import org.dromara.warm.flow.orm.mapper.FlowDefinitionMapper;
+import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
+import org.dromara.warm.flow.orm.mapper.FlowNodeMapper;
+import org.dromara.warm.flow.orm.mapper.FlowSkipMapper;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.domain.FlowCategory;
+import org.dromara.workflow.domain.vo.FlowDefinitionVo;
+import org.dromara.workflow.mapper.FlwCategoryMapper;
+import org.dromara.workflow.service.IFlwCommonService;
+import org.dromara.workflow.service.IFlwDefinitionService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.dromara.common.core.constant.TenantConstants.DEFAULT_TENANT_ID;
+
+/**
+ * 娴佺▼瀹氫箟 鏈嶅姟灞傚疄鐜�
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
+
+ private final DefService defService;
+ private final FlowDefinitionMapper flowDefinitionMapper;
+ private final FlowHisTaskMapper flowHisTaskMapper;
+ private final FlowNodeMapper flowNodeMapper;
+ private final FlowSkipMapper flowSkipMapper;
+ private final FlwCategoryMapper flwCategoryMapper;
+ private final IFlwCommonService flwCommonService;
+
+ /**
+ * 鏌ヨ娴佺▼瀹氫箟鍒楄〃
+ *
+ * @param flowDefinition 娴佺▼瀹氫箟淇℃伅
+ * @param pageQuery 鍒嗛〉
+ * @return 杩斿洖鍒嗛〉鍒楄〃
+ */
+ @Override
+ public TableDataInfo<FlowDefinitionVo> queryList(FlowDefinition flowDefinition, PageQuery pageQuery) {
+ LambdaQueryWrapper<FlowDefinition> wrapper = buildQueryWrapper(flowDefinition);
+ wrapper.eq(FlowDefinition::getIsPublish, PublishStatus.PUBLISHED.getKey());
+ Page<FlowDefinition> page = flowDefinitionMapper.selectPage(pageQuery.build(), wrapper);
+ List<FlowDefinitionVo> list = BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class);
+ return new TableDataInfo<>(list, page.getTotal());
+ }
+
+ /**
+ * 鏌ヨ鏈彂甯冪殑娴佺▼瀹氫箟鍒楄〃
+ *
+ * @param flowDefinition 娴佺▼瀹氫箟淇℃伅
+ * @param pageQuery 鍒嗛〉
+ * @return 杩斿洖鍒嗛〉鍒楄〃
+ */
+ @Override
+ public TableDataInfo<FlowDefinitionVo> unPublishList(FlowDefinition flowDefinition, PageQuery pageQuery) {
+ LambdaQueryWrapper<FlowDefinition> wrapper = buildQueryWrapper(flowDefinition);
+ wrapper.in(FlowDefinition::getIsPublish, Arrays.asList(PublishStatus.UNPUBLISHED.getKey(), PublishStatus.EXPIRED.getKey()));
+ Page<FlowDefinition> page = flowDefinitionMapper.selectPage(pageQuery.build(), wrapper);
+ List<FlowDefinitionVo> list = BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class);
+ return new TableDataInfo<>(list, page.getTotal());
+ }
+
+ private LambdaQueryWrapper<FlowDefinition> buildQueryWrapper(FlowDefinition flowDefinition) {
+ LambdaQueryWrapper<FlowDefinition> wrapper = Wrappers.lambdaQuery();
+ wrapper.like(StringUtils.isNotBlank(flowDefinition.getFlowCode()), FlowDefinition::getFlowCode, flowDefinition.getFlowCode());
+ wrapper.like(StringUtils.isNotBlank(flowDefinition.getFlowName()), FlowDefinition::getFlowName, flowDefinition.getFlowName());
+ if (StringUtils.isNotBlank(flowDefinition.getCategory())) {
+ List<Long> categoryIds = flwCategoryMapper.selectCategoryIdsByParentId(Convert.toLong(flowDefinition.getCategory()));
+ wrapper.in(FlowDefinition::getCategory, StreamUtils.toList(categoryIds, Convert::toStr));
+ }
+ wrapper.orderByDesc(FlowDefinition::getCreateTime);
+ return wrapper;
+ }
+
+ /**
+ * 鍙戝竷娴佺▼瀹氫箟
+ *
+ * @param id 娴佺▼瀹氫箟id
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean publish(Long id) {
+ List<FlowNode> flowNodes = flowNodeMapper.selectList(new LambdaQueryWrapper<FlowNode>().eq(FlowNode::getDefinitionId, id));
+ List<String> errorMsg = new ArrayList<>();
+ if (CollUtil.isNotEmpty(flowNodes)) {
+ String applyNodeCode = flwCommonService.applyNodeCode(id);
+ for (FlowNode flowNode : flowNodes) {
+ if (StringUtils.isBlank(flowNode.getPermissionFlag()) && !applyNodeCode.equals(flowNode.getNodeCode()) && NodeType.BETWEEN.getKey().equals(flowNode.getNodeType())) {
+ errorMsg.add(flowNode.getNodeName());
+ }
+ }
+ if (CollUtil.isNotEmpty(errorMsg)) {
+ throw new ServiceException("鑺傜偣銆恵}銆戞湭閰嶇疆鍔炵悊浜�!", StringUtils.joinComma(errorMsg));
+ }
+ }
+ return defService.publish(id);
+ }
+
+ /**
+ * 瀵煎叆娴佺▼瀹氫箟
+ *
+ * @param file 鏂囦欢
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean importJson(MultipartFile file, String category) {
+ try {
+ DefJson defJson = JsonUtils.parseObject(file.getBytes(), DefJson.class);
+ defJson.setCategory(category);
+ defService.importDef(defJson);
+ } catch (IOException e) {
+ log.error("璇诲彇鏂囦欢娴侀敊璇�: {}", e.getMessage(), e);
+ throw new IllegalStateException("鏂囦欢璇诲彇澶辫触锛岃妫�鏌ユ枃浠跺唴瀹�", e);
+ }
+ return true;
+ }
+
+ /**
+ * 瀵煎嚭娴佺▼瀹氫箟
+ *
+ * @param id 娴佺▼瀹氫箟id
+ * @param response 鍝嶅簲
+ * @throws IOException 寮傚父
+ */
+ @Override
+ public void exportDef(Long id, HttpServletResponse response) throws IOException {
+ byte[] data = defService.exportJson(id).getBytes(StandardCharsets.UTF_8);
+ // 璁剧疆鍝嶅簲澶村拰鍐呭绫诲瀷
+ response.reset();
+ response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ response.setContentType("application/text");
+ response.setHeader("Content-Disposition", "attachment;");
+ response.addHeader("Content-Length", "" + data.length);
+ IoUtil.write(response.getOutputStream(), false, data);
+ }
+
+ /**
+ * 鍒犻櫎娴佺▼瀹氫箟
+ *
+ * @param ids 娴佺▼瀹氫箟id
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean removeDef(List<Long> ids) {
+ LambdaQueryWrapper<FlowHisTask> wrapper = Wrappers.lambdaQuery();
+ wrapper.in(FlowHisTask::getDefinitionId, ids);
+ List<FlowHisTask> flowHisTasks = flowHisTaskMapper.selectList(wrapper);
+ if (CollUtil.isNotEmpty(flowHisTasks)) {
+ List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectByIds(StreamUtils.toList(flowHisTasks, FlowHisTask::getDefinitionId));
+ if (CollUtil.isNotEmpty(flowDefinitions)) {
+ String join = StreamUtils.join(flowDefinitions, FlowDefinition::getFlowCode);
+ log.info("娴佺▼瀹氫箟銆恵}銆戝凡琚娇鐢ㄤ笉鍙鍒犻櫎锛�", join);
+ throw new ServiceException("娴佺▼瀹氫箟銆恵}銆戝凡琚娇鐢ㄤ笉鍙鍒犻櫎锛�", join);
+ }
+ }
+ try {
+ defService.removeDef(ids);
+ } catch (Exception e) {
+ log.error("Error removing flow definitions: {}", e.getMessage(), e);
+ throw new RuntimeException("Failed to remove flow definitions", e);
+ }
+ return true;
+ }
+
+ /**
+ * 鏂板绉熸埛娴佺▼瀹氫箟
+ *
+ * @param tenantId 绉熸埛id
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void syncDef(String tenantId) {
+ FlowCategory flowCategory = flwCategoryMapper.selectOne(new LambdaQueryWrapper<FlowCategory>()
+ .eq(FlowCategory::getTenantId, DEFAULT_TENANT_ID)
+ .eq(FlowCategory::getCategoryId, FlowConstant.FLOW_CATEGORY_ID));
+ flowCategory.setCategoryId(null);
+ flowCategory.setTenantId(tenantId);
+ flowCategory.setCreateDept(null);
+ flowCategory.setCreateBy(null);
+ flowCategory.setCreateTime(null);
+ flowCategory.setUpdateBy(null);
+ flowCategory.setUpdateTime(null);
+ flwCategoryMapper.insert(flowCategory);
+
+ List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(new LambdaQueryWrapper<FlowDefinition>().eq(FlowDefinition::getTenantId, DEFAULT_TENANT_ID));
+ if (CollUtil.isEmpty(flowDefinitions)) {
+ return;
+ }
+ List<Long> defIds = StreamUtils.toList(flowDefinitions, FlowDefinition::getId);
+ List<FlowNode> flowNodes = flowNodeMapper.selectList(new LambdaQueryWrapper<FlowNode>().in(FlowNode::getDefinitionId, defIds));
+ List<FlowSkip> flowSkips = flowSkipMapper.selectList(new LambdaQueryWrapper<FlowSkip>().in(FlowSkip::getDefinitionId, defIds));
+ for (FlowDefinition definition : flowDefinitions) {
+ FlowDefinition flowDefinition = BeanUtil.toBean(definition, FlowDefinition.class);
+ flowDefinition.setId(null);
+ flowDefinition.setTenantId(tenantId);
+ flowDefinition.setIsPublish(0);
+ flowDefinition.setCategory(Convert.toStr(flowCategory.getCategoryId()));
+ int insert = flowDefinitionMapper.insert(flowDefinition);
+ if (insert <= 0) {
+ log.info("鍚屾娴佺▼瀹氫箟銆恵}銆戝け璐ワ紒", definition.getFlowCode());
+ continue;
+ }
+ log.info("鍚屾娴佺▼瀹氫箟銆恵}銆戞垚鍔燂紒", definition.getFlowCode());
+ Long definitionId = flowDefinition.getId();
+ if (CollUtil.isNotEmpty(flowNodes)) {
+ List<FlowNode> nodes = StreamUtils.filter(flowNodes, node -> node.getDefinitionId().equals(definition.getId()));
+ if (CollUtil.isNotEmpty(nodes)) {
+ List<FlowNode> flowNodeList = BeanUtil.copyToList(nodes, FlowNode.class);
+ flowNodeList.forEach(e -> {
+ e.setId(null);
+ e.setDefinitionId(definitionId);
+ e.setTenantId(tenantId);
+ e.setPermissionFlag(null);
+ });
+ flowNodeMapper.insertOrUpdate(flowNodeList);
+ }
+ }
+ if (CollUtil.isNotEmpty(flowSkips)) {
+ List<FlowSkip> skips = StreamUtils.filter(flowSkips, skip -> skip.getDefinitionId().equals(definition.getId()));
+ if (CollUtil.isNotEmpty(skips)) {
+ List<FlowSkip> flowSkipList = BeanUtil.copyToList(skips, FlowSkip.class);
+ flowSkipList.forEach(e -> {
+ e.setId(null);
+ e.setDefinitionId(definitionId);
+ e.setTenantId(tenantId);
+ });
+ flowSkipMapper.insertOrUpdate(flowSkipList);
+ }
+ }
+ }
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
new file mode 100755
index 0000000..4de669d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
@@ -0,0 +1,469 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.enums.BusinessStatusEnum;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.warm.flow.core.FlowEngine;
+import org.dromara.warm.flow.core.constant.ExceptionCons;
+import org.dromara.warm.flow.core.dto.FlowParams;
+import org.dromara.warm.flow.core.entity.Definition;
+import org.dromara.warm.flow.core.entity.Instance;
+import org.dromara.warm.flow.core.entity.Task;
+import org.dromara.warm.flow.core.entity.User;
+import org.dromara.warm.flow.core.enums.NodeType;
+import org.dromara.warm.flow.core.service.DefService;
+import org.dromara.warm.flow.core.service.InsService;
+import org.dromara.warm.flow.core.service.TaskService;
+import org.dromara.warm.flow.orm.entity.FlowHisTask;
+import org.dromara.warm.flow.orm.entity.FlowInstance;
+import org.dromara.warm.flow.orm.entity.FlowTask;
+import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
+import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.enums.TaskStatusEnum;
+import org.dromara.workflow.domain.bo.FlowCancelBo;
+import org.dromara.workflow.domain.bo.FlowInstanceBo;
+import org.dromara.workflow.domain.bo.FlowInvalidBo;
+import org.dromara.workflow.domain.bo.FlowVariableBo;
+import org.dromara.workflow.domain.vo.FlowHisTaskVo;
+import org.dromara.workflow.domain.vo.FlowInstanceVo;
+import org.dromara.workflow.handler.FlowProcessEventHandler;
+import org.dromara.workflow.mapper.FlwCategoryMapper;
+import org.dromara.workflow.mapper.FlwInstanceMapper;
+import org.dromara.workflow.service.IFlwInstanceService;
+import org.dromara.workflow.service.IFlwTaskService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.function.Function;
+
+/**
+ * 娴佺▼瀹炰緥 鏈嶅姟灞傚疄鐜�
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class FlwInstanceServiceImpl implements IFlwInstanceService {
+
+ private final InsService insService;
+ private final DefService defService;
+ private final TaskService taskService;
+ private final FlowHisTaskMapper flowHisTaskMapper;
+ private final FlowInstanceMapper flowInstanceMapper;
+ private final FlowProcessEventHandler flowProcessEventHandler;
+ private final IFlwTaskService flwTaskService;
+ private final FlwInstanceMapper flwInstanceMapper;
+ private final FlwCategoryMapper flwCategoryMapper;
+
+ /**
+ * 鍒嗛〉鏌ヨ姝e湪杩愯鐨勬祦绋嬪疄渚�
+ *
+ * @param flowInstanceBo 娴佺▼瀹炰緥
+ * @param pageQuery 鍒嗛〉
+ */
+ @Override
+ public TableDataInfo<FlowInstanceVo> selectRunningInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
+ QueryWrapper<FlowInstanceBo> queryWrapper = buildQueryWrapper(flowInstanceBo);
+ queryWrapper.in("fi.flow_status", BusinessStatusEnum.runningStatus());
+ Page<FlowInstanceVo> page = flwInstanceMapper.selectInstanceList(pageQuery.build(), queryWrapper);
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鍒嗛〉鏌ヨ宸茬粨鏉熺殑娴佺▼瀹炰緥
+ *
+ * @param flowInstanceBo 娴佺▼瀹炰緥
+ * @param pageQuery 鍒嗛〉
+ */
+ @Override
+ public TableDataInfo<FlowInstanceVo> selectFinishInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
+ QueryWrapper<FlowInstanceBo> queryWrapper = buildQueryWrapper(flowInstanceBo);
+ queryWrapper.in("fi.flow_status", BusinessStatusEnum.finishStatus());
+ Page<FlowInstanceVo> page = flwInstanceMapper.selectInstanceList(pageQuery.build(), queryWrapper);
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏍规嵁涓氬姟id鏌ヨ娴佺▼瀹炰緥璇︾粏淇℃伅
+ *
+ * @param businessId 涓氬姟id
+ * @return 缁撴灉
+ */
+ @Override
+ public FlowInstanceVo queryByBusinessId(Long businessId) {
+ FlowInstance instance = this.selectInstByBusinessId(Convert.toStr(businessId));
+ FlowInstanceVo instanceVo = BeanUtil.toBean(instance, FlowInstanceVo.class);
+ Definition definition = defService.getById(instanceVo.getDefinitionId());
+ instanceVo.setFlowName(definition.getFlowName());
+ instanceVo.setFlowCode(definition.getFlowCode());
+ instanceVo.setVersion(definition.getVersion());
+ instanceVo.setFormCustom(definition.getFormCustom());
+ instanceVo.setFormPath(definition.getFormPath());
+ instanceVo.setCategory(definition.getCategory());
+ return instanceVo;
+ }
+
+ /**
+ * 閫氱敤鏌ヨ鏉′欢
+ *
+ * @param flowInstanceBo 鏌ヨ鏉′欢
+ * @return 鏌ヨ鏉′欢鏋勯�犳柟娉�
+ */
+ private QueryWrapper<FlowInstanceBo> buildQueryWrapper(FlowInstanceBo flowInstanceBo) {
+ QueryWrapper<FlowInstanceBo> queryWrapper = Wrappers.query();
+ queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getNodeName()), "fi.node_name", flowInstanceBo.getNodeName());
+ queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getFlowName()), "fd.flow_name", flowInstanceBo.getFlowName());
+ queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getFlowCode()), "fd.flow_code", flowInstanceBo.getFlowCode());
+ if (StringUtils.isNotBlank(flowInstanceBo.getCategory())) {
+ List<Long> categoryIds = flwCategoryMapper.selectCategoryIdsByParentId(Convert.toLong(flowInstanceBo.getCategory()));
+ queryWrapper.in("fd.category", StreamUtils.toList(categoryIds, Convert::toStr));
+ }
+ queryWrapper.eq(StringUtils.isNotBlank(flowInstanceBo.getBusinessId()), "fi.business_id", flowInstanceBo.getBusinessId());
+ queryWrapper.in(CollUtil.isNotEmpty(flowInstanceBo.getCreateByIds()), "fi.create_by", flowInstanceBo.getCreateByIds());
+ queryWrapper.eq("fi.del_flag", "0");
+ queryWrapper.orderByDesc("fi.create_time");
+ return queryWrapper;
+ }
+
+ /**
+ * 鏍规嵁涓氬姟id鏌ヨ娴佺▼瀹炰緥
+ *
+ * @param businessId 涓氬姟id
+ */
+ @Override
+ public FlowInstance selectInstByBusinessId(String businessId) {
+ return flowInstanceMapper.selectOne(new LambdaQueryWrapper<FlowInstance>().eq(FlowInstance::getBusinessId, businessId));
+ }
+
+ /**
+ * 鎸夌収瀹炰緥id鏌ヨ娴佺▼瀹炰緥
+ *
+ * @param instanceId 瀹炰緥id
+ */
+ @Override
+ public FlowInstance selectInstById(Long instanceId) {
+ return flowInstanceMapper.selectById(instanceId);
+ }
+
+ /**
+ * 鎸夌収瀹炰緥id鏌ヨ娴佺▼瀹炰緥
+ *
+ * @param instanceIds 瀹炰緥id
+ */
+ @Override
+ public List<FlowInstance> selectInstListByIdList(List<Long> instanceIds) {
+ return flowInstanceMapper.selectByIds(instanceIds);
+ }
+
+ /**
+ * 鎸夌収涓氬姟id鍒犻櫎娴佺▼瀹炰緥
+ *
+ * @param businessIds 涓氬姟id
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean deleteByBusinessIds(List<String> businessIds) {
+ List<FlowInstance> flowInstances = flowInstanceMapper.selectList(new LambdaQueryWrapper<FlowInstance>().in(FlowInstance::getBusinessId, businessIds));
+ if (CollUtil.isEmpty(flowInstances)) {
+ log.warn("鏈壘鍒板搴旂殑娴佺▼瀹炰緥淇℃伅锛屾棤娉曟墽琛屽垹闄ゆ搷浣溿��");
+ return false;
+ }
+ return insService.remove(StreamUtils.toList(flowInstances, FlowInstance::getId));
+ }
+
+ /**
+ * 鎸夌収瀹炰緥id鍒犻櫎娴佺▼瀹炰緥
+ *
+ * @param instanceIds 瀹炰緥id
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean deleteByInstanceIds(List<Long> instanceIds) {
+ // 鑾峰彇瀹炰緥淇℃伅
+ List<Instance> instances = insService.getByIds(instanceIds);
+ if (CollUtil.isEmpty(instances)) {
+ log.warn("鏈壘鍒板搴旂殑娴佺▼瀹炰緥淇℃伅锛屾棤娉曟墽琛屽垹闄ゆ搷浣溿��");
+ return false;
+ }
+ // 鑾峰彇瀹氫箟淇℃伅
+ Map<Long, Definition> definitionMap = StreamUtils.toMap(
+ defService.getByIds(StreamUtils.toList(instances, Instance::getDefinitionId)),
+ Definition::getId,
+ Function.identity()
+ );
+
+ // 閫愪竴瑙﹀彂鍒犻櫎浜嬩欢
+ instances.forEach(instance -> {
+ Definition definition = definitionMap.get(instance.getDefinitionId());
+ if (ObjectUtil.isNull(definition)) {
+ log.warn("瀹炰緥 ID: {} 瀵瑰簲鐨勬祦绋嬪畾涔変俊鎭湭鎵惧埌锛岃烦杩囧垹闄や簨浠惰Е鍙戙��", instance.getId());
+ return;
+ }
+ flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
+ });
+ // 鍒犻櫎瀹炰緥
+ return insService.remove(instanceIds);
+ }
+
+ /**
+ * 鎸夌収瀹炰緥id鍒犻櫎宸插畬鎴愮殑娴佺▼瀹炰緥
+ *
+ * @param instanceIds 瀹炰緥id
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean deleteHisByInstanceIds(List<Long> instanceIds) {
+ // 鑾峰彇瀹炰緥淇℃伅
+ List<Instance> instances = insService.getByIds(instanceIds);
+ if (CollUtil.isEmpty(instances)) {
+ log.warn("鏈壘鍒板搴旂殑娴佺▼瀹炰緥淇℃伅锛屾棤娉曟墽琛屽垹闄ゆ搷浣溿��");
+ return false;
+ }
+ // 鑾峰彇瀹氫箟淇℃伅
+ Map<Long, Definition> definitionMap = StreamUtils.toMap(
+ defService.getByIds(StreamUtils.toList(instances, Instance::getDefinitionId)),
+ Definition::getId,
+ Function.identity()
+ );
+ // 閫愪竴瑙﹀彂鍒犻櫎浜嬩欢
+ instances.forEach(instance -> {
+ Definition definition = definitionMap.get(instance.getDefinitionId());
+ if (ObjectUtil.isNull(definition)) {
+ log.warn("瀹炰緥 ID: {} 瀵瑰簲鐨勬祦绋嬪畾涔変俊鎭湭鎵惧埌锛岃烦杩囧垹闄や簨浠惰Е鍙戙��", instance.getId());
+ return;
+ }
+ flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
+ });
+ List<FlowTask> flowTaskList = flwTaskService.selectByInstIds(instanceIds);
+ if (CollUtil.isNotEmpty(flowTaskList)) {
+ FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTaskList, FlowTask::getId));
+ }
+ FlowEngine.taskService().deleteByInsIds(instanceIds);
+ FlowEngine.hisTaskService().deleteByInsIds(instanceIds);
+ FlowEngine.insService().removeByIds(instanceIds);
+ return true;
+ }
+
+ /**
+ * 鎾ら攢娴佺▼
+ *
+ * @param bo 鍙傛暟
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean cancelProcessApply(FlowCancelBo bo) {
+ Instance instance = selectInstByBusinessId(bo.getBusinessId());
+ if (instance == null) {
+ throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
+ }
+ Definition definition = defService.getById(instance.getDefinitionId());
+ if (definition == null) {
+ throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF);
+ }
+ String message = bo.getMessage();
+ String userIdStr = LoginHelper.getUserIdStr();
+ BusinessStatusEnum.checkCancelStatus(instance.getFlowStatus());
+ FlowParams flowParams = FlowParams.build()
+ .message(message)
+ .flowStatus(BusinessStatusEnum.CANCEL.getStatus())
+ .hisStatus(BusinessStatusEnum.CANCEL.getStatus())
+ .handler(userIdStr)
+ .ignore(true);
+ taskService.revoke(instance.getId(), flowParams);
+ return true;
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鐧婚檰浜哄彂璧风殑娴佺▼瀹炰緥
+ *
+ * @param instanceBo 娴佺▼瀹炰緥
+ * @param pageQuery 鍒嗛〉
+ */
+ @Override
+ public TableDataInfo<FlowInstanceVo> selectCurrentInstanceList(FlowInstanceBo instanceBo, PageQuery pageQuery) {
+ QueryWrapper<FlowInstanceBo> queryWrapper = buildQueryWrapper(instanceBo);
+ queryWrapper.eq("fi.create_by", LoginHelper.getUserIdStr());
+ Page<FlowInstanceVo> page = flwInstanceMapper.selectInstanceList(pageQuery.build(), queryWrapper);
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鑾峰彇娴佺▼鍥�,娴佺▼璁板綍
+ *
+ * @param businessId 涓氬姟id
+ */
+ @Override
+ public Map<String, Object> flowHisTaskList(String businessId) {
+ FlowInstance flowInstance = this.selectInstByBusinessId(businessId);
+ if (ObjectUtil.isNull(flowInstance)) {
+ throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
+ }
+ Long instanceId = flowInstance.getId();
+
+ // 鍏堢粍瑁呭緟瀹℃壒浠诲姟锛堣繍琛屼腑鐨勪换鍔★級
+ List<FlowHisTaskVo> runningTaskVos = new ArrayList<>();
+ List<FlowTask> runningTasks = flwTaskService.selectByInstId(instanceId);
+ if (CollUtil.isNotEmpty(runningTasks)) {
+ runningTaskVos = BeanUtil.copyToList(runningTasks, FlowHisTaskVo.class);
+
+ List<User> associatedUsers = FlowEngine.userService()
+ .getByAssociateds(StreamUtils.toList(runningTasks, FlowTask::getId));
+ Map<Long, List<User>> taskUserMap = StreamUtils.groupByKey(associatedUsers, User::getAssociated);
+
+ for (FlowHisTaskVo vo : runningTaskVos) {
+ vo.setFlowStatus(TaskStatusEnum.WAITING.getStatus());
+ vo.setUpdateTime(null);
+ vo.setRunDuration(null);
+
+ List<User> users = taskUserMap.get(vo.getId());
+ if (CollUtil.isNotEmpty(users)) {
+ vo.setApprover(StreamUtils.join(users, User::getProcessedBy));
+ }
+ }
+ }
+
+ // 鍐嶇粍瑁呭巻鍙蹭换鍔★紙宸插鐞嗕换鍔★級
+ List<FlowHisTaskVo> hisTaskVos = new ArrayList<>();
+ List<FlowHisTask> hisTasks = flowHisTaskMapper.selectList(
+ new LambdaQueryWrapper<FlowHisTask>()
+ .eq(FlowHisTask::getInstanceId, instanceId)
+ .eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey())
+ .orderByDesc(FlowHisTask::getUpdateTime)
+ );
+ if (CollUtil.isNotEmpty(hisTasks)) {
+ hisTaskVos = BeanUtil.copyToList(hisTasks, FlowHisTaskVo.class);
+ }
+
+ // 缁撴灉鍒楄〃锛屽緟瀹℃壒浠诲姟鍦ㄥ墠锛屽巻鍙蹭换鍔″湪鍚�
+ List<FlowHisTaskVo> combinedList = new ArrayList<>();
+ combinedList.addAll(runningTaskVos);
+ combinedList.addAll(hisTaskVos);
+
+ return Map.of("list", combinedList, "instanceId", instanceId);
+ }
+
+ /**
+ * 鎸夌収瀹炰緥id鏇存柊鐘舵��
+ *
+ * @param instanceId 瀹炰緥id
+ * @param status 鐘舵��
+ */
+ @Override
+ public void updateStatus(Long instanceId, String status) {
+ LambdaUpdateWrapper<FlowInstance> wrapper = new LambdaUpdateWrapper<>();
+ wrapper.set(FlowInstance::getFlowStatus, status);
+ wrapper.eq(FlowInstance::getId, instanceId);
+ flowInstanceMapper.update(wrapper);
+ }
+
+ /**
+ * 鑾峰彇娴佺▼鍙橀噺
+ *
+ * @param instanceId 瀹炰緥id
+ */
+ @Override
+ public Map<String, Object> instanceVariable(Long instanceId) {
+ FlowInstance flowInstance = flowInstanceMapper.selectById(instanceId);
+ Map<String, Object> variableMap = Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap());
+ List<Map<String, Object>> variableList = variableMap.entrySet().stream()
+ .map(entry -> Map.of("key", entry.getKey(), "value", entry.getValue()))
+ .toList();
+ return Map.of("variableList", variableList, "variable", flowInstance.getVariable());
+ }
+
+ /**
+ * 璁剧疆娴佺▼鍙橀噺
+ *
+ * @param bo 鍙傛暟
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean updateVariable(FlowVariableBo bo) {
+ FlowInstance flowInstance = flowInstanceMapper.selectById(bo.getInstanceId());
+ if (flowInstance == null) {
+ throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
+ }
+ Map<String, Object> variableMap = new HashMap<>(Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap()));
+ if (!variableMap.containsKey(bo.getKey())) {
+ log.error("鍙橀噺涓嶅瓨鍦�: {}", bo.getKey());
+ return false;
+ }
+ variableMap.put(bo.getKey(), bo.getValue());
+ flowInstance.setVariable(FlowEngine.jsonConvert.objToStr(variableMap));
+ return flowInstanceMapper.updateById(flowInstance) > 0;
+ }
+
+ /**
+ * 璁剧疆娴佺▼鍙橀噺
+ *
+ * @param instanceId 瀹炰緥id
+ * @param variable 娴佺▼鍙橀噺
+ */
+ @Override
+ public void setVariable(Long instanceId, Map<String, Object> variable) {
+ Instance instance = insService.getById(instanceId);
+ if (instance != null) {
+ taskService.mergeVariable(instance, variable);
+ insService.updateById(instance);
+ }
+ }
+
+ /**
+ * 鎸変换鍔d鏌ヨ瀹炰緥
+ *
+ * @param taskId 浠诲姟id
+ */
+ @Override
+ public FlowInstance selectByTaskId(Long taskId) {
+ Task task = taskService.getById(taskId);
+ if (task == null) {
+ FlowHisTask flowHisTask = flwTaskService.selectHisTaskById(taskId);
+ if (flowHisTask != null) {
+ return this.selectInstById(flowHisTask.getInstanceId());
+ }
+ } else {
+ return this.selectInstById(task.getInstanceId());
+ }
+ return null;
+ }
+
+ /**
+ * 浣滃簾娴佺▼
+ *
+ * @param bo 鍙傛暟
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean processInvalid(FlowInvalidBo bo) {
+ Instance instance = insService.getById(bo.getId());
+ if (instance != null) {
+ BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
+ }
+ FlowParams flowParams = FlowParams.build()
+ .message(bo.getComment())
+ .flowStatus(BusinessStatusEnum.INVALID.getStatus())
+ .hisStatus(TaskStatusEnum.INVALID.getStatus())
+ .ignore(true);
+ taskService.terminationByInsId(bo.getId(), flowParams);
+ return true;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java
new file mode 100755
index 0000000..ce845dc
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java
@@ -0,0 +1,354 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.dto.DictTypeDTO;
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.warm.flow.core.FlowEngine;
+import org.dromara.warm.flow.core.utils.CollUtil;
+import org.dromara.warm.flow.core.utils.ExpressionUtil;
+import org.dromara.warm.flow.ui.service.NodeExtService;
+import org.dromara.warm.flow.ui.vo.NodeExt;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.enums.ButtonPermissionEnum;
+import org.dromara.workflow.common.enums.CopySettingEnum;
+import org.dromara.workflow.common.enums.NodeExtEnum;
+import org.dromara.workflow.common.enums.VariablesEnum;
+import org.dromara.workflow.domain.vo.ButtonPermissionVo;
+import org.dromara.workflow.domain.vo.NodeExtVo;
+import org.dromara.workflow.service.IFlwNodeExtService;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 娴佺▼璁捐鍣�-鑺傜偣鎵╁睍灞炴��
+ *
+ * @author AprilWind
+ */
+@ConditionalOnEnable
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService {
+
+ /**
+ * 瀛樺偍涓嶅悓 dictType 瀵瑰簲鐨勯厤缃俊鎭�
+ */
+ private static final Map<String, Map<String, Object>> CHILD_NODE_MAP;
+
+ static {
+ CHILD_NODE_MAP = Map.of(
+ CopySettingEnum.class.getSimpleName(),
+ Map.of(
+ "label", "鎶勯�佸璞�",
+ "type", 5,
+ "must", false,
+ "multiple", false,
+ "desc", "璁剧疆璇ヨ妭鐐圭殑鎶勯�佸姙鐞嗕汉"
+ ),
+ VariablesEnum.class.getSimpleName(),
+ Map.of(
+ "label", "鑷畾涔夊弬鏁�",
+ "type", 2,
+ "must", false,
+ "multiple", false,
+ "desc", "鑺傜偣鎵ц鏃跺彲璁剧疆鑷畾涔夊弬鏁帮紝澶氫釜鍙傛暟浠ラ�楀彿鍒嗛殧锛屽锛歬ey1=value1,key2=value2"
+ ),
+ ButtonPermissionEnum.class.getSimpleName(),
+ Map.of(
+ "label", "鏉冮檺鎸夐挳",
+ "type", 4,
+ "must", false,
+ "multiple", true,
+ "desc", "鎺у埗璇ヨ妭鐐圭殑鎸夐挳鏉冮檺"
+ )
+ );
+ }
+
+ private final DictService dictService;
+
+ /**
+ * 鑾峰彇鑺傜偣鎵╁睍灞炴��
+ *
+ * @return 鑺傜偣鎵╁睍灞炴�у垪琛�
+ */
+ @Override
+ public List<NodeExt> getNodeExt() {
+ List<NodeExt> nodeExtList = new ArrayList<>();
+ // 鏋勫缓鍩虹璁剧疆椤甸潰
+ nodeExtList.add(buildNodeExt("wf_basic_tab", "鍩虹璁剧疆", 1,
+ List.of(CopySettingEnum.class, VariablesEnum.class)));
+ // 鏋勫缓鎸夐挳鏉冮檺椤甸潰
+ nodeExtList.add(buildNodeExt("wf_button_tab", "鏉冮檺", 2,
+ List.of(ButtonPermissionEnum.class)));
+ // 鑷畾涔夋瀯寤� 瑙勫垯鍙傝�� NodeExt 涓� warm-flow鏂囨。璇存槑
+ // nodeExtList.add(buildNodeExt("xxx_xxx", "xxx", 1, List);
+ return nodeExtList;
+ }
+
+ /**
+ * 鏋勫缓涓�涓� `NodeExt` 瀵硅薄
+ *
+ * @param code 鍞竴缂栫爜
+ * @param name 鍚嶇О锛堟柊椤电鏃讹紝浣滀负椤电鍚嶇О锛�
+ * @param type 鑺傜偣绫诲瀷锛�1: 鍩虹璁剧疆锛�2: 鏂伴〉绛撅級
+ * @param sources 鏁版嵁鏉ユ簮锛堟灇涓剧被鎴栧瓧鍏哥被鍨嬶級
+ * @return 鏋勫缓鐨� `NodeExt` 瀵硅薄
+ */
+ @SuppressWarnings("unchecked cast")
+ private NodeExt buildNodeExt(String code, String name, int type, List<Object> sources) {
+ NodeExt nodeExt = new NodeExt();
+ nodeExt.setCode(code);
+ nodeExt.setType(type);
+ nodeExt.setName(name);
+ nodeExt.setChilds(sources.stream()
+ .map(source -> {
+ if (source instanceof Class<?> clazz && NodeExtEnum.class.isAssignableFrom(clazz)) {
+ return buildChildNode((Class<? extends NodeExtEnum>) clazz);
+ } else if (source instanceof String dictType) {
+ return buildChildNode(dictType);
+ }
+ return null;
+ })
+ .filter(ObjectUtil::isNotNull)
+ .toList()
+ );
+ return nodeExt;
+ }
+
+ /**
+ * 鏍规嵁鏋氫妇绫诲瀷鏋勫缓涓�涓� `ChildNode` 瀵硅薄
+ *
+ * @param enumClass 鏋氫妇绫伙紝蹇呴』瀹炵幇 `NodeExtEnum` 鎺ュ彛
+ * @return 鏋勫缓鐨� `ChildNode` 瀵硅薄
+ */
+ private NodeExt.ChildNode buildChildNode(Class<? extends NodeExtEnum> enumClass) {
+ if (!enumClass.isEnum()) {
+ return null;
+ }
+ String simpleName = enumClass.getSimpleName();
+ NodeExt.ChildNode childNode = new NodeExt.ChildNode();
+ Map<String, Object> map = CHILD_NODE_MAP.get(simpleName);
+ // 缂栫爜锛屾json涓敮
+ childNode.setCode(simpleName);
+ // label鍚嶇О
+ childNode.setLabel(Convert.toStr(map.get("label")));
+ // 1锛氳緭鍏ユ 2锛氭枃鏈煙 3锛氫笅鎷夋 4锛氶�夋嫨妗� 5锛氱敤鎴烽�夋嫨鍣�
+ childNode.setType(Convert.toInt(map.get("type"), 1));
+ // 鏄惁蹇呭~
+ childNode.setMust(Convert.toBool(map.get("must"), false));
+ // 鏄惁澶氶��
+ childNode.setMultiple(Convert.toBool(map.get("multiple"), true));
+ // 鎻忚堪
+ childNode.setDesc(Convert.toStr(map.get("desc"), null));
+ // 瀛楀吀锛屼笅鎷夋鍜屽閫夋鏃剁敤鍒�
+ childNode.setDict(Arrays.stream(enumClass.getEnumConstants())
+ .map(NodeExtEnum.class::cast)
+ .map(x ->
+ new NodeExt.DictItem(x.getLabel(), x.getValue(), x.isSelected())
+ ).toList());
+ return childNode;
+ }
+
+ /**
+ * 鏍规嵁瀛楀吀绫诲瀷鏋勫缓 `ChildNode` 瀵硅薄
+ *
+ * @param dictType 瀛楀吀绫诲瀷
+ * @return 鏋勫缓鐨� `ChildNode` 瀵硅薄
+ */
+ private NodeExt.ChildNode buildChildNode(String dictType) {
+ DictTypeDTO dictTypeDTO = dictService.getDictType(dictType);
+ if (ObjectUtil.isNull(dictTypeDTO)) {
+ return null;
+ }
+ NodeExt.ChildNode childNode = new NodeExt.ChildNode();
+ // 缂栫爜锛屾json涓敮涓�
+ childNode.setCode(dictType);
+ // label鍚嶇О
+ childNode.setLabel(dictTypeDTO.getDictName());
+ // 1锛氳緭鍏ユ 2锛氭枃鏈煙 3锛氫笅鎷夋 4锛氶�夋嫨妗� 5锛氱敤鎴烽�夋嫨鍣�
+ childNode.setType(3);
+ // 鏄惁蹇呭~
+ childNode.setMust(false);
+ // 鏄惁澶氶��
+ childNode.setMultiple(true);
+ // 鎻忚堪 (鍙牴鎹弿杩板弬鏁拌В鏋愭洿澶氶厤缃紝濡倀ype锛宮ust锛宮ultiple绛�)
+ childNode.setDesc(dictTypeDTO.getRemark());
+ // 瀛楀吀锛屼笅鎷夋鍜屽閫夋鏃剁敤鍒�
+ childNode.setDict(dictService.getDictData(dictType)
+ .stream().map(x ->
+ new NodeExt.DictItem(x.getDictLabel(), x.getDictValue(), Convert.toBool(x.getIsDefault(), false))
+ ).toList());
+ return childNode;
+ }
+
+ /**
+ * 瑙f瀽鎵╁睍灞炴�� JSON 骞舵瀯寤� Node 鎵╁睍灞炴�у璞�
+ * <p>
+ * 鏍规嵁浼犲叆鐨� JSON 瀛楃涓诧紝灏嗘墿灞曞睘鎬у垎涓轰笁绫伙細
+ * 1. ButtonPermissionEnum锛氳В鏋愪负鎸夐挳鏉冮檺鍒楄〃锛屾爣璁版瘡涓寜閽槸鍚﹀嬀閫�
+ * 2. CopySettingEnum锛氳В鏋愪负鎶勯�佸璞� ID 闆嗗悎
+ * 3. VariablesEnum锛氳В鏋愪负鑷畾涔夊弬鏁� Map
+ *
+ * <p>绀轰緥 JSON锛�
+ * [
+ * {"code": "ButtonPermissionEnum", "value": "back,termination"},
+ * {"code": "CopySettingEnum", "value": "1,3,4,#{@spelRuleComponent.selectDeptLeaderById(#deptId", "#roleId)}"},
+ * {"code": "VariablesEnum", "value": "key1=value1,key2=value2"}
+ * ]
+ *
+ * @param ext 鎵╁睍灞炴�� JSON 瀛楃涓�
+ * @param variable 娴佺▼鍙橀噺
+ * @return NodeExtVo 瀵硅薄锛屽皝瑁呮寜閽潈闄愬垪琛ㄣ�佹妱閫佸璞¢泦鍚堝拰鑷畾涔夊弬鏁� Map
+ */
+ @Override
+ public NodeExtVo parseNodeExt(String ext, Map<String, Object> variable) {
+ NodeExtVo nodeExtVo = new NodeExtVo();
+
+ // 瑙f瀽 JSON 涓� Dict 鍒楄〃
+ List<Dict> nodeExtMap = JsonUtils.parseArrayMap(ext);
+ if (ObjectUtil.isEmpty(nodeExtMap)) {
+ return nodeExtVo;
+ }
+
+ for (Dict nodeExt : nodeExtMap) {
+ String code = nodeExt.getStr("code");
+ String value = nodeExt.getStr("value");
+
+ if (ButtonPermissionEnum.class.getSimpleName().equals(code)) {
+ // 瑙f瀽鎸夐挳鏉冮檺
+ // 灏� value 鎷嗗垎涓� Set<String>锛屼究浜庣簿纭尮閰�
+ Set<String> buttonSet = StringUtils.str2Set(value, StringUtils.SEPARATOR);
+
+ // 鑾峰彇鎸夐挳瀛楀吀閰嶇疆
+ NodeExt.ChildNode childNode = buildChildNode(ButtonPermissionEnum.class);
+
+ // 鏋勫缓 ButtonPermissionVo 鍒楄〃
+ List<ButtonPermissionVo> buttonList = Optional.ofNullable(childNode)
+ .map(NodeExt.ChildNode::getDict)
+ .orElse(List.of())
+ .stream()
+ .map(dict -> new ButtonPermissionVo(dict.getValue(), buttonSet.contains(dict.getValue())))
+ .toList();
+
+ nodeExtVo.setButtonPermissions(buttonList);
+
+ } else if (CopySettingEnum.class.getSimpleName().equals(code)) {
+ List<String> permissions = spelSmartSplit(value).stream()
+ .map(s -> {
+ List<String> result = ExpressionUtil.evalVariable(s, variable);
+ if (CollUtil.isNotEmpty(result)) {
+ return result;
+ }
+ return Collections.singletonList(s);
+ }).filter(Objects::nonNull)
+ .flatMap(List::stream)
+ .distinct()
+ .collect(Collectors.toList());
+ List<String> copySettings = FlowEngine.permissionHandler().convertPermissions(permissions);
+ // 瑙f瀽鎶勯�佸璞� ID 闆嗗悎
+ nodeExtVo.setCopySettings(new HashSet<>(copySettings));
+
+ } else if (VariablesEnum.class.getSimpleName().equals(code)) {
+ // 瑙f瀽鑷畾涔夊弬鏁�
+ // 灏� key=value 瀛楃涓叉媶鍒嗕负 Map
+ Map<String, String> variables = Arrays.stream(StringUtils.split(value, StringUtils.SEPARATOR))
+ .map(s -> StringUtils.split(s, "="))
+ .filter(arr -> arr.length == 2)
+ .collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
+
+ nodeExtVo.setVariables(variables);
+ } else {
+ // 鏈煡鎵╁睍绫诲瀷锛岃褰曟棩蹇�
+ log.warn("鏈煡鎵╁睍绫诲瀷锛歝ode={}, value={}", code, value);
+ }
+ }
+ return nodeExtVo;
+ }
+
+ /**
+ * 鎸夐�楀彿鍒嗗壊瀛楃涓诧紝浣嗕繚鐣� #{...} 琛ㄨ揪寮忓拰瀛楃涓插父閲忎腑鐨勯�楀彿
+ */
+ private static List<String> spelSmartSplit(String str) {
+ List<String> result = new ArrayList<>();
+ if (str == null || str.trim().isEmpty()) {
+ return result;
+ }
+
+ StringBuilder token = new StringBuilder();
+ // #{...} 鐨勫祵濂楁繁搴�
+ int depth = 0;
+ // 鏄惁鍦ㄥ瓧绗︿覆甯搁噺涓紙" 鎴� '锛�
+ boolean inString = false;
+ // 褰撳墠瀛楃涓插紩鍙风被鍨�
+ char stringQuote = 0;
+
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+
+ // 妫�娴嬭繘鍏� SpEL 琛ㄨ揪寮� #{...}
+ if (!inString && c == '#' && depth == 0 && checkNext(str, i, '{')) {
+ depth++;
+ token.append("#{");
+ // 璺宠繃 {
+ i++;
+ continue;
+ }
+
+ // 鍦ㄨ〃杈惧紡涓亣鍒� { 鎴� } 鏀瑰彉宓屽娣卞害
+ if (!inString && depth > 0) {
+ if (c == '{') {
+ depth++;
+ } else if (c == '}') {
+ depth--;
+ }
+ token.append(c);
+ continue;
+ }
+
+ // 妫�娴嬪瓧绗︿覆寮�濮�/缁撴潫
+ if (depth > 0 && (c == '"' || c == '\'')) {
+ if (!inString) {
+ inString = true;
+ stringQuote = c;
+ } else if (stringQuote == c) {
+ inString = false;
+ }
+ token.append(c);
+ continue;
+ }
+
+ // 澶栧眰閫楀彿鎵嶅垎鍓�
+ if (c == ',' && depth == 0 && !inString) {
+ String part = token.toString().trim();
+ if (!part.isEmpty()) {
+ result.add(part);
+ }
+ token.setLength(0);
+ continue;
+ }
+
+ token.append(c);
+ }
+
+ // 娣诲姞鏈�鍚庝竴涓�
+ String part = token.toString().trim();
+ if (!part.isEmpty()) {
+ result.add(part);
+ }
+
+ return result;
+ }
+
+ private static boolean checkNext(String str, int index, char expected) {
+ return index + 1 < str.length() && str.charAt(index + 1) == expected;
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwSpelServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwSpelServiceImpl.java
new file mode 100755
index 0000000..3790cdf
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwSpelServiceImpl.java
@@ -0,0 +1,190 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
+import org.dromara.common.core.domain.model.TaskAssigneeBody;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.FlowSpel;
+import org.dromara.workflow.domain.bo.FlowSpelBo;
+import org.dromara.workflow.domain.vo.FlowSpelVo;
+import org.dromara.workflow.mapper.FlwSpelMapper;
+import org.dromara.workflow.service.IFlwSpelService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 娴佺▼spel琛ㄨ揪寮忓畾涔塖ervice涓氬姟灞傚鐞�
+ *
+ * @author Michelle.Chung
+ * @date 2025-07-04
+ */
+@ConditionalOnEnable
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class FlwSpelServiceImpl implements IFlwSpelService {
+
+ private final FlwSpelMapper baseMapper;
+
+ /**
+ * 鏌ヨ娴佺▼spel琛ㄨ揪寮忓畾涔�
+ *
+ * @param id 涓婚敭
+ * @return 娴佺▼spel琛ㄨ揪寮忓畾涔�
+ */
+ @Override
+ public FlowSpelVo queryById(Long id){
+ return baseMapper.selectVoById(id);
+ }
+
+ /**
+ * 鍒嗛〉鏌ヨ娴佺▼spel琛ㄨ揪寮忓畾涔夊垪琛�
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @param pageQuery 鍒嗛〉鍙傛暟
+ * @return 娴佺▼spel琛ㄨ揪寮忓畾涔夊垎椤靛垪琛�
+ */
+ @Override
+ public TableDataInfo<FlowSpelVo> queryPageList(FlowSpelBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper<FlowSpel> lqw = buildQueryWrapper(bo);
+ Page<FlowSpelVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 鏌ヨ绗﹀悎鏉′欢鐨勬祦绋媠pel琛ㄨ揪寮忓畾涔夊垪琛�
+ *
+ * @param bo 鏌ヨ鏉′欢
+ * @return 娴佺▼spel琛ㄨ揪寮忓畾涔夊垪琛�
+ */
+ @Override
+ public List<FlowSpelVo> queryList(FlowSpelBo bo) {
+ LambdaQueryWrapper<FlowSpel> lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper<FlowSpel> buildQueryWrapper(FlowSpelBo bo) {
+ Map<String, Object> params = bo.getParams();
+ LambdaQueryWrapper<FlowSpel> lqw = Wrappers.lambdaQuery();
+ lqw.orderByAsc(FlowSpel::getId);
+ lqw.like(StringUtils.isNotBlank(bo.getComponentName()), FlowSpel::getComponentName, bo.getComponentName());
+ lqw.like(StringUtils.isNotBlank(bo.getMethodName()), FlowSpel::getMethodName, bo.getMethodName());
+ lqw.eq(StringUtils.isNotBlank(bo.getMethodParams()), FlowSpel::getMethodParams, bo.getMethodParams());
+ lqw.eq(StringUtils.isNotBlank(bo.getViewSpel()), FlowSpel::getViewSpel, bo.getViewSpel());
+ lqw.eq(StringUtils.isNotBlank(bo.getStatus()), FlowSpel::getStatus, bo.getStatus());
+ lqw.like(StringUtils.isNotBlank(bo.getRemark()), FlowSpel::getRemark, bo.getRemark());
+ return lqw;
+ }
+
+ /**
+ * 鏂板娴佺▼spel琛ㄨ揪寮忓畾涔�
+ *
+ * @param bo 娴佺▼spel琛ㄨ揪寮忓畾涔�
+ * @return 鏄惁鏂板鎴愬姛
+ */
+ @Override
+ public Boolean insertByBo(FlowSpelBo bo) {
+ FlowSpel add = MapstructUtils.convert(bo, FlowSpel.class);
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setId(add.getId());
+ }
+ return flag;
+ }
+
+ /**
+ * 淇敼娴佺▼spel琛ㄨ揪寮忓畾涔�
+ *
+ * @param bo 娴佺▼spel琛ㄨ揪寮忓畾涔�
+ * @return 鏄惁淇敼鎴愬姛
+ */
+ @Override
+ public Boolean updateByBo(FlowSpelBo bo) {
+ FlowSpel update = MapstructUtils.convert(bo, FlowSpel.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+ */
+ private void validEntityBeforeSave(FlowSpel entity){
+ //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+ }
+
+ /**
+ * 鏍¢獙骞舵壒閲忓垹闄ゆ祦绋媠pel琛ㄨ揪寮忓畾涔変俊鎭�
+ *
+ * @param ids 寰呭垹闄ょ殑涓婚敭闆嗗悎
+ * @param isValid 鏄惁杩涜鏈夋晥鎬ф牎楠�
+ * @return 鏄惁鍒犻櫎鎴愬姛
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+ if(isValid){
+ //TODO 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+ }
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+
+ /**
+ * 鏌ヨspel骞惰繑鍥炰换鍔℃寚娲剧殑鍒楄〃锛屾敮鎸佸垎椤�
+ *
+ * @param taskQuery 鏌ヨ鏉′欢
+ * @return 鍔炵悊浜�
+ */
+ @Override
+ public TaskAssigneeDTO selectSpelByTaskAssigneeList(TaskAssigneeBody taskQuery) {
+ PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum());
+ FlowSpelBo bo = new FlowSpelBo();
+ bo.setViewSpel(taskQuery.getHandlerCode());
+ bo.setRemark(taskQuery.getHandlerName());
+ bo.setStatus(SystemConstants.NORMAL);
+ Map<String, Object> params = bo.getParams();
+ params.put("beginTime", taskQuery.getBeginTime());
+ params.put("endTime", taskQuery.getEndTime());
+ TableDataInfo<FlowSpelVo> page = this.queryPageList(bo, pageQuery);
+ // 浣跨敤灏佽鐨勫瓧娈垫槧灏勬柟娉曡繘琛岃浆鎹�
+ List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
+ FlowSpelVo::getViewSpel, item -> "", FlowSpelVo::getRemark, item -> "", FlowSpelVo::getCreateTime);
+ return new TaskAssigneeDTO(page.getTotal(), handlers);
+ }
+
+ /**
+ * 鏍规嵁瑙嗗浘 SpEL 琛ㄨ揪寮忓垪琛紝鏌ヨ瀵瑰簲鐨勫娉ㄤ俊鎭�
+ *
+ * @param viewSpels SpEL 琛ㄨ揪寮忓垪琛�
+ * @return 鏄犲皠琛細key 涓� SpEL 琛ㄨ揪寮忥紝value 涓哄搴斿娉紱鑻ヤ负绌哄垯杩斿洖绌� Map
+ */
+ @Override
+ public Map<String, String> selectRemarksBySpels(List<String> viewSpels) {
+ if (CollUtil.isEmpty(viewSpels)) {
+ return Collections.emptyMap();
+ }
+ List<FlowSpel> list = baseMapper.selectList(
+ new LambdaQueryWrapper<FlowSpel>()
+ .select(FlowSpel::getViewSpel, FlowSpel::getRemark)
+ .in(FlowSpel::getViewSpel, viewSpels)
+ );
+ return StreamUtils.toMap(list, FlowSpel::getViewSpel, x ->
+ StringUtils.isEmpty(x.getRemark()) ? "" : x.getRemark()
+ );
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java
new file mode 100755
index 0000000..9e9c091
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java
@@ -0,0 +1,291 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Pair;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.dto.DeptDTO;
+import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
+import org.dromara.common.core.domain.dto.UserDTO;
+import org.dromara.common.core.domain.model.TaskAssigneeBody;
+import org.dromara.common.core.enums.FormatsType;
+import org.dromara.common.core.service.*;
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.warm.flow.ui.dto.HandlerFunDto;
+import org.dromara.warm.flow.ui.dto.HandlerQuery;
+import org.dromara.warm.flow.ui.dto.TreeFunDto;
+import org.dromara.warm.flow.ui.service.HandlerSelectService;
+import org.dromara.warm.flow.ui.vo.HandlerFeedBackVo;
+import org.dromara.warm.flow.ui.vo.HandlerSelectVo;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.enums.TaskAssigneeEnum;
+import org.dromara.workflow.service.IFlwSpelService;
+import org.dromara.workflow.service.IFlwTaskAssigneeService;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 娴佺▼璁捐鍣�-鑾峰彇鍔炵悊浜烘潈闄愯缃垪琛�
+ *
+ * @author AprilWind
+ */
+@ConditionalOnEnable
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, HandlerSelectService {
+
+ private static final String DEFAULT_GROUP_NAME = "榛樿鍒嗙粍";
+ private final TaskAssigneeService taskAssigneeService;
+ private final UserService userService;
+ private final DeptService deptService;
+ private final RoleService roleService;
+ private final PostService postService;
+ private final IFlwSpelService spelService;
+
+ /**
+ * 鑾峰彇鍔炵悊浜烘潈闄愯缃垪琛╰abs椤电
+ *
+ * @return tabs椤电
+ */
+ @Override
+ public List<String> getHandlerType() {
+ return TaskAssigneeEnum.getAssigneeTypeList();
+ }
+
+ /**
+ * 鑾峰彇鍔炵悊鍒楄〃, 鍚屾椂鏋勫缓宸︿晶閮ㄩ棬鏍戠姸缁撴瀯
+ *
+ * @param query 鏌ヨ鏉′欢
+ * @return HandlerSelectVo
+ */
+ @Override
+ public HandlerSelectVo getHandlerSelect(HandlerQuery query) {
+ // 鑾峰彇浠诲姟鍔炵悊绫诲瀷
+ TaskAssigneeEnum type = TaskAssigneeEnum.fromDesc(query.getHandlerType());
+ // 杞崲鏌ヨ鏉′欢涓� TaskAssigneeBody
+ TaskAssigneeBody taskQuery = BeanUtil.toBean(query, TaskAssigneeBody.class);
+
+ // 缁熶竴鏌ヨ骞舵瀯寤轰笟鍔℃暟鎹�
+ TaskAssigneeDTO dto = fetchTaskAssigneeData(type, taskQuery);
+ List<DeptDTO> depts = fetchDeptData(type);
+
+ return getHandlerSelectVo(buildHandlerData(dto, type), buildDeptTree(depts));
+ }
+
+ /**
+ * 鍔炵悊浜烘潈闄愬悕绉板洖鏄�
+ *
+ * @param storageIds 鍏ュ簱涓婚敭闆嗗悎
+ * @return 缁撴灉
+ */
+ @Override
+ public List<HandlerFeedBackVo> handlerFeedback(List<String> storageIds) {
+ if (CollUtil.isEmpty(storageIds)) {
+ return Collections.emptyList();
+ }
+ // 瑙f瀽骞跺綊绫� ID锛屽悓鏃惰褰曞師濮嬮『搴忓拰瀵瑰簲瑙f瀽缁撴灉
+ Map<TaskAssigneeEnum, List<String>> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class);
+ Map<String, Pair<TaskAssigneeEnum, String>> parsedMap = new LinkedHashMap<>();
+ for (String storageId : storageIds) {
+ Pair<TaskAssigneeEnum, String> parsed = this.parseStorageId(storageId);
+ parsedMap.put(storageId, parsed);
+ if (parsed != null) {
+ typeIdMap.computeIfAbsent(parsed.getKey(), k -> new ArrayList<>()).add(parsed.getValue());
+ }
+ }
+
+ // 鏌ヨ鎵�鏈夌被鍨嬪搴旂殑 ID 鍚嶇О鏄犲皠
+ Map<TaskAssigneeEnum, Map<String, String>> nameMap = new EnumMap<>(TaskAssigneeEnum.class);
+ typeIdMap.forEach((type, ids) -> nameMap.put(type, this.getNamesByType(type, ids)));
+ // 缁勮杩斿洖缁撴灉锛屼繚鎸佸師濮嬮『搴�
+ return parsedMap.entrySet().stream()
+ .map(entry -> {
+ String storageId = entry.getKey();
+ Pair<TaskAssigneeEnum, String> parsed = entry.getValue();
+ String handlerName = (parsed == null) ? null
+ : nameMap.getOrDefault(parsed.getKey(), Collections.emptyMap())
+ .get(parsed.getValue());
+ return new HandlerFeedBackVo(storageId, handlerName);
+ }).toList();
+ }
+
+ /**
+ * 鏍规嵁浠诲姟鍔炵悊绫诲瀷鏌ヨ瀵瑰簲鐨勬暟鎹�
+ */
+ private TaskAssigneeDTO fetchTaskAssigneeData(TaskAssigneeEnum type, TaskAssigneeBody taskQuery) {
+ return switch (type) {
+ case USER -> taskAssigneeService.selectUsersByTaskAssigneeList(taskQuery);
+ case ROLE -> taskAssigneeService.selectRolesByTaskAssigneeList(taskQuery);
+ case DEPT -> taskAssigneeService.selectDeptsByTaskAssigneeList(taskQuery);
+ case POST -> taskAssigneeService.selectPostsByTaskAssigneeList(taskQuery);
+ case SPEL -> spelService.selectSpelByTaskAssigneeList(taskQuery);
+ };
+ }
+
+ /**
+ * 鏍规嵁浠诲姟鍔炵悊绫诲瀷鑾峰彇閮ㄩ棬鏁版嵁
+ */
+ private List<DeptDTO> fetchDeptData(TaskAssigneeEnum type) {
+ if (type.needsDeptService()) {
+ return deptService.selectDeptsByList();
+ }
+ return new ArrayList<>();
+ }
+
+ /**
+ * 鑾峰彇鏉冮檺鍒嗙粍鍚嶇О
+ *
+ * @param type 浠诲姟鍒嗛厤浜烘灇涓�
+ * @param groupName 鏉冮檺鍒嗙粍
+ * @return 鏉冮檺鍒嗙粍鍚嶇О
+ */
+ private String getGroupName(TaskAssigneeEnum type, String groupName) {
+ if (StringUtils.isEmpty(groupName)) {
+ return DEFAULT_GROUP_NAME;
+ }
+ if (type.needsDeptService()) {
+ return deptService.selectDeptNameByIds(groupName);
+ }
+ return DEFAULT_GROUP_NAME;
+ }
+
+ /**
+ * 鏋勫缓閮ㄩ棬鏍戠姸缁撴瀯
+ */
+ private TreeFunDto<DeptDTO> buildDeptTree(List<DeptDTO> depts) {
+ return new TreeFunDto<>(depts)
+ .setId(dept -> Convert.toStr(dept.getDeptId()))
+ .setName(DeptDTO::getDeptName)
+ .setParentId(dept -> Convert.toStr(dept.getParentId()));
+ }
+
+ /**
+ * 鏋勫缓浠诲姟鍔炵悊浜烘暟鎹�
+ */
+ private HandlerFunDto<TaskAssigneeDTO.TaskHandler> buildHandlerData(TaskAssigneeDTO dto, TaskAssigneeEnum type) {
+ return new HandlerFunDto<>(dto.getList(), dto.getTotal())
+ .setStorageId(assignee -> type.getCode() + assignee.getStorageId())
+ .setHandlerCode(assignee -> StringUtils.blankToDefault(assignee.getHandlerCode(), "鏃�"))
+ .setHandlerName(assignee -> StringUtils.blankToDefault(assignee.getHandlerName(), "鏃�"))
+ .setGroupName(assignee -> this.getGroupName(type, assignee.getGroupName()))
+ .setCreateTime(assignee -> DateUtils.parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, assignee.getCreateTime()));
+ }
+
+ /**
+ * 鎵归噺瑙f瀽澶氫釜瀛樺偍鏍囪瘑绗︼紙storageIds锛夛紝鎸夌被鍨嬪垎绫诲苟鍚堝苟鏌ヨ鐢ㄦ埛鍒楄〃
+ * 杈撳叆鏍煎紡鏀寔澶氫釜浠ラ�楀彿鍒嗛殧鐨勬爣璇嗭紙濡� "user:123,role:456,789"锛�
+ * 浼氳嚜鍔ㄥ幓閲嶈繑鍥炵粨鏋滐紝闈炴硶鏍煎紡鐨勬爣璇嗗皢琚拷鐣�
+ *
+ * @param storageIds 澶氫釜瀛樺偍鏍囪瘑绗﹀瓧绗︿覆锛堥�楀彿鍒嗛殧锛�
+ * @return 鍚堝苟鍚庣殑鐢ㄦ埛鍒楄〃锛屽幓閲嶅悗杩斿洖锛岄潪娉曟牸寮忕殑鏍囪瘑灏嗚璺宠繃
+ */
+ @Override
+ public List<UserDTO> fetchUsersByStorageIds(String storageIds) {
+ if (StringUtils.isEmpty(storageIds)) {
+ return List.of();
+ }
+ Map<TaskAssigneeEnum, List<String>> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class);
+ for (String storageId : storageIds.split(StringUtils.SEPARATOR)) {
+ Pair<TaskAssigneeEnum, String> parsed = this.parseStorageId(storageId);
+ if (parsed != null) {
+ typeIdMap.computeIfAbsent(parsed.getKey(), k -> new ArrayList<>()).add(parsed.getValue());
+ }
+ }
+ return typeIdMap.entrySet().stream()
+ .flatMap(entry -> this.getUsersByType(entry.getKey(), entry.getValue()).stream())
+ .distinct()
+ .toList();
+ }
+
+ /**
+ * 鏍规嵁鎸囧畾鐨勪换鍔″垎閰嶇被鍨嬶紙TaskAssigneeEnum锛夊拰 ID 鍒楄〃锛岃幏鍙栧搴旂殑鐢ㄦ埛淇℃伅鍒楄〃
+ *
+ * @param type 浠诲姟鍒嗛厤绫诲瀷锛岃〃绀虹敤鎴枫�佽鑹层�侀儴闂ㄦ垨鍏朵粬锛圱askAssigneeEnum 鏋氫妇鍊硷級
+ * @param ids 涓庢寚瀹氬垎閰嶇被鍨嬪叧鑱旂殑 ID 鍒楄〃锛堜緥濡傜敤鎴稩D銆佽鑹睮D銆侀儴闂↖D绛夛級
+ * @return 杩斿洖鍖呭惈鐢ㄦ埛淇℃伅鐨勫垪琛ㄣ�傚鏋滅被鍨嬩负鐢ㄦ埛锛圲SER锛夛紝鍒欓�氳繃鐢ㄦ埛ID鍒楄〃鏌ヨ锛�
+ * 濡傛灉绫诲瀷涓鸿鑹诧紙ROLE锛夛紝鍒欓�氳繃瑙掕壊ID鍒楄〃鏌ヨ锛�
+ * 濡傛灉绫诲瀷涓洪儴闂紙DEPT锛夛紝鍒欓�氳繃閮ㄩ棬ID鍒楄〃鏌ヨ锛�
+ * 濡傛灉绫诲瀷涓哄矖浣嶏紙POST锛夋垨鏃犳硶璇嗗埆鐨勭被鍨嬶紝鍒欒繑鍥炵┖鍒楄〃
+ */
+ private List<UserDTO> getUsersByType(TaskAssigneeEnum type, List<String> ids) {
+ if (type == TaskAssigneeEnum.SPEL) {
+ return new ArrayList<>();
+ }
+ List<Long> longIds = StreamUtils.toList(ids, Convert::toLong);
+ return switch (type) {
+ case USER -> userService.selectListByIds(longIds);
+ case ROLE -> userService.selectUsersByRoleIds(longIds);
+ case DEPT -> userService.selectUsersByDeptIds(longIds);
+ case POST -> userService.selectUsersByPostIds(longIds);
+ default -> new ArrayList<>();
+ };
+ }
+
+ /**
+ * 鏍规嵁浠诲姟鍒嗛厤绫诲瀷鍜屽搴� ID 鍒楄〃锛屾壒閲忔煡璇㈠悕绉版槧灏勫叧绯�
+ *
+ * @param type 鍒嗛厤绫诲瀷锛堢敤鎴枫�佽鑹层�侀儴闂ㄣ�佸矖浣嶏級
+ * @param ids ID 鍒楄〃锛堝鐢ㄦ埛ID銆佽鑹睮D绛夛級
+ * @return 杩斿洖 Map锛屽叾涓� key 涓� ID锛寁alue 涓哄搴旂殑鍚嶇О
+ */
+ private Map<String, String> getNamesByType(TaskAssigneeEnum type, List<String> ids) {
+ if (type == TaskAssigneeEnum.SPEL) {
+ return spelService.selectRemarksBySpels(ids);
+ }
+
+ List<Long> longIds = StreamUtils.toList(ids, Convert::toLong);
+ Map<Long, String> rawMap = switch (type) {
+ case USER -> userService.selectUserNamesByIds(longIds);
+ case ROLE -> roleService.selectRoleNamesByIds(longIds);
+ case DEPT -> deptService.selectDeptNamesByIds(longIds);
+ case POST -> postService.selectPostNamesByIds(longIds);
+ default -> Collections.emptyMap();
+ };
+ if (MapUtil.isEmpty(rawMap)) {
+ return Collections.emptyMap();
+ }
+ return rawMap.entrySet()
+ .stream()
+ .collect(Collectors.toMap(
+ e -> Convert.toStr(e.getKey()),
+ Map.Entry::getValue
+ ));
+ }
+
+ /**
+ * 瑙f瀽 storageId 瀛楃涓诧紝杩斿洖绫诲瀷鍜孖D鐨勭粍鍚�
+ *
+ * @param storageId 渚嬪 "user:123" 鎴� "456"
+ * @return Pair(TaskAssigneeEnum, Long)锛屽鏋滄牸寮忛潪娉曡繑鍥� null
+ */
+ private Pair<TaskAssigneeEnum, String> parseStorageId(String storageId) {
+ if (StringUtils.isBlank(storageId)) {
+ return null;
+ }
+ if (TaskAssigneeEnum.isSpelExpression(storageId)) {
+ return Pair.of(TaskAssigneeEnum.SPEL, storageId);
+ }
+ try {
+ String[] parts = storageId.split(StrUtil.COLON, 2);
+ if (parts.length < 2) {
+ return Pair.of(TaskAssigneeEnum.USER, parts[0]);
+ } else {
+ TaskAssigneeEnum type = TaskAssigneeEnum.fromCode(parts[0] + StrUtil.COLON);
+ return Pair.of(type, parts[1]);
+ }
+ } catch (Exception e) {
+ log.warn("瑙f瀽 storageId 澶辫触锛屾牸寮忛潪娉曪細{}锛岄敊璇俊鎭細{}", storageId, e.getMessage());
+ return null;
+ }
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java
new file mode 100755
index 0000000..0a8bdc7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java
@@ -0,0 +1,859 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.lock.annotation.Lock4j;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.dto.StartProcessReturnDTO;
+import org.dromara.common.core.domain.dto.UserDTO;
+import org.dromara.common.core.enums.BusinessStatusEnum;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.UserService;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.utils.IdGeneratorUtil;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.warm.flow.core.FlowEngine;
+import org.dromara.warm.flow.core.dto.FlowParams;
+import org.dromara.warm.flow.core.entity.*;
+import org.dromara.warm.flow.core.enums.CooperateType;
+import org.dromara.warm.flow.core.enums.NodeType;
+import org.dromara.warm.flow.core.enums.SkipType;
+import org.dromara.warm.flow.core.enums.UserType;
+import org.dromara.warm.flow.core.service.*;
+import org.dromara.warm.flow.core.utils.ExpressionUtil;
+import org.dromara.warm.flow.core.utils.MapUtil;
+import org.dromara.warm.flow.orm.entity.*;
+import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
+import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper;
+import org.dromara.warm.flow.orm.mapper.FlowNodeMapper;
+import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.common.enums.TaskAssigneeType;
+import org.dromara.workflow.common.enums.TaskStatusEnum;
+import org.dromara.workflow.domain.FlowInstanceBizExt;
+import org.dromara.workflow.domain.bo.*;
+import org.dromara.workflow.domain.vo.FlowCopyVo;
+import org.dromara.workflow.domain.vo.FlowHisTaskVo;
+import org.dromara.workflow.domain.vo.FlowTaskVo;
+import org.dromara.workflow.domain.vo.NodeExtVo;
+import org.dromara.workflow.mapper.FlwCategoryMapper;
+import org.dromara.workflow.mapper.FlwInstanceBizExtMapper;
+import org.dromara.workflow.mapper.FlwTaskMapper;
+import org.dromara.workflow.service.IFlwCommonService;
+import org.dromara.workflow.service.IFlwNodeExtService;
+import org.dromara.workflow.service.IFlwTaskAssigneeService;
+import org.dromara.workflow.service.IFlwTaskService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
+import static org.dromara.workflow.common.constant.FlowConstant.*;
+
+/**
+ * 浠诲姟 鏈嶅姟灞傚疄鐜�
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class FlwTaskServiceImpl implements IFlwTaskService {
+
+ private final TaskService taskService;
+ private final InsService insService;
+ private final DefService defService;
+ private final HisTaskService hisTaskService;
+ private final NodeService nodeService;
+ private final FlowInstanceMapper flowInstanceMapper;
+ private final FlowTaskMapper flowTaskMapper;
+ private final FlowHisTaskMapper flowHisTaskMapper;
+ private final UserService userService;
+ private final FlwTaskMapper flwTaskMapper;
+ private final FlwCategoryMapper flwCategoryMapper;
+ private final FlowNodeMapper flowNodeMapper;
+ private final IFlwTaskAssigneeService flwTaskAssigneeService;
+ private final IFlwCommonService flwCommonService;
+ private final IFlwNodeExtService flwNodeExtService;
+ private final FlwInstanceBizExtMapper flwInstanceBizExtMapper;
+
+ /**
+ * 鍚姩浠诲姟
+ *
+ * @param startProcessBo 鍚姩娴佺▼鍙傛暟
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ @Lock4j(keys = {"#startProcessBo.flowCode + #startProcessBo.businessId"})
+ public StartProcessReturnDTO startWorkFlow(StartProcessBo startProcessBo) {
+ String businessId = startProcessBo.getBusinessId();
+ if (StringUtils.isBlank(businessId)) {
+ throw new ServiceException("鍚姩宸ヤ綔娴佹椂蹇呴』鍖呭惈涓氬姟ID");
+ }
+
+ // 鍚姩娴佺▼瀹炰緥锛堟彁浜ょ敵璇凤級
+ Map<String, Object> variables = startProcessBo.getVariables();
+ // 娴佺▼鍙戣捣浜�
+ variables.put(INITIATOR, LoginHelper.getUserIdStr());
+ // 鍙戣捣浜洪儴闂╥d
+ variables.put(INITIATOR_DEPT_ID, LoginHelper.getDeptId());
+ // 涓氬姟id
+ variables.put(BUSINESS_ID, businessId);
+ FlowInstanceBizExt bizExt = startProcessBo.getBizExt();
+
+ // 鑾峰彇宸叉湁娴佺▼瀹炰緥
+ FlowInstance flowInstance = flowInstanceMapper.selectOne(new LambdaQueryWrapper<>(FlowInstance.class)
+ .eq(FlowInstance::getBusinessId, businessId));
+
+ if (ObjectUtil.isNotNull(flowInstance)) {
+ // 宸插瓨鍦ㄦ祦绋�
+ BusinessStatusEnum.checkStartStatus(flowInstance.getFlowStatus());
+ List<Task> taskList = taskService.list(new FlowTask().setInstanceId(flowInstance.getId()));
+ taskService.mergeVariable(flowInstance, variables);
+ insService.updateById(flowInstance);
+ StartProcessReturnDTO dto = new StartProcessReturnDTO();
+ dto.setProcessInstanceId(taskList.get(0).getInstanceId());
+ dto.setTaskId(taskList.get(0).getId());
+ // 淇濆瓨娴佺▼瀹炰緥涓氬姟淇℃伅
+ this.buildFlowInstanceBizExt(flowInstance, bizExt);
+ return dto;
+ }
+
+ // 灏嗘祦绋嬪畾涔夊唴鐨勬墿灞曞弬鏁拌缃埌鍙橀噺涓�
+ Definition definition = FlowEngine.defService().getPublishByFlowCode(startProcessBo.getFlowCode());
+ if (ObjectUtil.isNull(definition)) {
+ throw new ServiceException("娴佺▼銆�" + startProcessBo.getFlowCode() + "銆戞湭鍙戝竷锛岃鍏堝湪娴佺▼璁捐鍣ㄤ腑鍙戝竷娴佺▼瀹氫箟");
+ }
+ Dict dict = JsonUtils.parseMap(definition.getExt());
+ boolean autoPass = !ObjectUtil.isNull(dict) && dict.getBool(FlowConstant.AUTO_PASS);
+ variables.put(FlowConstant.AUTO_PASS, autoPass);
+ variables.put(FlowConstant.BUSINESS_CODE, this.generateBusinessCode(bizExt));
+ FlowParams flowParams = FlowParams.build()
+ .handler(startProcessBo.getHandler())
+ .flowCode(startProcessBo.getFlowCode())
+ .variable(startProcessBo.getVariables())
+ .flowStatus(BusinessStatusEnum.DRAFT.getStatus());
+ Instance instance = insService.start(businessId, flowParams);
+ // 淇濆瓨娴佺▼瀹炰緥涓氬姟淇℃伅
+ this.buildFlowInstanceBizExt(instance, bizExt);
+ // 鐢宠浜烘墽琛屾祦绋�
+ List<Task> taskList = taskService.list(new FlowTask().setInstanceId(instance.getId()));
+ if (taskList.size() > 1) {
+ throw new ServiceException("璇锋鏌ユ祦绋嬬涓�涓幆鑺傛槸鍚︿负鐢宠浜猴紒");
+ }
+ StartProcessReturnDTO dto = new StartProcessReturnDTO();
+ dto.setProcessInstanceId(instance.getId());
+ dto.setTaskId(taskList.get(0).getId());
+ return dto;
+ }
+
+ /**
+ * 鐢熸垚涓氬姟缂栧彿锛屽鏋滃凡鏈夊垯鐩存帴杩斿洖宸叉湁鍊�
+ */
+ private String generateBusinessCode(FlowInstanceBizExt bizExt) {
+ if (StringUtils.isBlank(bizExt.getBusinessCode())) {
+ // TODO: 鎸夌収鑷繁涓氬姟瑙勫垯鐢熸垚缂栧彿
+ String businessCode = Convert.toStr(System.currentTimeMillis());
+ bizExt.setBusinessCode(businessCode);
+ return businessCode;
+ }
+ return bizExt.getBusinessCode();
+ }
+
+ /**
+ * 鏋勫缓娴佺▼瀹炰緥涓氬姟淇℃伅
+ *
+ * @param instance 娴佺▼瀹炰緥
+ * @param bizExt 娴佺▼涓氬姟鎵╁睍淇℃伅
+ */
+ private void buildFlowInstanceBizExt(Instance instance, FlowInstanceBizExt bizExt) {
+ bizExt.setInstanceId(instance.getId());
+ bizExt.setBusinessId(instance.getBusinessId());
+ flwInstanceBizExtMapper.saveOrUpdateByInstanceId(bizExt);
+ }
+
+ /**
+ * 鍔炵悊浠诲姟
+ *
+ * @param completeTaskBo 鍔炵悊浠诲姟鍙傛暟
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ @Lock4j(keys = {"#completeTaskBo.taskId"})
+ public boolean completeTask(CompleteTaskBo completeTaskBo) {
+ // 鑾峰彇浠诲姟ID骞舵煡璇㈠搴旂殑娴佺▼浠诲姟鍜屽疄渚嬩俊鎭�
+ Long taskId = completeTaskBo.getTaskId();
+ List<String> messageType = completeTaskBo.getMessageType();
+ String notice = completeTaskBo.getNotice();
+ // 鑾峰彇鎶勯�佷汉
+ List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
+ // 璁剧疆鎶勯�佷汉
+ Map<String, Object> variables = completeTaskBo.getVariables();
+ variables.put(FlowConstant.FLOW_COPY_LIST, flowCopyList);
+ // 娑堟伅绫诲瀷
+ variables.put(FlowConstant.MESSAGE_TYPE, messageType);
+ // 娑堟伅閫氱煡
+ variables.put(FlowConstant.MESSAGE_NOTICE, notice);
+
+
+ FlowTask flowTask = flowTaskMapper.selectById(taskId);
+ if (ObjectUtil.isNull(flowTask)) {
+ throw new ServiceException("娴佺▼浠诲姟涓嶅瓨鍦ㄦ垨浠诲姟宸插鎵癸紒");
+ }
+ Instance ins = insService.getById(flowTask.getInstanceId());
+ // 妫�鏌ユ祦绋嬬姸鎬佹槸鍚︿负鑽夌銆佸凡鎾ら攢鎴栧凡閫�鍥炵姸鎬侊紝鑻ユ槸鍒欐墽琛屾祦绋嬫彁浜ょ洃鍚�
+ if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
+ variables.put(FlowConstant.SUBMIT, true);
+ }
+ // 璁剧疆寮圭獥澶勭悊浜�
+ Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
+ if (CollUtil.isNotEmpty(assigneeMap)) {
+ variables.putAll(assigneeMap);
+ }
+ // 鏋勫缓娴佺▼鍙傛暟锛屽寘鎷彉閲忋�佽烦杞被鍨嬨�佹秷鎭�佸鐞嗕汉銆佹潈闄愮瓑淇℃伅
+ FlowParams flowParams = FlowParams.build()
+ .handler(completeTaskBo.getHandler())
+ .variable(variables)
+ .ignore(Convert.toBool(variables.getOrDefault(VAR_IGNORE, false)))
+ .ignoreDepute(Convert.toBool(variables.getOrDefault(VAR_IGNORE_DEPUTE, false)))
+ .ignoreCooperate(Convert.toBool(variables.getOrDefault(VAR_IGNORE_COOPERATE, false)))
+ .skipType(SkipType.PASS.getKey())
+ .message(completeTaskBo.getMessage())
+ .flowStatus(BusinessStatusEnum.WAITING.getStatus())
+ .hisStatus(TaskStatusEnum.PASS.getStatus())
+ .hisTaskExt(completeTaskBo.getFileId());
+ Boolean autoPass = Convert.toBool(variables.getOrDefault(AUTO_PASS, false));
+ skipTask(taskId, flowParams, flowTask.getInstanceId(), autoPass);
+ return true;
+ }
+
+ /**
+ * 娴佺▼鍔炵悊
+ *
+ * @param taskId 浠诲姟ID
+ * @param flowParams 鍙傛暟
+ * @param instanceId 瀹炰緥ID
+ * @param autoPass 鑷姩瀹℃壒
+ */
+ private void skipTask(Long taskId, FlowParams flowParams, Long instanceId, Boolean autoPass) {
+ // 鎵ц浠诲姟璺宠浆锛屽苟鏍规嵁杩斿洖鐨勫鐞嗕汉璁剧疆涓嬩竴姝ュ鐞嗕汉
+ taskService.skip(taskId, flowParams);
+ List<FlowTask> flowTaskList = selectByInstId(instanceId);
+ if (CollUtil.isEmpty(flowTaskList)) {
+ return;
+ }
+ List<User> userList = FlowEngine.userService()
+ .getByAssociateds(StreamUtils.toList(flowTaskList, FlowTask::getId));
+ if (CollUtil.isEmpty(userList)) {
+ return;
+ }
+ for (FlowTask task : flowTaskList) {
+ if (!task.getId().equals(taskId) && autoPass) {
+ List<User> users = StreamUtils.filter(userList, e -> ObjectUtil.equals(task.getId(), e.getAssociated()) && ObjectUtil.equal(e.getProcessedBy(), LoginHelper.getUserIdStr()));
+ if (CollUtil.isEmpty(users)) {
+ continue;
+ }
+ flowParams.
+ message("娴佺▼寮曟搸鑷姩瀹℃壒锛�").
+ variable(Map.of(
+ FlowConstant.SUBMIT, false,
+ FlowConstant.FLOW_COPY_LIST, Collections.emptyList(),
+ FlowConstant.MESSAGE_NOTICE, StringUtils.EMPTY));
+ skipTask(task.getId(), flowParams, instanceId, true);
+ }
+ }
+ }
+
+ /**
+ * 璁剧疆寮圭獥澶勭悊浜�
+ *
+ * @param assigneeMap 澶勭悊浜�
+ * @param variablesMap 鍙橀噺
+ */
+ private Map<String, Object> setPopAssigneeMap(Map<String, Object> assigneeMap, Map<String, Object> variablesMap) {
+ Map<String, Object> map = new HashMap<>();
+ if (CollUtil.isEmpty(assigneeMap)) {
+ return map;
+ }
+ for (Map.Entry<String, Object> entry : assigneeMap.entrySet()) {
+ if (variablesMap.containsKey(entry.getKey())) {
+ String userIds = variablesMap.get(entry.getKey()).toString();
+ if (StringUtils.isNotBlank(userIds)) {
+ Set<String> hashSet = new HashSet<>();
+ //寮圭獥浼犲叆鐨勯�変汉
+ List<String> popUserIds = Arrays.asList(entry.getValue().toString().split(StringUtils.SEPARATOR));
+ //宸叉湁鐨勯�変汉
+ List<String> variableUserIds = Arrays.asList(userIds.split(StringUtils.SEPARATOR));
+ hashSet.addAll(popUserIds);
+ hashSet.addAll(variableUserIds);
+ map.put(TaskStatusEnum.PASS.getStatus() + StrUtil.COLON + entry.getKey(), StringUtils.joinComma(hashSet));
+ map.put(TaskStatusEnum.BACK.getStatus() + StrUtil.COLON + entry.getKey(), StringUtils.joinComma(hashSet));
+ }
+ } else {
+ map.put(TaskStatusEnum.PASS.getStatus() + StrUtil.COLON + entry.getKey(), entry.getValue());
+ map.put(TaskStatusEnum.BACK.getStatus() + StrUtil.COLON + entry.getKey(), entry.getValue());
+ }
+ }
+ return map;
+ }
+
+ /**
+ * 娣诲姞鎶勯�佷汉
+ *
+ * @param task 浠诲姟淇℃伅
+ * @param flowCopyList 鎶勯�佷汉
+ */
+ @Override
+ public void setCopy(Task task, List<FlowCopyBo> flowCopyList) {
+ if (CollUtil.isEmpty(flowCopyList)) {
+ return;
+ }
+ // 娣诲姞鎶勯�佷汉璁板綍
+ FlowHisTask flowHisTask = flowHisTaskMapper.selectList(
+ new LambdaQueryWrapper<>(FlowHisTask.class)
+ .eq(FlowHisTask::getTaskId, task.getId())).get(0);
+ FlowNode flowNode = new FlowNode();
+ flowNode.setNodeCode(flowHisTask.getTargetNodeCode());
+ flowNode.setNodeName(flowHisTask.getTargetNodeName());
+ //鐢熸垚鏂扮殑浠诲姟id
+ long taskId = IdGeneratorUtil.nextLongId();
+ task.setId(taskId);
+ task.setNodeName("銆愭妱閫併��" + task.getNodeName());
+ Date updateTime = new Date(flowHisTask.getUpdateTime().getTime() - 1000);
+ FlowParams flowParams = FlowParams.build()
+ .skipType(SkipType.NONE.getKey())
+ .hisStatus(TaskStatusEnum.COPY.getStatus())
+ .message("銆愭妱閫佺粰銆�" + StreamUtils.join(flowCopyList, FlowCopyBo::getUserName));
+ HisTask hisTask = hisTaskService.setSkipHisTask(task, flowNode, flowParams);
+ hisTask.setCreateTime(updateTime);
+ hisTask.setUpdateTime(updateTime);
+ hisTaskService.save(hisTask);
+ List<User> userList = StreamUtils.toList(flowCopyList, x ->
+ new FlowUser()
+ .setType(TaskAssigneeType.COPY.getCode())
+ .setProcessedBy(Convert.toStr(x.getUserId()))
+ .setAssociated(taskId));
+ // 鎵归噺淇濆瓨鎶勯�佷汉鍛�
+ FlowEngine.userService().saveBatch(userList);
+ }
+
+ /**
+ * 鏌ヨ褰撳墠鐢ㄦ埛鐨勫緟鍔炰换鍔�
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ */
+ @Override
+ public TableDataInfo<FlowTaskVo> pageByTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
+ QueryWrapper<FlowTaskBo> queryWrapper = buildQueryWrapper(flowTaskBo);
+ queryWrapper.eq("t.node_type", NodeType.BETWEEN.getKey());
+ queryWrapper.in("t.processed_by", LoginHelper.getUserIdStr());
+ queryWrapper.in("t.flow_status", BusinessStatusEnum.WAITING.getStatus());
+ Page<FlowTaskVo> page = flwTaskMapper.getListRunTask(pageQuery.build(), queryWrapper);
+ this.wrapAssigneeInfo(page.getRecords());
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏌ヨ褰撳墠鐢ㄦ埛鐨勫凡鍔炰换鍔�
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ */
+ @Override
+ public TableDataInfo<FlowHisTaskVo> pageByTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
+ QueryWrapper<FlowTaskBo> queryWrapper = buildQueryWrapper(flowTaskBo);
+ queryWrapper.eq("t.node_type", NodeType.BETWEEN.getKey());
+ queryWrapper.in("t.approver", LoginHelper.getUserIdStr());
+ Page<FlowHisTaskVo> page = flwTaskMapper.getListFinishTask(pageQuery.build(), queryWrapper);
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏌ヨ寰呭姙浠诲姟
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ */
+ @Override
+ public TableDataInfo<FlowTaskVo> pageByAllTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
+ QueryWrapper<FlowTaskBo> queryWrapper = buildQueryWrapper(flowTaskBo);
+ queryWrapper.eq("t.node_type", NodeType.BETWEEN.getKey());
+ Page<FlowTaskVo> page = flwTaskMapper.getListRunTask(pageQuery.build(), queryWrapper);
+ this.wrapAssigneeInfo(page.getRecords());
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 涓烘祦绋嬩换鍔″垪琛ㄥ皝瑁呭鐞嗕汉 ID锛坅ssigneeIds锛�
+ *
+ * @param taskList 娴佺▼浠诲姟鍒楄〃
+ */
+ private void wrapAssigneeInfo(List<FlowTaskVo> taskList) {
+ if (CollUtil.isEmpty(taskList)) {
+ return;
+ }
+ List<User> associatedUsers = FlowEngine.userService().getByAssociateds(StreamUtils.toList(taskList, FlowTaskVo::getId));
+ Map<Long, List<User>> taskUserMap = StreamUtils.groupByKey(associatedUsers, User::getAssociated);
+ // 缁勮鐢ㄦ埛鏁版嵁鍥炰换鍔″垪琛�
+ for (FlowTaskVo task : taskList) {
+ List<User> users = taskUserMap.get(task.getId());
+ task.setAssigneeIds(StreamUtils.join(users, User::getProcessedBy));
+ }
+ }
+
+ /**
+ * 鏌ヨ宸插姙浠诲姟
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ */
+ @Override
+ public TableDataInfo<FlowHisTaskVo> pageByAllTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
+ QueryWrapper<FlowTaskBo> queryWrapper = buildQueryWrapper(flowTaskBo);
+ Page<FlowHisTaskVo> page = flwTaskMapper.getListFinishTask(pageQuery.build(), queryWrapper);
+ return TableDataInfo.build(page);
+ }
+
+ /**
+ * 鏌ヨ褰撳墠鐢ㄦ埛鐨勬妱閫�
+ *
+ * @param flowTaskBo 鍙傛暟
+ * @param pageQuery 鍒嗛〉
+ */
+ @Override
+ public TableDataInfo<FlowTaskVo> pageByTaskCopy(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
+ QueryWrapper<FlowTaskBo> queryWrapper = buildQueryWrapper(flowTaskBo);
+ queryWrapper.in("t.processed_by", LoginHelper.getUserIdStr());
+ Page<FlowTaskVo> page = flwTaskMapper.getTaskCopyByPage(pageQuery.build(), queryWrapper);
+ return TableDataInfo.build(page);
+ }
+
+ private QueryWrapper<FlowTaskBo> buildQueryWrapper(FlowTaskBo flowTaskBo) {
+ Map<String, Object> params = flowTaskBo.getParams();
+ QueryWrapper<FlowTaskBo> wrapper = Wrappers.query();
+ wrapper.like(StringUtils.isNotBlank(flowTaskBo.getNodeName()), "t.node_name", flowTaskBo.getNodeName());
+ wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowName()), "t.flow_name", flowTaskBo.getFlowName());
+ wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowCode()), "t.flow_code", flowTaskBo.getFlowCode());
+ wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowStatus()), "t.flow_status", flowTaskBo.getFlowStatus());
+ wrapper.in(CollUtil.isNotEmpty(flowTaskBo.getCreateByIds()), "t.create_by", flowTaskBo.getCreateByIds());
+ if (StringUtils.isNotBlank(flowTaskBo.getCategory())) {
+ List<Long> categoryIds = flwCategoryMapper.selectCategoryIdsByParentId(Convert.toLong(flowTaskBo.getCategory()));
+ wrapper.in("t.category", StreamUtils.toList(categoryIds, Convert::toStr));
+ }
+ wrapper.between(params.get("beginTime") != null && params.get("endTime") != null,
+ "t.create_time", params.get("beginTime"), params.get("endTime"));
+ wrapper.orderByDesc("t.create_time").orderByDesc("t.update_time");
+ return wrapper;
+ }
+
+ /**
+ * 椹冲洖浠诲姟
+ *
+ * @param bo 鍙傛暟
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean backProcess(BackProcessBo bo) {
+ Long taskId = bo.getTaskId();
+ String notice = bo.getNotice();
+ List<String> messageType = bo.getMessageType();
+ String message = bo.getMessage();
+ FlowTask task = flowTaskMapper.selectById(taskId);
+ if (ObjectUtil.isNull(task)) {
+ throw new ServiceException("浠诲姟涓嶅瓨鍦紒");
+ }
+ Instance inst = insService.getById(task.getInstanceId());
+ BusinessStatusEnum.checkBackStatus(inst.getFlowStatus());
+ Long definitionId = task.getDefinitionId();
+ String applyNodeCode = flwCommonService.applyNodeCode(definitionId);
+
+ Map<String, Object> variable = new HashMap<>();
+ // 娑堟伅绫诲瀷
+ variable.put(FlowConstant.MESSAGE_TYPE, messageType);
+ // 娑堟伅閫氱煡
+ variable.put(FlowConstant.MESSAGE_NOTICE, notice);
+
+ FlowParams flowParams = FlowParams.build()
+ .nodeCode(bo.getNodeCode())
+ .variable(variable)
+ .message(message)
+ .skipType(SkipType.REJECT.getKey())
+ .flowStatus(applyNodeCode.equals(bo.getNodeCode()) ? TaskStatusEnum.BACK.getStatus() : TaskStatusEnum.WAITING.getStatus())
+ .hisStatus(TaskStatusEnum.BACK.getStatus())
+ .hisTaskExt(bo.getFileId());
+ taskService.skip(task.getId(), flowParams);
+ return true;
+ }
+
+ /**
+ * 鑾峰彇鍙┏鍥炵殑鍓嶇疆鑺傜偣
+ *
+ * @param taskId 浠诲姟id
+ * @param nowNodeCode 褰撳墠鑺傜偣
+ */
+ @Override
+ public List<Node> getBackTaskNode(Long taskId, String nowNodeCode) {
+ FlowTask task = flowTaskMapper.selectById(taskId);
+ List<Node> nodeCodes = nodeService.getByNodeCodes(Collections.singletonList(nowNodeCode), task.getDefinitionId());
+ if (!CollUtil.isNotEmpty(nodeCodes)) {
+ return nodeCodes;
+ }
+ List<User> userList = FlowEngine.userService()
+ .getByAssociateds(Collections.singletonList(task.getId()), UserType.DEPUTE.getKey());
+ if (CollUtil.isNotEmpty(userList)) {
+ return nodeCodes;
+ }
+ //鍒ゆ柇鏄惁閰嶇疆浜嗗浐瀹氶┏鍥炶妭鐐�
+ Node node = nodeCodes.get(0);
+ if (StringUtils.isNotBlank(node.getAnyNodeSkip())) {
+ return nodeService.getByNodeCodes(Collections.singletonList(node.getAnyNodeSkip()), task.getDefinitionId());
+ }
+ //鑾峰彇鍙┏鍥炵殑鍓嶇疆鑺傜偣
+ List<Node> nodes = nodeService.previousNodeList(task.getDefinitionId(), nowNodeCode);
+ List<HisTask> hisTaskList = hisTaskService.getByInsId(task.getInstanceId());
+
+ Map<String, Node> nodeMap = StreamUtils.toIdentityMap(nodes, Node::getNodeCode);
+ Set<String> added = new HashSet<>();
+ List<Node> backNodeList = new ArrayList<>();
+ for (HisTask hisTask : hisTaskList) {
+ Node nodeValue = nodeMap.get(hisTask.getNodeCode());
+ if (nodeValue != null
+ && NodeType.BETWEEN.getKey().equals(nodeValue.getNodeType())
+ && added.add(nodeValue.getNodeCode())) {
+ backNodeList.add(nodeValue);
+ }
+ }
+ if (CollUtil.isNotEmpty(backNodeList)) {
+ Collections.reverse(backNodeList);
+ return backNodeList;
+ }
+ return nodes;
+ }
+
+ /**
+ * 缁堟浠诲姟
+ *
+ * @param bo 鍙傛暟
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean terminationTask(FlowTerminationBo bo) {
+ Long taskId = bo.getTaskId();
+ Task task = taskService.getById(taskId);
+ if (task == null) {
+ throw new ServiceException("浠诲姟涓嶅瓨鍦紒");
+ }
+ Instance instance = insService.getById(task.getInstanceId());
+ if (ObjectUtil.isNotNull(instance)) {
+ BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
+ }
+ FlowParams flowParams = FlowParams.build()
+ .message(bo.getComment())
+ .flowStatus(BusinessStatusEnum.TERMINATION.getStatus())
+ .hisStatus(TaskStatusEnum.TERMINATION.getStatus());
+ taskService.termination(taskId, flowParams);
+ return true;
+ }
+
+ /**
+ * 鎸夌収浠诲姟id鏌ヨ浠诲姟
+ *
+ * @param taskIdList 浠诲姟id
+ */
+ @Override
+ public List<FlowTask> selectByIdList(List<Long> taskIdList) {
+ return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class).in(FlowTask::getId, taskIdList));
+ }
+
+ /**
+ * 鎸夌収浠诲姟id鏌ヨ浠诲姟
+ *
+ * @param taskId 浠诲姟id
+ */
+ @Override
+ public FlowTaskVo selectById(Long taskId) {
+ Task task = taskService.getById(taskId);
+ if (ObjectUtil.isNull(task)) {
+ return null;
+ }
+ FlowTaskVo flowTaskVo = BeanUtil.toBean(task, FlowTaskVo.class);
+ Instance instance = insService.getById(task.getInstanceId());
+ Definition definition = defService.getById(task.getDefinitionId());
+ flowTaskVo.setFlowStatus(instance.getFlowStatus());
+ flowTaskVo.setVersion(definition.getVersion());
+ flowTaskVo.setFlowCode(definition.getFlowCode());
+ flowTaskVo.setFlowName(definition.getFlowName());
+ flowTaskVo.setBusinessId(instance.getBusinessId());
+ FlowNode flowNode = this.getByNodeCode(flowTaskVo.getNodeCode(), instance.getDefinitionId());
+ if (ObjectUtil.isNull(flowNode)) {
+ throw new NullPointerException("褰撳墠銆�" + flowTaskVo.getNodeCode() + "銆戣妭鐐圭紪鐮佷笉瀛樺湪");
+ }
+ NodeExtVo nodeExtVo = flwNodeExtService.parseNodeExt(flowNode.getExt(), instance.getVariableMap());
+ //璁剧疆鎸夐挳鏉冮檺
+ if (CollUtil.isNotEmpty(nodeExtVo.getButtonPermissions())) {
+ flowTaskVo.setButtonList(nodeExtVo.getButtonPermissions());
+ } else {
+ flowTaskVo.setButtonList(new ArrayList<>());
+ }
+ if (CollUtil.isNotEmpty(nodeExtVo.getCopySettings())) {
+ List<FlowCopyVo> list = StreamUtils.toList(nodeExtVo.getCopySettings(), x -> new FlowCopyVo(Convert.toLong(x)));
+ flowTaskVo.setCopyList(list);
+ } else {
+ flowTaskVo.setCopyList(new ArrayList<>());
+ }
+ if (CollUtil.isNotEmpty(nodeExtVo.getVariables())) {
+ flowTaskVo.setVarList(nodeExtVo.getVariables());
+ } else {
+ flowTaskVo.setVarList(new HashMap<>());
+ }
+ flowTaskVo.setNodeRatio(flowNode.getNodeRatio());
+ flowTaskVo.setApplyNode(flowNode.getNodeCode().equals(flwCommonService.applyNodeCode(task.getDefinitionId())));
+ return flowTaskVo;
+ }
+
+ /**
+ * 鑾峰彇涓嬩竴鑺傜偣淇℃伅
+ *
+ * @param bo 鍙傛暟
+ */
+ @Override
+ public List<FlowNode> getNextNodeList(FlowNextNodeBo bo) {
+ Long taskId = bo.getTaskId();
+ Map<String, Object> variables = bo.getVariables();
+ Task task = taskService.getById(taskId);
+ Instance instance = insService.getById(task.getInstanceId());
+ Definition definition = defService.getById(task.getDefinitionId());
+ Map<String, Object> mergeVariable = MapUtil.mergeAll(instance.getVariableMap(), variables);
+ // 鑾峰彇涓嬩竴鑺傜偣鍒楄〃
+ List<Node> nextNodeList = nodeService.getNextNodeList(task.getDefinitionId(), task.getNodeCode(), null, SkipType.PASS.getKey(), mergeVariable);
+ List<FlowNode> nextFlowNodes = BeanUtil.copyToList(nextNodeList, FlowNode.class);
+ // 鍙幏鍙栦腑闂磋妭鐐�
+ nextFlowNodes = StreamUtils.filter(nextFlowNodes, node -> NodeType.BETWEEN.getKey().equals(node.getNodeType()));
+ if (CollUtil.isNotEmpty(nextNodeList)) {
+ //鏋勫缓浠ヤ笅鑺傜偣鏁版嵁
+ List<Task> buildNextTaskList = StreamUtils.toList(nextNodeList, node -> taskService.addTask(node, instance, definition, FlowParams.build()));
+ //鍔炵悊浜哄彉閲忔浛鎹�
+ ExpressionUtil.evalVariable(buildNextTaskList, FlowParams.build().variable(mergeVariable));
+ for (FlowNode flowNode : nextFlowNodes) {
+ StreamUtils.findFirst(buildNextTaskList, t -> t.getNodeCode().equals(flowNode.getNodeCode()))
+ .ifPresent(first -> {
+ List<UserDTO> users;
+ if (CollUtil.isNotEmpty(first.getPermissionList())
+ && CollUtil.isNotEmpty(users = flwTaskAssigneeService.fetchUsersByStorageIds(StringUtils.joinComma(first.getPermissionList())))) {
+ flowNode.setPermissionFlag(StreamUtils.join(users, e -> Convert.toStr(e.getUserId())));
+ }
+ });
+ }
+ }
+ return nextFlowNodes;
+ }
+
+ /**
+ * 鎸夌収浠诲姟id鏌ヨ浠诲姟
+ *
+ * @param taskId 浠诲姟id
+ * @return 缁撴灉
+ */
+ @Override
+ public FlowHisTask selectHisTaskById(Long taskId) {
+ return flowHisTaskMapper.selectOne(new LambdaQueryWrapper<>(FlowHisTask.class).eq(FlowHisTask::getId, taskId));
+ }
+
+ /**
+ * 鎸夌収瀹炰緥id鏌ヨ浠诲姟
+ *
+ * @param instanceId 娴佺▼瀹炰緥id
+ */
+ @Override
+ public List<FlowTask> selectByInstId(Long instanceId) {
+ return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class).eq(FlowTask::getInstanceId, instanceId));
+ }
+
+ /**
+ * 鎸夌収瀹炰緥id鏌ヨ浠诲姟
+ *
+ * @param instanceIds 娴佺▼瀹炰緥id
+ */
+ @Override
+ public List<FlowTask> selectByInstIds(List<Long> instanceIds) {
+ return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class).in(FlowTask::getInstanceId, instanceIds));
+ }
+
+ /**
+ * 鍒ゆ柇娴佺▼鏄惁宸茬粨鏉燂紙鍗宠娴佺▼瀹炰緥涓嬫槸鍚﹁繕鏈夋湭瀹屾垚鐨勪换鍔★級
+ *
+ * @param instanceId 娴佺▼瀹炰緥ID
+ * @return true 琛ㄧず浠诲姟宸插叏閮ㄧ粨鏉燂紱false 琛ㄧず浠嶆湁浠诲姟瀛樺湪
+ */
+ @Override
+ public boolean isTaskEnd(Long instanceId) {
+ boolean exists = flowTaskMapper.exists(new LambdaQueryWrapper<FlowTask>().eq(FlowTask::getInstanceId, instanceId));
+ return !exists;
+ }
+
+ /**
+ * 浠诲姟鎿嶄綔
+ *
+ * @param bo 鍙傛暟
+ * @param taskOperation 鎿嶄綔绫诲瀷锛屽娲� delegateTask銆佽浆鍔� transferTask銆佸姞绛� addSignature銆佸噺绛� reductionSignature
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean taskOperation(TaskOperationBo bo, String taskOperation) {
+ FlowParams flowParams = FlowParams.build().message(bo.getMessage());
+ if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
+ flowParams.ignore(true);
+ }
+
+ // 鏍规嵁鎿嶄綔绫诲瀷鏋勫缓 FlowParams
+ switch (taskOperation) {
+ case DELEGATE_TASK, TRANSFER_TASK -> {
+ ValidatorUtils.validate(bo, AddGroup.class);
+ flowParams.addHandlers(Collections.singletonList(bo.getUserId()));
+ }
+ case ADD_SIGNATURE -> {
+ ValidatorUtils.validate(bo, EditGroup.class);
+ flowParams.addHandlers(bo.getUserIds());
+ }
+ case REDUCTION_SIGNATURE -> {
+ ValidatorUtils.validate(bo, EditGroup.class);
+ flowParams.reductionHandlers(bo.getUserIds());
+ }
+ default -> {
+ log.error("Invalid operation type:{} ", taskOperation);
+ throw new ServiceException("Invalid operation type " + taskOperation);
+ }
+ }
+
+ Long taskId = bo.getTaskId();
+ Task task = taskService.getById(taskId);
+ FlowNode flowNode = getByNodeCode(task.getNodeCode(), task.getDefinitionId());
+ if (ADD_SIGNATURE.equals(taskOperation) || REDUCTION_SIGNATURE.equals(taskOperation)) {
+ if (CooperateType.isOrSign(flowNode.getNodeRatio())) {
+ throw new ServiceException(task.getNodeName() + "涓嶆槸浼氱鎴栫エ绛捐妭鐐癸紒");
+ }
+ }
+ // 璁剧疆浠诲姟鐘舵�佸苟鎵ц瀵瑰簲鐨勪换鍔℃搷浣�
+ switch (taskOperation) {
+ //濮旀淳浠诲姟
+ case DELEGATE_TASK -> {
+ flowParams.hisStatus(TaskStatusEnum.DEPUTE.getStatus());
+ return taskService.depute(taskId, flowParams);
+ }
+ //杞姙浠诲姟
+ case TRANSFER_TASK -> {
+ flowParams.hisStatus(TaskStatusEnum.TRANSFER.getStatus());
+ return taskService.transfer(taskId, flowParams);
+ }
+ //鍔犵锛屽鍔犲姙鐞嗕汉
+ case ADD_SIGNATURE -> {
+ flowParams.hisStatus(TaskStatusEnum.SIGN.getStatus());
+ return taskService.addSignature(taskId, flowParams);
+ }
+ //鍑忕锛屽噺灏戝姙鐞嗕汉
+ case REDUCTION_SIGNATURE -> {
+ flowParams.hisStatus(TaskStatusEnum.SIGN_OFF.getStatus());
+ return taskService.reductionSignature(taskId, flowParams);
+ }
+ default -> {
+ log.error("Invalid operation type:{} ", taskOperation);
+ throw new ServiceException("Invalid operation type " + taskOperation);
+ }
+ }
+ }
+
+ /**
+ * 淇敼浠诲姟鍔炵悊浜猴紙姝ゆ柟娉曞皢浼氭壒閲忎慨鏀规墍鏈変换鍔$殑鍔炵悊浜猴級
+ *
+ * @param taskIdList 浠诲姟id
+ * @param userId 鐢ㄦ埛id
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean updateAssignee(List<Long> taskIdList, String userId) {
+ if (CollUtil.isEmpty(taskIdList)) {
+ return false;
+ }
+ List<FlowTask> flowTasks = this.selectByIdList(taskIdList);
+ // 鎵归噺鍒犻櫎鐜版湁浠诲姟鐨勫姙鐞嗕汉璁板綍
+ if (CollUtil.isNotEmpty(flowTasks)) {
+ FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTasks, FlowTask::getId));
+ List<User> userList = StreamUtils.toList(flowTasks, flowTask ->
+ new FlowUser()
+ .setType(TaskAssigneeType.APPROVER.getCode())
+ .setProcessedBy(userId)
+ .setAssociated(flowTask.getId()));
+ if (CollUtil.isNotEmpty(userList)) {
+ FlowEngine.userService().saveBatch(userList);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 鑾峰彇褰撳墠浠诲姟鐨勬墍鏈夊姙鐞嗕汉
+ *
+ * @param taskIds 浠诲姟id
+ */
+ @Override
+ public List<UserDTO> currentTaskAllUser(List<Long> taskIds) {
+ // 鑾峰彇涓庡綋鍓嶄换鍔″叧鑱旂殑鐢ㄦ埛鍒楄〃
+ List<User> userList = FlowEngine.userService().getByAssociateds(taskIds);
+ if (CollUtil.isEmpty(userList)) {
+ return Collections.emptyList();
+ }
+ return userService.selectListByIds(StreamUtils.toList(userList, e -> Convert.toLong(e.getProcessedBy())));
+ }
+
+ /**
+ * 鎸夌収鑺傜偣缂栫爜鏌ヨ鑺傜偣
+ *
+ * @param nodeCode 鑺傜偣缂栫爜
+ * @param definitionId 娴佺▼瀹氫箟id
+ */
+ @Override
+ public FlowNode getByNodeCode(String nodeCode, Long definitionId) {
+ return flowNodeMapper.selectOne(new LambdaQueryWrapper<FlowNode>()
+ .eq(FlowNode::getNodeCode, nodeCode)
+ .eq(FlowNode::getDefinitionId, definitionId));
+ }
+
+ /**
+ * 鍌姙浠诲姟
+ *
+ * @param bo 鍙傛暟
+ */
+ @Override
+ public boolean urgeTask(FlowUrgeTaskBo bo) {
+ if (CollUtil.isEmpty(bo.getTaskIdList())) {
+ return false;
+ }
+ List<UserDTO> userList = this.currentTaskAllUser(bo.getTaskIdList());
+ if (CollUtil.isEmpty(userList)) {
+ return false;
+ }
+ List<String> messageType = bo.getMessageType();
+ String message = bo.getMessage();
+ flwCommonService.sendMessage(messageType, message, "鍗曟嵁瀹℃壒鎻愰啋", userList);
+ return true;
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java
new file mode 100755
index 0000000..ec0be3c
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java
@@ -0,0 +1,242 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.dto.StartProcessDTO;
+import org.dromara.common.core.domain.event.ProcessDeleteEvent;
+import org.dromara.common.core.domain.event.ProcessEvent;
+import org.dromara.common.core.domain.event.ProcessTaskEvent;
+import org.dromara.common.core.enums.BusinessStatusEnum;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.WorkflowService;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.domain.TestLeave;
+import org.dromara.workflow.domain.bo.TestLeaveBo;
+import org.dromara.workflow.domain.vo.TestLeaveVo;
+import org.dromara.workflow.mapper.TestLeaveMapper;
+import org.dromara.workflow.service.ITestLeaveService;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 璇峰亣Service涓氬姟灞傚鐞�
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class TestLeaveServiceImpl implements ITestLeaveService {
+
+ private final TestLeaveMapper baseMapper;
+ private final WorkflowService workflowService;
+
+ /**
+ * spel鏉′欢琛ㄨ揪锛氬垽鏂皬浜�2
+ *
+ * @param leaveDays 寰呭垽鏂殑鍙橀噺锛堝彲涓嶄紶鑷杩斿洖true鎴杅alse锛�
+ * @return boolean
+ */
+ public boolean eval(Integer leaveDays) {
+ if (leaveDays <= 2) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 鏌ヨ璇峰亣
+ */
+ @Override
+ public TestLeaveVo queryById(Long id) {
+ return baseMapper.selectVoById(id);
+ }
+
+ /**
+ * 鏌ヨ璇峰亣鍒楄〃
+ */
+ @Override
+ public TableDataInfo<TestLeaveVo> queryPageList(TestLeaveBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper<TestLeave> lqw = buildQueryWrapper(bo);
+ Page<TestLeaveVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 鏌ヨ璇峰亣鍒楄〃
+ */
+ @Override
+ public List<TestLeaveVo> queryList(TestLeaveBo bo) {
+ LambdaQueryWrapper<TestLeave> lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper<TestLeave> buildQueryWrapper(TestLeaveBo bo) {
+ LambdaQueryWrapper<TestLeave> lqw = Wrappers.lambdaQuery();
+ lqw.eq(StringUtils.isNotBlank(bo.getLeaveType()), TestLeave::getLeaveType, bo.getLeaveType());
+ lqw.ge(bo.getStartLeaveDays() != null, TestLeave::getLeaveDays, bo.getStartLeaveDays());
+ lqw.le(bo.getEndLeaveDays() != null, TestLeave::getLeaveDays, bo.getEndLeaveDays());
+ lqw.orderByDesc(BaseEntity::getCreateTime);
+ return lqw;
+ }
+
+ /**
+ * 鏂板璇峰亣
+ */
+ @Override
+ public TestLeaveVo insertByBo(TestLeaveBo bo) {
+ long day = DateUtil.betweenDay(bo.getStartDate(), bo.getEndDate(), true);
+ // 鎴鏃ユ湡涔熺畻涓�澶�
+ bo.setLeaveDays((int) day + 1);
+ bo.setApplyCode(System.currentTimeMillis() + StrUtil.EMPTY);
+ TestLeave add = MapstructUtils.convert(bo, TestLeave.class);
+ if (StringUtils.isBlank(add.getStatus())) {
+ add.setStatus(BusinessStatusEnum.DRAFT.getStatus());
+ }
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setId(add.getId());
+ }
+ return MapstructUtils.convert(add, TestLeaveVo.class);
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public TestLeaveVo submitAndFlowStart(TestLeaveBo bo) {
+ long day = DateUtil.betweenDay(bo.getStartDate(), bo.getEndDate(), true);
+ // 鎴鏃ユ湡涔熺畻涓�澶�
+ bo.setLeaveDays((int) day + 1);
+ if (ObjectUtil.isNull(bo.getId())) {
+ bo.setApplyCode(System.currentTimeMillis() + StrUtil.EMPTY);
+ }
+ TestLeave leave = MapstructUtils.convert(bo, TestLeave.class);
+ boolean flag = baseMapper.insertOrUpdate(leave);
+ if (flag) {
+ bo.setId(leave.getId());
+ // 鍚庣鍙戣捣闇�瑕佸拷鐣ユ潈闄�
+ bo.getParams().put("ignore", true);
+
+ StartProcessDTO startProcess = new StartProcessDTO();
+ startProcess.setBusinessId(leave.getId().toString());
+ startProcess.setFlowCode(StringUtils.isEmpty(bo.getFlowCode()) ? "leave1" : bo.getFlowCode());
+ startProcess.setVariables(bo.getParams());
+ // 鍚庣鍙戣捣 濡傛灉娌℃湁鐧诲綍鐢ㄦ埛 姣斿瀹氭椂浠诲姟 鍙互鎵嬪姩璁剧疆涓�涓鐞嗕汉id
+ // startProcess.setHandler("0");
+
+ boolean flag1 = workflowService.startCompleteTask(startProcess);
+ if (!flag1) {
+ throw new ServiceException("娴佺▼鍙戣捣寮傚父");
+ }
+ }
+ return MapstructUtils.convert(leave, TestLeaveVo.class);
+ }
+
+ /**
+ * 淇敼璇峰亣
+ */
+ @Override
+ public TestLeaveVo updateByBo(TestLeaveBo bo) {
+ TestLeave update = MapstructUtils.convert(bo, TestLeave.class);
+ baseMapper.updateById(update);
+ return MapstructUtils.convert(update, TestLeaveVo.class);
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎璇峰亣
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean deleteWithValidByIds(List<Long> ids) {
+ workflowService.deleteInstance(StreamUtils.toList(ids, Convert::toStr));
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+
+ /**
+ * 鎬讳綋娴佺▼鐩戝惉(渚嬪: 鑽夌锛屾挙閿�锛岄��鍥烇紝浣滃簾锛岀粓姝紝宸插畬鎴愮瓑)
+ * 姝e父浣跨敤鍙渶#processEvent.flowCode=='leave1'
+ * 绀轰緥涓轰簡鏂逛究鍒欎娇鐢╯tartsWith鍖归厤浜嗗叏閮ㄧず渚媖ey
+ *
+ * @param processEvent 鍙傛暟
+ */
+ @EventListener(condition = "#processEvent.flowCode.startsWith('leave')")
+ public void processHandler(ProcessEvent processEvent) {
+ log.info("褰撳墠浠诲姟鎵ц浜唟}", processEvent.toString());
+ TestLeave testLeave = baseMapper.selectById(Convert.toLong(processEvent.getBusinessId()));
+ testLeave.setStatus(processEvent.getStatus());
+ // 鐢ㄤ簬渚嬪瀹℃壒闄勪欢 瀹℃壒鎰忚绛� 瀛樺偍鍒颁笟鍔¤〃鍐� 鑷鏍规嵁涓氬姟瀹炵幇瀛樺偍娴佺▼
+ Map<String, Object> params = processEvent.getParams();
+ if (MapUtil.isNotEmpty(params)) {
+ // 鍘嗗彶浠诲姟鎵╁睍(閫氬父涓洪檮浠�)
+ String hisTaskExt = Convert.toStr(params.get("hisTaskExt"));
+ // 鍔炵悊浜�
+ String handler = Convert.toStr(params.get("handler"));
+ // 鍔炵悊鎰忚
+ String message = Convert.toStr(params.get("message"));
+ }
+ if (processEvent.getSubmit()) {
+ if (StringUtils.isBlank(testLeave.getApplyCode())) {
+ String businessCode = MapUtil.getStr(params, FlowConstant.BUSINESS_CODE, StrUtil.EMPTY);
+ testLeave.setApplyCode(businessCode);
+ }
+ testLeave.setStatus(BusinessStatusEnum.WAITING.getStatus());
+ log.info("鐢宠浜烘彁浜�");
+ }
+ String status = BusinessStatusEnum.findByStatus(processEvent.getStatus());
+ log.info("褰撳墠娴佺▼鐘舵�佷负{}", status);
+ baseMapper.updateById(testLeave);
+ }
+
+ /**
+ * 鎵ц浠诲姟鍒涘缓鐩戝惉(涔熶唬琛ㄤ笂涓�鏉′换鍔″畬鎴愪簨浠�)
+ * 绀轰緥锛氫篃鍙�氳繃 @EventListener(condition = "#processTaskEvent.flowCode=='leave1'")杩涜鍒ゆ柇
+ * 鍦ㄦ柟娉曚腑鍒ゆ柇娴佺▼鑺傜偣key
+ * if ("xxx".equals(processTaskEvent.getNodeCode())) {
+ * //鎵ц涓氬姟閫昏緫
+ * }
+ *
+ * @param processTaskEvent 鍙傛暟
+ */
+ @EventListener(condition = "#processTaskEvent.flowCode.startsWith('leave')")
+ public void processTaskHandler(ProcessTaskEvent processTaskEvent) {
+ log.info("褰撳墠浠诲姟鍒涘缓浜唟}", processTaskEvent.toString());
+ }
+
+ /**
+ * 鐩戝惉鍒犻櫎娴佺▼浜嬩欢
+ * 姝e父浣跨敤鍙渶#processDeleteEvent.flowCode=='leave1'
+ * 绀轰緥涓轰簡鏂逛究鍒欎娇鐢╯tartsWith鍖归厤浜嗗叏閮ㄧず渚媖ey
+ *
+ * @param processDeleteEvent 鍙傛暟
+ */
+ @EventListener(condition = "#processDeleteEvent.flowCode.startsWith('leave')")
+ public void processDeleteHandler(ProcessDeleteEvent processDeleteEvent) {
+ log.info("鐩戝惉鍒犻櫎娴佺▼浜嬩欢锛屽綋鍓嶄换鍔℃墽琛屼簡{}", processDeleteEvent.toString());
+ TestLeave testLeave = baseMapper.selectById(Convert.toLong(processDeleteEvent.getBusinessId()));
+ if (ObjectUtil.isNull(testLeave)) {
+ return;
+ }
+ baseMapper.deleteById(testLeave.getId());
+ }
+
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java
new file mode 100755
index 0000000..8d6b8d2
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java
@@ -0,0 +1,178 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.dto.CompleteTaskDTO;
+import org.dromara.common.core.domain.dto.StartProcessDTO;
+import org.dromara.common.core.domain.dto.StartProcessReturnDTO;
+import org.dromara.common.core.service.WorkflowService;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.warm.flow.orm.entity.FlowInstance;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.enums.MessageTypeEnum;
+import org.dromara.workflow.domain.FlowInstanceBizExt;
+import org.dromara.workflow.domain.bo.CompleteTaskBo;
+import org.dromara.workflow.domain.bo.StartProcessBo;
+import org.dromara.workflow.service.IFlwDefinitionService;
+import org.dromara.workflow.service.IFlwInstanceService;
+import org.dromara.workflow.service.IFlwTaskService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 閫氱敤 宸ヤ綔娴佹湇鍔″疄鐜�
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@Service
+public class WorkflowServiceImpl implements WorkflowService {
+
+ private final IFlwInstanceService flwInstanceService;
+ private final IFlwDefinitionService flwDefinitionService;
+ private final IFlwTaskService flwTaskService;
+
+ /**
+ * 鍒犻櫎娴佺▼瀹炰緥
+ *
+ * @param businessIds 涓氬姟id
+ * @return 缁撴灉
+ */
+ @Override
+ public boolean deleteInstance(List<String> businessIds) {
+ return flwInstanceService.deleteByBusinessIds(businessIds);
+ }
+
+ /**
+ * 鑾峰彇褰撳墠娴佺▼鐘舵��
+ *
+ * @param taskId 浠诲姟id
+ */
+ @Override
+ public String getBusinessStatusByTaskId(Long taskId) {
+ FlowInstance flowInstance = flwInstanceService.selectByTaskId(taskId);
+ return ObjectUtil.isNotNull(flowInstance) ? flowInstance.getFlowStatus() : StringUtils.EMPTY;
+ }
+
+ /**
+ * 鑾峰彇褰撳墠娴佺▼鐘舵��
+ *
+ * @param businessId 涓氬姟id
+ */
+ @Override
+ public String getBusinessStatus(String businessId) {
+ FlowInstance flowInstance = flwInstanceService.selectInstByBusinessId(businessId);
+ return ObjectUtil.isNotNull(flowInstance) ? flowInstance.getFlowStatus() : StringUtils.EMPTY;
+ }
+
+ /**
+ * 璁剧疆娴佺▼鍙橀噺
+ *
+ * @param instanceId 娴佺▼瀹炰緥id
+ * @param variables 娴佺▼鍙橀噺
+ */
+ @Override
+ public void setVariable(Long instanceId, Map<String, Object> variables) {
+ flwInstanceService.setVariable(instanceId, variables);
+ }
+
+ /**
+ * 鑾峰彇娴佺▼鍙橀噺
+ *
+ * @param instanceId 娴佺▼瀹炰緥id
+ */
+ @Override
+ public Map<String, Object> instanceVariable(Long instanceId) {
+ return flwInstanceService.instanceVariable(instanceId);
+ }
+
+ /**
+ * 鎸夌収涓氬姟id鏌ヨ娴佺▼瀹炰緥id
+ *
+ * @param businessId 涓氬姟id
+ * @return 缁撴灉
+ */
+ @Override
+ public Long getInstanceIdByBusinessId(String businessId) {
+ FlowInstance flowInstance = flwInstanceService.selectInstByBusinessId(businessId);
+ return ObjectUtil.isNotNull(flowInstance) ? flowInstance.getId() : null;
+ }
+
+ /**
+ * 鏂板绉熸埛娴佺▼瀹氫箟
+ *
+ * @param tenantId 绉熸埛id
+ */
+ @Override
+ public void syncDef(String tenantId) {
+ flwDefinitionService.syncDef(tenantId);
+ }
+
+ /**
+ * 鍚姩娴佺▼
+ *
+ * @param startProcess 鍙傛暟
+ */
+ @Override
+ public StartProcessReturnDTO startWorkFlow(StartProcessDTO startProcess) {
+ return flwTaskService.startWorkFlow(BeanUtil.toBean(startProcess, StartProcessBo.class));
+ }
+
+ /**
+ * 鍔炵悊浠诲姟
+ * 绯荤粺鍚庡彴鍙戣捣瀹℃壒 鏃犵敤鎴蜂俊鎭� 闇�瑕佸拷鐣ユ潈闄�
+ * completeTask.getVariables().put("ignore", true);
+ *
+ * @param completeTask 鍙傛暟
+ */
+ @Override
+ public boolean completeTask(CompleteTaskDTO completeTask) {
+ return flwTaskService.completeTask(BeanUtil.toBean(completeTask, CompleteTaskBo.class));
+ }
+
+ /**
+ * 鍔炵悊浠诲姟
+ *
+ * @param taskId 浠诲姟ID
+ * @param message 鍔炵悊鎰忚
+ */
+ @Override
+ public boolean completeTask(Long taskId, String message) {
+ CompleteTaskBo completeTask = new CompleteTaskBo();
+ completeTask.setTaskId(taskId);
+ completeTask.setMessage(message);
+ // 蹇界暐鏉冮檺(绯荤粺鍚庡彴鍙戣捣瀹℃壒 鏃犵敤鎴蜂俊鎭� 闇�瑕佸拷鐣ユ潈闄�)
+ completeTask.getVariables().put("ignore", true);
+ return flwTaskService.completeTask(completeTask);
+ }
+
+ /**
+ * 鍚姩娴佺▼骞跺姙鐞嗙涓�涓换鍔�
+ *
+ * @param startProcess 鍙傛暟
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean startCompleteTask(StartProcessDTO startProcess) {
+ StartProcessBo processBo = new StartProcessBo();
+ processBo.setBusinessId(startProcess.getBusinessId());
+ processBo.setFlowCode(startProcess.getFlowCode());
+ processBo.setVariables(startProcess.getVariables());
+ processBo.setHandler(startProcess.getHandler());
+ processBo.setBizExt(BeanUtil.toBean(startProcess.getBizExt(), FlowInstanceBizExt.class));
+
+ StartProcessReturnDTO result = flwTaskService.startWorkFlow(processBo);
+ CompleteTaskBo taskBo = new CompleteTaskBo();
+ taskBo.setTaskId(result.getTaskId());
+ taskBo.setMessageType(Collections.singletonList(MessageTypeEnum.SYSTEM_MESSAGE.getCode()));
+ taskBo.setVariables(startProcess.getVariables());
+ taskBo.setHandler(startProcess.getHandler());
+ return flwTaskService.completeTask(taskBo);
+ }
+}
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/package-info.md b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/package-info.md
new file mode 100755
index 0000000..c938b1e
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/package-info.md
@@ -0,0 +1,3 @@
+java鍖呬娇鐢� `.` 鍒嗗壊 resource 鐩綍浣跨敤 `/` 鍒嗗壊
+<br>
+姝ゆ枃浠剁洰鐨� 闃叉鏂囦欢澶圭矘杩炴壘涓嶅埌 `xml` 鏂囦欢
\ No newline at end of file
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwCategoryMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwCategoryMapper.xml
new file mode 100755
index 0000000..10c948d
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwCategoryMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.workflow.mapper.FlwCategoryMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceBizExtMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceBizExtMapper.xml
new file mode 100755
index 0000000..c2cc9c7
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceBizExtMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.workflow.mapper.FlwInstanceBizExtMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceMapper.xml
new file mode 100755
index 0000000..0ae7adb
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceMapper.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.workflow.mapper.FlwInstanceMapper">
+ <resultMap type="org.dromara.workflow.domain.vo.FlowInstanceVo" id="FlowInstanceResult">
+ </resultMap>
+
+ <select id="selectInstanceList" resultMap="FlowInstanceResult">
+ select fi.id,
+ fi.create_time,
+ fi.update_time,
+ fi.tenant_id,
+ fi.del_flag,
+ fi.definition_id,
+ fi.business_id,
+ fi.node_type,
+ fi.node_code,
+ fi.node_name,
+ fi.variable,
+ fi.flow_status,
+ fi.activity_status,
+ fi.create_by,
+ fi.ext,
+ fd.flow_name,
+ fd.flow_code,
+ fd.version,
+ fd.form_custom,
+ fd.form_path,
+ fd.category,
+ biz.business_code,
+ biz.business_title
+ from flow_instance fi
+ left join flow_definition fd on fi.definition_id = fd.id
+ left join flow_instance_biz_ext biz on biz.instance_id = fi.id
+ ${ew.getCustomSqlSegment}
+ </select>
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwSpelMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwSpelMapper.xml
new file mode 100755
index 0000000..03355f6
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwSpelMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.workflow.mapper.FlwSpelMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml
new file mode 100755
index 0000000..ead3d07
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.workflow.mapper.FlwTaskMapper">
+ <resultMap type="org.dromara.workflow.domain.vo.FlowTaskVo" id="FlowTaskResult">
+ </resultMap>
+ <resultMap type="org.dromara.workflow.domain.vo.FlowHisTaskVo" id="FlowHisTaskResult">
+ </resultMap>
+
+ <select id="getListRunTask" resultMap="FlowTaskResult">
+ select * from (
+ select distinct
+ t.id,
+ t.node_code,
+ t.node_name,
+ t.node_type,
+ t.definition_id,
+ t.instance_id,
+ t.create_time,
+ t.update_time,
+ t.tenant_id,
+ i.business_id,
+ i.flow_status,
+ i.create_by,
+ d.flow_name,
+ d.flow_code,
+ d.form_custom,
+ d.category,
+ COALESCE(
+ NULLIF(TRIM(t.form_path), ''),
+ NULLIF(TRIM(d.form_path), '')
+ ) AS form_path,
+ d.version,
+ uu.processed_by,
+ uu.type,
+ biz.business_code,
+ biz.business_title
+ from flow_task t
+ left join flow_user uu on uu.associated = t.id
+ left join flow_definition d on t.definition_id = d.id
+ left join flow_instance i on t.instance_id = i.id
+ left join flow_instance_biz_ext biz on biz.instance_id = i.id
+ where t.node_type = 1
+ and t.del_flag = '0'
+ and uu.del_flag = '0'
+ and uu.type in ('1','2','3')
+ ) t
+ ${ew.getCustomSqlSegment}
+ </select>
+
+ <select id="getListFinishTask" resultMap="FlowHisTaskResult">
+ select * from (
+ select
+ a.id,
+ a.node_code,
+ a.node_name,
+ a.cooperate_type,
+ a.approver,
+ a.collaborator,
+ a.node_type,
+ a.target_node_code,
+ a.target_node_name,
+ a.definition_id,
+ a.instance_id,
+ a.flow_status flow_task_status,
+ a.message,
+ a.ext,
+ a.create_time,
+ a.update_time,
+ a.tenant_id,
+ a.form_custom,
+ a.form_path,
+ b.flow_status,
+ b.business_id,
+ b.create_by,
+ c.flow_name,
+ c.flow_code,
+ c.category,
+ c.version,
+ biz.business_code,
+ biz.business_title
+ from flow_his_task a
+ left join flow_instance b on a.instance_id = b.id
+ left join flow_definition c on a.definition_id = c.id
+ left join flow_instance_biz_ext biz on biz.instance_id = b.id
+ where a.del_flag ='0'
+ and b.del_flag = '0'
+ and c.del_flag = '0'
+ and a.node_type in ('1','3','4')
+ ) t
+ ${ew.getCustomSqlSegment}
+ </select>
+
+ <select id="getTaskCopyByPage" resultMap="FlowTaskResult">
+ select * from (
+ select
+ b.id,
+ b.update_time,
+ c.business_id,
+ c.flow_status,
+ c.create_by,
+ a.processed_by,
+ a.create_time,
+ b.form_custom,
+ b.form_path,
+ b.node_name,
+ b.node_code,
+ d.flow_name,
+ d.flow_code,
+ d.category,
+ d.version,
+ biz.business_code,
+ biz.business_title
+ from flow_user a
+ left join flow_his_task b on a.associated = b.task_id
+ left join flow_instance c on b.instance_id = c.id
+ left join flow_definition d on c.definition_id=d.id
+ left join flow_instance_biz_ext biz on biz.instance_id = c.id
+ where a.type = '4'
+ and a.del_flag = '0'
+ and b.del_flag = '0'
+ and d.del_flag = '0'
+ ) t
+ ${ew.getCustomSqlSegment}
+ </select>
+</mapper>
diff --git a/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/TestLeaveMapper.xml b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/TestLeaveMapper.xml
new file mode 100755
index 0000000..d52f6b0
--- /dev/null
+++ b/RuoYi-Vue-Plus/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/TestLeaveMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.workflow.mapper.TestLeaveMapper">
+
+</mapper>
diff --git a/RuoYi-Vue-Plus/script/bin/ry.bat b/RuoYi-Vue-Plus/script/bin/ry.bat
new file mode 100755
index 0000000..ea98cbe
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/bin/ry.bat
@@ -0,0 +1,68 @@
+rem 浣跨敤鑰呭簲鏍规嵁鑷韩骞冲彴缂栫爜鑷杞崲 闃叉涔辩爜 渚嬪 win浣跨敤gbk缂栫爜
+@echo off
+
+rem jar骞崇骇鐩綍
+set AppName=ruoyi-admin.jar
+
+rem JVM鍙傛暟
+set JVM_OPTS="-Dname=%AppName% -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC"
+
+
+ECHO.
+ ECHO. [1] 鍚姩%AppName%
+ ECHO. [2] 鍏抽棴%AppName%
+ ECHO. [3] 閲嶅惎%AppName%
+ ECHO. [4] 鍚姩鐘舵�� %AppName%
+ ECHO. [5] 閫� 鍑�
+ECHO.
+
+ECHO.璇疯緭鍏ラ�夋嫨椤圭洰鐨勫簭鍙�:
+set /p ID=
+ IF "%id%"=="1" GOTO start
+ IF "%id%"=="2" GOTO stop
+ IF "%id%"=="3" GOTO restart
+ IF "%id%"=="4" GOTO status
+ IF "%id%"=="5" EXIT
+PAUSE
+:start
+ for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
+ set pid=%%a
+ set image_name=%%b
+ )
+ if defined pid (
+ echo %%is running
+ PAUSE
+ )
+
+start javaw %JVM_OPTS% -jar %AppName%
+
+echo starting鈥︹��
+echo Start %AppName% success...
+goto:eof
+
+rem 鍑芥暟stop閫氳繃jps鍛戒护鏌ユ壘pid骞剁粨鏉熻繘绋�
+:stop
+ for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
+ set pid=%%a
+ set image_name=%%b
+ )
+ if not defined pid (echo process %AppName% does not exists) else (
+ echo prepare to kill %image_name%
+ echo start kill %pid% ...
+ rem 鏍规嵁杩涚▼ID锛宬ill杩涚▼
+ taskkill /f /pid %pid%
+ )
+goto:eof
+:restart
+ call :stop
+ call :start
+goto:eof
+:status
+ for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
+ set pid=%%a
+ set image_name=%%b
+ )
+ if not defined pid (echo process %AppName% is dead ) else (
+ echo %image_name% is running
+ )
+goto:eof
diff --git a/RuoYi-Vue-Plus/script/bin/ry.sh b/RuoYi-Vue-Plus/script/bin/ry.sh
new file mode 100755
index 0000000..a6f5d9c
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/bin/ry.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+# ./ry.sh start 鍚姩 stop 鍋滄 restart 閲嶅惎 status 鐘舵��
+AppName=ruoyi-admin.jar
+
+# JVM鍙傛暟
+JVM_OPTS="-Dname=$AppName -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC"
+APP_HOME=`pwd`
+LOG_PATH=$APP_HOME/logs/$AppName.log
+
+if [ "$1" = "" ];
+then
+ echo -e "\033[0;31m 鏈緭鍏ユ搷浣滃悕 \033[0m \033[0;34m {start|stop|restart|status} \033[0m"
+ exit 1
+fi
+
+if [ "$AppName" = "" ];
+then
+ echo -e "\033[0;31m 鏈緭鍏ュ簲鐢ㄥ悕 \033[0m"
+ exit 1
+fi
+
+function start()
+{
+ PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'`
+
+ if [ x"$PID" != x"" ]; then
+ echo "$AppName is running..."
+ else
+ nohup java $JVM_OPTS -jar $AppName > /dev/null 2>&1 &
+ echo "Start $AppName success..."
+ fi
+}
+
+function stop()
+{
+ echo "Stop $AppName"
+
+ PID=""
+ query(){
+ PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'`
+ }
+
+ query
+ if [ x"$PID" != x"" ]; then
+ kill -TERM $PID
+ echo "$AppName (pid:$PID) exiting..."
+ while [ x"$PID" != x"" ]
+ do
+ sleep 1
+ query
+ done
+ echo "$AppName exited."
+ else
+ echo "$AppName already stopped."
+ fi
+}
+
+function restart()
+{
+ stop
+ sleep 2
+ start
+}
+
+function status()
+{
+ PID=`ps -ef |grep java|grep $AppName|grep -v grep|wc -l`
+ if [ $PID != 0 ];then
+ echo "$AppName is running..."
+ else
+ echo "$AppName is not running..."
+ fi
+}
+
+case $1 in
+ start)
+ start;;
+ stop)
+ stop;;
+ restart)
+ restart;;
+ status)
+ status;;
+ *)
+
+esac
diff --git a/RuoYi-Vue-Plus/script/docker/database.yml b/RuoYi-Vue-Plus/script/docker/database.yml
new file mode 100755
index 0000000..6034b39
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/docker/database.yml
@@ -0,0 +1,59 @@
+services:
+ # 姝ら暅鍍忎粎鐢ㄤ簬娴嬭瘯 姝e紡鐜闇�鑷瀹夎鏁版嵁搴�
+ # SID: XE user: system password: oracle
+ oracle:
+ image: tekintian/oracle12c:latest
+ container_name: oracle
+ environment:
+ # 鏃跺尯涓婃捣
+ TZ: Asia/Shanghai
+ DBCA_TOTAL_MEMORY: 16192
+ ports:
+ - "18080:8080"
+ - "1521:1521"
+ volumes:
+ # 鏁版嵁鎸傝浇
+ - "/docker/oracle/data:/u01/app/oracle"
+ network_mode: "host"
+
+ # 姝ら暅鍍忎粎鐢ㄤ簬娴嬭瘯 姝e紡鐜闇�鑷瀹夎鏁版嵁搴�
+ sqlserver:
+ image: mcr.microsoft.com/mssql/server:2017-latest
+ container_name: sqlserver
+ environment:
+ # 鏃跺尯涓婃捣
+ TZ: Asia/Shanghai
+ ACCEPT_EULA: "Y"
+ SA_PASSWORD: "Ruoyi@123"
+ ports:
+ - "1433:1433"
+ volumes:
+ # 鏁版嵁鎸傝浇
+ - "/docker/sqlserver/data:/var/opt/mssql"
+ network_mode: "host"
+
+ postgres:
+ image: postgres:14.2
+ container_name: postgres
+ environment:
+ POSTGRES_USER: root
+ POSTGRES_PASSWORD: root
+ POSTGRES_DB: postgres
+ ports:
+ - "5432:5432"
+ volumes:
+ - /docker/postgres/data:/var/lib/postgresql/data
+ network_mode: "host"
+
+ postgres13:
+ image: postgres:13.6
+ container_name: postgres13
+ environment:
+ POSTGRES_USER: root
+ POSTGRES_PASSWORD: root
+ POSTGRES_DB: postgres
+ ports:
+ - "5433:5432"
+ volumes:
+ - /docker/postgres13/data:/var/lib/postgresql/data
+ network_mode: "host"
diff --git a/RuoYi-Vue-Plus/script/docker/docker-compose.yml b/RuoYi-Vue-Plus/script/docker/docker-compose.yml
new file mode 100755
index 0000000..f40a4ff
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/docker/docker-compose.yml
@@ -0,0 +1,157 @@
+services:
+ mysql:
+ image: mysql:8.0.42
+ container_name: mysql
+ environment:
+ # 鏃跺尯涓婃捣
+ TZ: Asia/Shanghai
+ # root 瀵嗙爜
+ MYSQL_ROOT_PASSWORD: root
+ # 鍒濆鍖栨暟鎹簱(鍚庣画鐨勫垵濮嬪寲sql浼氬湪杩欎釜搴撴墽琛�)
+ MYSQL_DATABASE: ry-vue
+ ports:
+ - "3306:3306"
+ volumes:
+ # 鏁版嵁鎸傝浇
+ - /docker/mysql/data/:/var/lib/mysql/
+ # 閰嶇疆鎸傝浇
+ - /docker/mysql/conf/:/etc/mysql/conf.d/
+ command:
+ # 灏唌ysql8.0榛樿瀵嗙爜绛栫暐 淇敼涓� 鍘熷厛 绛栫暐 (mysql8.0瀵瑰叾榛樿绛栫暐鍋氫簡鏇存敼 浼氬鑷村瘑鐮佹棤娉曞尮閰�)
+ --default-authentication-plugin=mysql_native_password
+ --character-set-server=utf8mb4
+ --collation-server=utf8mb4_general_ci
+ --explicit_defaults_for_timestamp=true
+ --lower_case_table_names=1
+ privileged: true
+ network_mode: "host"
+
+ nginx-web:
+ image: nginx:1.23.4
+ container_name: nginx-web
+ environment:
+ # 鏃跺尯涓婃捣
+ TZ: Asia/Shanghai
+ ports:
+ - "80:80"
+ - "443:443"
+ volumes:
+ # 璇佷功鏄犲皠
+ - /docker/nginx/cert:/etc/nginx/cert
+ # 閰嶇疆鏂囦欢鏄犲皠
+ - /docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf
+ # 椤甸潰鐩綍
+ - /docker/nginx/html:/usr/share/nginx/html
+ # 鏃ュ織鐩綍
+ - /docker/nginx/log:/var/log/nginx
+ privileged: true
+ network_mode: "host"
+
+ redis:
+ image: redis:7.2.8
+ container_name: redis
+ ports:
+ - "6379:6379"
+ environment:
+ # 鏃跺尯涓婃捣
+ TZ: Asia/Shanghai
+ volumes:
+ # 閰嶇疆鏂囦欢
+ - /docker/redis/conf:/redis/config:rw
+ # 鏁版嵁鏂囦欢
+ - /docker/redis/data/:/redis/data/:rw
+ command: "redis-server /redis/config/redis.conf"
+ privileged: true
+ network_mode: "host"
+
+ minio:
+ # minio 鏈�鍚庝竴涓湭闃夊壊鐗堟湰 涓嶈兘鍐嶈繘琛屽崌绾� 鍦ㄥ線涓婄殑鐗堟湰鍔熻兘琚槈鍓�
+ image: minio/minio:RELEASE.2025-04-22T22-12-26Z
+ container_name: minio
+ ports:
+ # api 绔彛
+ - "9000:9000"
+ # 鎺у埗鍙扮鍙�
+ - "9001:9001"
+ environment:
+ # 鏃跺尯涓婃捣
+ TZ: Asia/Shanghai
+ # 绠$悊鍚庡彴鐢ㄦ埛鍚�
+ MINIO_ROOT_USER: ruoyi
+ # 绠$悊鍚庡彴瀵嗙爜锛屾渶灏�8涓瓧绗�
+ MINIO_ROOT_PASSWORD: ruoyi123
+ # https闇�瑕佹寚瀹氬煙鍚�
+ #MINIO_SERVER_URL: "https://xxx.com:9000"
+ #MINIO_BROWSER_REDIRECT_URL: "https://xxx.com:9001"
+ # 寮�鍚帇缂� on 寮�鍚� off 鍏抽棴
+ MINIO_COMPRESS: "off"
+ # 鎵╁睍鍚� .pdf,.doc 涓虹┖ 鎵�鏈夌被鍨嬪潎鍘嬬缉
+ MINIO_COMPRESS_EXTENSIONS: ""
+ # mime 绫诲瀷 application/pdf 涓虹┖ 鎵�鏈夌被鍨嬪潎鍘嬬缉
+ MINIO_COMPRESS_MIME_TYPES: ""
+ volumes:
+ # 鏄犲皠褰撳墠鐩綍涓嬬殑data鐩綍鑷冲鍣ㄥ唴/data鐩綍
+ - /docker/minio/data:/data
+ # 鏄犲皠閰嶇疆鐩綍
+ - /docker/minio/config:/root/.minio/
+ command: server --address ':9000' --console-address ':9001' /data # 鎸囧畾瀹瑰櫒涓殑鐩綍 /data
+ privileged: true
+ network_mode: "host"
+
+ ruoyi-server1:
+ image: ruoyi/ruoyi-server:5.5.3
+ container_name: ruoyi-server1
+ environment:
+ # 鏃跺尯涓婃捣
+ TZ: Asia/Shanghai
+ SERVER_PORT: 8080
+ SNAIL_PORT: 28080
+ volumes:
+ # 閰嶇疆鏂囦欢
+ - /docker/server1/logs/:/ruoyi/server/logs/
+ # skywalking 鎺㈤拡
+# - /docker/skywalking/agent/:/ruoyi/skywalking/agent
+ privileged: true
+ network_mode: "host"
+
+ ruoyi-server2:
+ image: ruoyi/ruoyi-server:5.5.3
+ container_name: ruoyi-server2
+ environment:
+ # 鏃跺尯涓婃捣
+ TZ: Asia/Shanghai
+ SERVER_PORT: 8081
+ SNAIL_PORT: 28081
+ volumes:
+ # 閰嶇疆鏂囦欢
+ - /docker/server2/logs/:/ruoyi/server/logs/
+ # skywalking 鎺㈤拡
+# - /docker/skywalking/agent/:/ruoyi/skywalking/agent
+ privileged: true
+ network_mode: "host"
+
+ ruoyi-monitor-admin:
+ image: ruoyi/ruoyi-monitor-admin:5.5.3
+ container_name: ruoyi-monitor-admin
+ environment:
+ # 鏃跺尯涓婃捣
+ TZ: Asia/Shanghai
+ volumes:
+ # 閰嶇疆鏂囦欢
+ - /docker/monitor/logs/:/ruoyi/monitor/logs
+ privileged: true
+ network_mode: "host"
+
+ ruoyi-snailjob-server:
+ image: ruoyi/ruoyi-snailjob-server:5.5.3
+ container_name: ruoyi-snailjob-server
+ environment:
+ # 鏃跺尯涓婃捣
+ TZ: Asia/Shanghai
+ ports:
+ - "8800:8800"
+ - "17888:17888"
+ volumes:
+ - /docker/snailjob/logs/:/ruoyi/snailjob/logs
+ privileged: true
+ network_mode: "host"
diff --git a/RuoYi-Vue-Plus/script/docker/nginx/conf/nginx.conf b/RuoYi-Vue-Plus/script/docker/nginx/conf/nginx.conf
new file mode 100755
index 0000000..0a5cb7b
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/docker/nginx/conf/nginx.conf
@@ -0,0 +1,156 @@
+worker_processes 1;
+
+error_log /var/log/nginx/error.log warn;
+pid /var/run/nginx.pid;
+
+events {
+ # 鍙互鏍规嵁涓氬姟骞跺彂閲忛�傚綋璋冮珮
+ worker_connections 1024;
+}
+
+http {
+ include mime.types;
+ default_type application/octet-stream;
+ # 楂樻晥浼犺緭鏂囦欢
+ sendfile on;
+ # 闀胯繛鎺ヨ秴鏃舵椂闂�
+ keepalive_timeout 65;
+ # 鍗曡繛鎺ユ渶澶ц姹傛暟锛屾彁楂橀暱杩炴帴澶嶇敤鐜�
+ keepalive_requests 100000;
+ # 闄愬埗body澶у皬
+ client_max_body_size 100m;
+ client_header_buffer_size 32k;
+ client_body_buffer_size 512k;
+ # 寮�鍚潤鎬佽祫婧愬帇缂�
+ gzip_static on;
+ # 杩炴帴鏁伴檺鍒� (闃插尽绫婚厤缃�) 10m 涓�鑸鐢ㄤ簡锛岃兘瀛樺偍涓婁竾 IP 鐨勮鏁�
+ limit_conn_zone $binary_remote_addr zone=perip:10m;
+ limit_conn_zone $server_name zone=perserver:10m;
+ # 闅愯棌 nginx 鐗堟湰鍙凤紝闃叉鏆撮湶鐗堟湰淇℃伅
+ server_tokens off;
+
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ '$status $body_bytes_sent "$http_referer" '
+ '"$http_user_agent" "$http_x_forwarded_for"';
+
+ access_log /var/log/nginx/access.log main;
+
+ upstream server {
+ ip_hash;
+ server 127.0.0.1:8080;
+ server 127.0.0.1:8081;
+ }
+
+ upstream monitor-admin {
+ server 127.0.0.1:9090;
+ }
+
+ upstream snailjob-server {
+ server 127.0.0.1:8800;
+ }
+
+ server {
+ listen 80;
+ server_name localhost;
+
+ # https閰嶇疆鍙傝�� start
+ #listen 443 ssl;
+
+ # 璇佷功鐩存帴瀛樻斁 /docker/nginx/cert/ 鐩綍涓嬪嵆鍙� 鏇存敼璇佷功鍚嶇О鍗冲彲 鏃犻渶鏇存敼璇佷功璺緞
+ #ssl on;
+ #ssl_certificate /etc/nginx/cert/xxx.local.crt; # /etc/nginx/cert/ 涓篸ocker鏄犲皠璺緞 涓嶅厑璁告洿鏀�
+ #ssl_certificate_key /etc/nginx/cert/xxx.local.key; # /etc/nginx/cert/ 涓篸ocker鏄犲皠璺緞 涓嶅厑璁告洿鏀�
+ #ssl_session_timeout 5m;
+ #ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
+ #ssl_protocols TLSv1.3 TLSv1.2 TLSv1.1 TLSv1;
+ #ssl_prefer_server_ciphers on;
+ # https閰嶇疆鍙傝�� end
+
+ # 婕旂ず鐜閰嶇疆 鎷︽埅闄� GET POST 涔嬪鐨勬墍鏈夎姹�
+ # if ($request_method !~* GET|POST) {
+ # rewrite ^/(.*)$ /403;
+ # }
+
+ # location = /403 {
+ # default_type application/json;
+ # return 200 '{"msg":"婕旂ず妯″紡锛屼笉鍏佽鎿嶄綔","code":500}';
+ # }
+
+ # 闄愬埗澶栫綉璁块棶鍐呯綉 actuator 鐩稿叧璺緞
+ location ~ ^(/[^/]*)?/actuator.*(/.*)?$ {
+ return 403;
+ }
+
+ location / {
+ root /usr/share/nginx/html; # docker鏄犲皠璺緞 涓嶅厑璁告洿鏀�
+ try_files $uri $uri/ /index.html;
+ index index.html index.htm;
+ }
+
+ location /prod-api/ {
+ # 璁剧疆瀹㈡埛绔姹傚ご涓殑 Host 淇℃伅锛堜繚鎸佸師濮� Host锛�
+ proxy_set_header Host $http_host;
+ # 鑾峰彇瀹㈡埛绔湡瀹� IP
+ proxy_set_header X-Real-IP $remote_addr;
+ # 鑷畾涔夊ご REMOTE-HOST锛岃褰曞鎴风 IP
+ proxy_set_header REMOTE-HOST $remote_addr;
+ # 鑾峰彇瀹屾暣鐨勫鎴风 IP 閾撅紙缁忚繃澶氱骇浠g悊鏃讹級
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ # 璁剧疆鍚庣鍝嶅簲瓒呮椂鏃堕棿锛堣繖閲屾槸 24 灏忔椂锛岄�傚悎闀胯繛鎺�/SSE锛�
+ proxy_read_timeout 86400s;
+ # SSE (Server-Sent Events) 涓� WebSocket 鏀寔鍙傛暟
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ # 绂佺敤浠g悊缂撳啿锛屾暟鎹洿鎺ヤ紶缁欏鎴风
+ proxy_buffering off;
+ # 绂佺敤浠g悊缂撳瓨
+ proxy_cache off;
+ proxy_pass http://server/;
+ }
+
+ # https 浼氭嫤鎴唴閾炬墍鏈夌殑 http 璇锋眰 閫犳垚鍔熻兘鏃犳硶浣跨敤
+ # 瑙e喅鏂规1 灏� admin 鏈嶅姟 涔熼厤缃垚 https
+ # 瑙e喅鏂规2 灏嗚彍鍗曢厤缃负澶栭摼璁块棶 璧扮嫭绔嬮〉闈� http 璁块棶
+ location /admin/ {
+ # 璁剧疆瀹㈡埛绔姹傚ご涓殑 Host 淇℃伅锛堜繚鎸佸師濮� Host锛�
+ proxy_set_header Host $http_host;
+ # 鑾峰彇瀹㈡埛绔湡瀹� IP
+ proxy_set_header X-Real-IP $remote_addr;
+ # 鑷畾涔夊ご REMOTE-HOST锛岃褰曞鎴风 IP
+ proxy_set_header REMOTE-HOST $remote_addr;
+ # 鑾峰彇瀹屾暣鐨勫鎴风 IP 閾撅紙缁忚繃澶氱骇浠g悊鏃讹級
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ # 绂佺敤浠g悊缂撳啿锛屾暟鎹洿鎺ヤ紶缁欏鎴风
+ proxy_buffering off;
+ # 绂佺敤浠g悊缂撳瓨
+ proxy_cache off;
+ proxy_pass http://monitor-admin/admin/;
+ }
+
+ location /snail-job/ {
+ # 璁剧疆瀹㈡埛绔姹傚ご涓殑 Host 淇℃伅锛堜繚鎸佸師濮� Host锛�
+ proxy_set_header Host $http_host;
+ # 鑾峰彇瀹㈡埛绔湡瀹� IP
+ proxy_set_header X-Real-IP $remote_addr;
+ # 鑷畾涔夊ご REMOTE-HOST锛岃褰曞鎴风 IP
+ proxy_set_header REMOTE-HOST $remote_addr;
+ # 鑾峰彇瀹屾暣鐨勫鎴风 IP 閾撅紙缁忚繃澶氱骇浠g悊鏃讹級
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ # SSE (Server-Sent Events) 涓� WebSocket 鏀寔鍙傛暟
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ # 绂佺敤浠g悊缂撳啿锛岀洿鎺ヤ紶杈撶粰瀹㈡埛绔�
+ proxy_buffering off;
+ # 绂佺敤浠g悊缂撳瓨
+ proxy_cache off;
+ proxy_pass http://snailjob-server/snail-job/;
+ }
+
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root html;
+ }
+ }
+}
diff --git a/RuoYi-Vue-Plus/script/docker/redis/conf/redis.conf b/RuoYi-Vue-Plus/script/docker/redis/conf/redis.conf
new file mode 100755
index 0000000..72255c6
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/docker/redis/conf/redis.conf
@@ -0,0 +1,28 @@
+# redis 瀵嗙爜
+requirepass ruoyi123
+
+# key 鐩戝惉鍣ㄩ厤缃�
+# notify-keyspace-events Ex
+
+# 閰嶇疆鎸佷箙鍖栨枃浠跺瓨鍌ㄨ矾寰�
+dir /redis/data
+# 閰嶇疆rdb
+# 15鍒嗛挓鍐呮湁鑷冲皯1涓猭ey琚洿鏀瑰垯杩涜蹇収
+save 900 1
+# 5鍒嗛挓鍐呮湁鑷冲皯10涓猭ey琚洿鏀瑰垯杩涜蹇収
+save 300 10
+# 1鍒嗛挓鍐呮湁鑷冲皯10000涓猭ey琚洿鏀瑰垯杩涜蹇収
+save 60 10000
+# 寮�鍚帇缂�
+rdbcompression yes
+# rdb鏂囦欢鍚� 鐢ㄩ粯璁ょ殑鍗冲彲
+dbfilename dump.rdb
+
+# 寮�鍚痑of
+appendonly yes
+# 鏂囦欢鍚�
+appendfilename "appendonly.aof"
+# 鎸佷箙鍖栫瓥鐣�,no:涓嶅悓姝�,everysec:姣忕涓�娆�,always:鎬绘槸鍚屾,閫熷害姣旇緝鎱�
+# appendfsync always
+appendfsync everysec
+# appendfsync no
diff --git a/RuoYi-Vue-Plus/script/docker/redis/data/README.md b/RuoYi-Vue-Plus/script/docker/redis/data/README.md
new file mode 100755
index 0000000..fbc5474
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/docker/redis/data/README.md
@@ -0,0 +1 @@
+鏁版嵁鐩綍 璇锋墽琛� `chmod 777 /docker/redis/data` 璧嬩簣璇诲啓鏉冮檺 鍚﹀垯灏嗘棤娉曞啓鍏ユ暟鎹�
\ No newline at end of file
diff --git a/RuoYi-Vue-Plus/script/leave/leave1.json b/RuoYi-Vue-Plus/script/leave/leave1.json
new file mode 100755
index 0000000..d883a4b
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/leave/leave1.json
@@ -0,0 +1,129 @@
+{
+ "nodeList": [
+ {
+ "nodeType": "0",
+ "nodeCode": "d5ee3ddf-3968-4379-a86f-9ceabde5faac",
+ "nodeName": "寮�濮�",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "200,200|200,200",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "d5ee3ddf-3968-4379-a86f-9ceabde5faac",
+ "nextNodeCode": "dd515cdd-59f6-446f-94ca-25ca062afb42",
+ "coordinate": "220,200;310,200"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "dd515cdd-59f6-446f-94ca-25ca062afb42",
+ "nodeName": "鐢宠浜�",
+ "permissionFlag": "",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,copy\"}]",
+ "coordinate": "360,200|360,200",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "dd515cdd-59f6-446f-94ca-25ca062afb42",
+ "nextNodeCode": "78fa8e5b-e809-44ed-978a-41092409ebcf",
+ "coordinate": "410,200;490,200"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "78fa8e5b-e809-44ed-978a-41092409ebcf",
+ "nodeName": "缁勯暱",
+ "permissionFlag": "role:1",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,copy,transfer,trust,file\"}]",
+ "coordinate": "540,200|540,200",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "78fa8e5b-e809-44ed-978a-41092409ebcf",
+ "nextNodeCode": "a8abf15f-b83e-428a-86cc-033555ea9bbe",
+ "coordinate": "590,200;670,200"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "a8abf15f-b83e-428a-86cc-033555ea9bbe",
+ "nodeName": "閮ㄩ棬涓荤",
+ "permissionFlag": "role:3@@role:4",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,copy,transfer,trust,file\"}]",
+ "coordinate": "720,200|720,200",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "a8abf15f-b83e-428a-86cc-033555ea9bbe",
+ "nextNodeCode": "8b82b7d7-8660-455e-b880-d6d22ea3eb6d",
+ "coordinate": "770,200;880,200"
+ }
+ ]
+ },
+ {
+ "nodeType": "2",
+ "nodeCode": "8b82b7d7-8660-455e-b880-d6d22ea3eb6d",
+ "nodeName": "缁撴潫",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "900,200|900,200",
+ "version": "1",
+ "skipList": []
+ }
+ ],
+ "flowCode": "leave1",
+ "flowName": "璇峰亣鐢宠-鏅��",
+ "modelValue": "CLASSICS",
+ "category": "103",
+ "version": "1",
+ "formCustom": "N",
+ "formPath": "/workflow/leaveEdit/index",
+ "listenerType": null,
+ "listenerPath": null
+}
diff --git a/RuoYi-Vue-Plus/script/leave/leave2.json b/RuoYi-Vue-Plus/script/leave/leave2.json
new file mode 100755
index 0000000..92aa6a9
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/leave/leave2.json
@@ -0,0 +1,187 @@
+{
+ "nodeList": [
+ {
+ "nodeType": "0",
+ "nodeCode": "cef3895c-f7d8-4598-8bf3-8ec2ef6ce84a",
+ "nodeName": "寮�濮�",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "300,240|300,240",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "cef3895c-f7d8-4598-8bf3-8ec2ef6ce84a",
+ "nextNodeCode": "fdcae93b-b69c-498a-b231-09255e74bcbd",
+ "coordinate": "320,240;390,240"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "fdcae93b-b69c-498a-b231-09255e74bcbd",
+ "nodeName": "鐢宠浜�",
+ "permissionFlag": "",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file\"}]",
+ "coordinate": "440,240|440,240",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "fdcae93b-b69c-498a-b231-09255e74bcbd",
+ "nextNodeCode": "7b8c7ead-7dc8-4951-a7f3-f0c41995909e",
+ "coordinate": "490,240;535,240"
+ }
+ ]
+ },
+ {
+ "nodeType": "3",
+ "nodeCode": "7b8c7ead-7dc8-4951-a7f3-f0c41995909e",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "560,240",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": "le@@leaveDays|2",
+ "skipName": null,
+ "nowNodeCode": "7b8c7ead-7dc8-4951-a7f3-f0c41995909e",
+ "nextNodeCode": "b3528155-dcb7-4445-bbdf-3d00e3499e86",
+ "coordinate": "560,265;560,320;670,320"
+ },
+ {
+ "skipType": "PASS",
+ "skipCondition": "gt@@leaveDays|2",
+ "skipName": "澶т簬涓ゅぉ",
+ "nowNodeCode": "7b8c7ead-7dc8-4951-a7f3-f0c41995909e",
+ "nextNodeCode": "5ed2362b-fc0c-4d52-831f-95208b830605",
+ "coordinate": "560,215;560,160;670,160|560,187"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "b3528155-dcb7-4445-bbdf-3d00e3499e86",
+ "nodeName": "缁勯暱",
+ "permissionFlag": "3@@4",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]",
+ "coordinate": "720,320|720,320",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "b3528155-dcb7-4445-bbdf-3d00e3499e86",
+ "nextNodeCode": "c9fa6d7d-2a74-4e78-b947-0cad8a6af869",
+ "coordinate": "770,320;860,320;860,280"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "c9fa6d7d-2a74-4e78-b947-0cad8a6af869",
+ "nodeName": "鎬荤粡鐞�",
+ "permissionFlag": "role:1",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]",
+ "coordinate": "860,240|860,240",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "c9fa6d7d-2a74-4e78-b947-0cad8a6af869",
+ "nextNodeCode": "40aa65fd-0712-4d23-b6f7-d0432b920fd1",
+ "coordinate": "910,240;980,240"
+ }
+ ]
+ },
+ {
+ "nodeType": "2",
+ "nodeCode": "40aa65fd-0712-4d23-b6f7-d0432b920fd1",
+ "nodeName": "缁撴潫",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "1000,240|1000,240",
+ "version": "1",
+ "skipList": []
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "5ed2362b-fc0c-4d52-831f-95208b830605",
+ "nodeName": "閮ㄩ棬棰嗗",
+ "permissionFlag": "role:1",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]",
+ "coordinate": "720,160|720,160",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "5ed2362b-fc0c-4d52-831f-95208b830605",
+ "nextNodeCode": "c9fa6d7d-2a74-4e78-b947-0cad8a6af869",
+ "nextNodeType": "1",
+ "coordinate": "770,160;860,160;860,200"
+ }
+ ]
+ }
+ ],
+ "flowCode": "leave2",
+ "flowName": "璇峰亣鐢宠-鎺掍粬缃戝叧",
+ "modelValue": "CLASSICS",
+ "category": "103",
+ "version": "1",
+ "formCustom": "N",
+ "formPath": "/workflow/leaveEdit/index",
+ "listenerType": null,
+ "listenerPath": null
+}
diff --git a/RuoYi-Vue-Plus/script/leave/leave3.json b/RuoYi-Vue-Plus/script/leave/leave3.json
new file mode 100755
index 0000000..7699fc9
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/leave/leave3.json
@@ -0,0 +1,211 @@
+{
+ "nodeList": [
+ {
+ "nodeType": "0",
+ "nodeCode": "a80ecf9f-f465-4ae5-a429-e30ec5d0f957",
+ "nodeName": "寮�濮�",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "380,220|380,220",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "a80ecf9f-f465-4ae5-a429-e30ec5d0f957",
+ "nextNodeCode": "b7bbb571-06de-455c-8083-f83c07bf0b99",
+ "coordinate": "400,220;470,220"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "b7bbb571-06de-455c-8083-f83c07bf0b99",
+ "nodeName": "鐢宠浜�",
+ "permissionFlag": "",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file\"}]",
+ "coordinate": "520,220|520,220",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "b7bbb571-06de-455c-8083-f83c07bf0b99",
+ "nextNodeCode": "84d7ed24-bb44-4ba1-bf1f-e6f5092d3f0a",
+ "coordinate": "570,220;655,220"
+ }
+ ]
+ },
+ {
+ "nodeType": "4",
+ "nodeCode": "84d7ed24-bb44-4ba1-bf1f-e6f5092d3f0a",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "680,220",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "84d7ed24-bb44-4ba1-bf1f-e6f5092d3f0a",
+ "nextNodeCode": "4b7743cd-940c-431b-926f-e7b614fbf1fe",
+ "coordinate": "680,195;680,140;750,140"
+ },
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "84d7ed24-bb44-4ba1-bf1f-e6f5092d3f0a",
+ "nextNodeCode": "762cb975-37d8-4276-b6db-79a4c3606394",
+ "coordinate": "680,245;680,300;750,300"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "4b7743cd-940c-431b-926f-e7b614fbf1fe",
+ "nodeName": "甯傚満閮�",
+ "permissionFlag": "role:1",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]",
+ "coordinate": "800,140|800,140",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "4b7743cd-940c-431b-926f-e7b614fbf1fe",
+ "nextNodeCode": "b66b6563-f9fe-41cc-a782-f7837bb6f3d2",
+ "coordinate": "850,140;920,140;920,195"
+ }
+ ]
+ },
+ {
+ "nodeType": "4",
+ "nodeCode": "b66b6563-f9fe-41cc-a782-f7837bb6f3d2",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "920,220",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "b66b6563-f9fe-41cc-a782-f7837bb6f3d2",
+ "nextNodeCode": "23e7429e-2b47-4431-b93e-40db7c431ce6",
+ "coordinate": "945,220;975,220;975,220;960,220;960,220;990,220"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "23e7429e-2b47-4431-b93e-40db7c431ce6",
+ "nodeName": "CEO",
+ "permissionFlag": "1",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]",
+ "coordinate": "1040,220|1040,220",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "23e7429e-2b47-4431-b93e-40db7c431ce6",
+ "nextNodeCode": "f5ace37f-5a5e-4e64-a6f6-913ab9a71cd1",
+ "coordinate": "1090,220;1140,220"
+ }
+ ]
+ },
+ {
+ "nodeType": "2",
+ "nodeCode": "f5ace37f-5a5e-4e64-a6f6-913ab9a71cd1",
+ "nodeName": "缁撴潫",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "1160,220|1160,220",
+ "version": "1",
+ "skipList": []
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "762cb975-37d8-4276-b6db-79a4c3606394",
+ "nodeName": "缁煎悎閮�",
+ "permissionFlag": "role:3@@role:4",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]",
+ "coordinate": "800,300|800,300",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "762cb975-37d8-4276-b6db-79a4c3606394",
+ "nextNodeCode": "b66b6563-f9fe-41cc-a782-f7837bb6f3d2",
+ "nextNodeType": "4",
+ "coordinate": "850,300;920,300;920,245"
+ }
+ ]
+ }
+ ],
+ "flowCode": "leave3",
+ "flowName": "璇峰亣鐢宠-骞惰缃戝叧",
+ "modelValue": "CLASSICS",
+ "category": "103",
+ "version": "1",
+ "formCustom": "N",
+ "formPath": "/workflow/leaveEdit/index",
+ "listenerType": null,
+ "listenerPath": null
+}
diff --git a/RuoYi-Vue-Plus/script/leave/leave4.json b/RuoYi-Vue-Plus/script/leave/leave4.json
new file mode 100755
index 0000000..8dc6f44
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/leave/leave4.json
@@ -0,0 +1,154 @@
+{
+ "nodeList": [
+ {
+ "nodeType": "0",
+ "nodeCode": "9ce8bf00-f25b-4fc6-91b8-827082fc4876",
+ "nodeName": "寮�濮�",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "320,240|320,240",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "9ce8bf00-f25b-4fc6-91b8-827082fc4876",
+ "nextNodeCode": "e90b98ef-35b4-410c-a663-bae8b7624b9f",
+ "coordinate": "340,240;410,240"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "e90b98ef-35b4-410c-a663-bae8b7624b9f",
+ "nodeName": "鐢宠浜�",
+ "permissionFlag": "",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file\"}]",
+ "coordinate": "460,240|460,240",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "e90b98ef-35b4-410c-a663-bae8b7624b9f",
+ "nextNodeCode": "768b5b1a-6726-4d67-8853-4cc70d5b1045",
+ "coordinate": "510,240;590,240"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "768b5b1a-6726-4d67-8853-4cc70d5b1045",
+ "nodeName": "鐧惧垎涔�60閫氳繃",
+ "permissionFlag": "${userList}",
+ "nodeRatio": "60.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,addSign,subSign\"}]",
+ "coordinate": "640,240|640,240",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "768b5b1a-6726-4d67-8853-4cc70d5b1045",
+ "nextNodeCode": "2f9f2e21-9bcf-42a3-a07c-13037aad22d1",
+ "coordinate": "690,240;770,240"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "2f9f2e21-9bcf-42a3-a07c-13037aad22d1",
+ "nodeName": "鍏ㄩ儴瀹℃壒閫氳繃",
+ "permissionFlag": "role:1@@role:3",
+ "nodeRatio": "100.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,addSign,subSign\"}]",
+ "coordinate": "820,240|820,240",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "2f9f2e21-9bcf-42a3-a07c-13037aad22d1",
+ "nextNodeCode": "27461e01-3d9f-4530-8fe3-bd5ec7f9571f",
+ "coordinate": "870,240;950,240"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "27461e01-3d9f-4530-8fe3-bd5ec7f9571f",
+ "nodeName": "CEO",
+ "permissionFlag": "1",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]",
+ "coordinate": "1000,240|1000,240",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "27461e01-3d9f-4530-8fe3-bd5ec7f9571f",
+ "nextNodeCode": "b62b88c3-8d8d-4969-911e-2aaea219e7fc",
+ "coordinate": "1050,240;1080,240;1080,240;1070,240;1070,240;1100,240"
+ }
+ ]
+ },
+ {
+ "nodeType": "2",
+ "nodeCode": "b62b88c3-8d8d-4969-911e-2aaea219e7fc",
+ "nodeName": "缁撴潫",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "1120,240|1120,240",
+ "version": "1",
+ "skipList": []
+ }
+ ],
+ "flowCode": "leave4",
+ "flowName": "璇峰亣鐢宠-浼氱",
+ "modelValue": "CLASSICS",
+ "category": "103",
+ "version": "1",
+ "formCustom": "N",
+ "formPath": "/workflow/leaveEdit/index",
+ "listenerType": null,
+ "listenerPath": null
+}
diff --git a/RuoYi-Vue-Plus/script/leave/leave5.json b/RuoYi-Vue-Plus/script/leave/leave5.json
new file mode 100755
index 0000000..7a9a613
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/leave/leave5.json
@@ -0,0 +1,211 @@
+{
+ "nodeList": [
+ {
+ "nodeType": "0",
+ "nodeCode": "ebebaf26-9cb6-497e-8119-4c9fed4c597c",
+ "nodeName": "寮�濮�",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "300,220|300,220",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "ebebaf26-9cb6-497e-8119-4c9fed4c597c",
+ "nextNodeCode": "e1b04e96-dc81-4858-a309-2fe945d2f374",
+ "coordinate": "320,220;350,220;350,220;340,220;340,220;370,220"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "e1b04e96-dc81-4858-a309-2fe945d2f374",
+ "nodeName": "鐢宠浜�",
+ "permissionFlag": "",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file\"}]",
+ "coordinate": "420,220|420,220",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "e1b04e96-dc81-4858-a309-2fe945d2f374",
+ "nextNodeCode": "3e743f4f-51ca-41d4-8e94-21f5dd9b59c9",
+ "coordinate": "470,220;535,220"
+ }
+ ]
+ },
+ {
+ "nodeType": "4",
+ "nodeCode": "3e743f4f-51ca-41d4-8e94-21f5dd9b59c9",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "560,220",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "3e743f4f-51ca-41d4-8e94-21f5dd9b59c9",
+ "nextNodeCode": "c80f273e-1f17-4bd8-9ad1-04a4a94ea862",
+ "coordinate": "560,245;560,320;650,320"
+ },
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "3e743f4f-51ca-41d4-8e94-21f5dd9b59c9",
+ "nextNodeCode": "1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4",
+ "coordinate": "560,195;560,120;650,120"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "c80f273e-1f17-4bd8-9ad1-04a4a94ea862",
+ "nodeName": "浼氱",
+ "permissionFlag": "role:1@@role:3",
+ "nodeRatio": "100.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,addSign,subSign\"}]",
+ "coordinate": "700,320|700,320",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "c80f273e-1f17-4bd8-9ad1-04a4a94ea862",
+ "nextNodeCode": "1a20169e-3d82-4926-a151-e2daad28de1b",
+ "coordinate": "750,320;860,320;860,245"
+ }
+ ]
+ },
+ {
+ "nodeType": "4",
+ "nodeCode": "1a20169e-3d82-4926-a151-e2daad28de1b",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "860,220",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "1a20169e-3d82-4926-a151-e2daad28de1b",
+ "nextNodeCode": "7a8f0473-e409-442e-a843-5c2b813d00e9",
+ "coordinate": "885,220;950,220"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "7a8f0473-e409-442e-a843-5c2b813d00e9",
+ "nodeName": "CEO",
+ "permissionFlag": "1",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]",
+ "coordinate": "1000,220|1000,220",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "7a8f0473-e409-442e-a843-5c2b813d00e9",
+ "nextNodeCode": "03c4d2bc-58b5-4408-a2e4-65afb046f169",
+ "coordinate": "1050,220;1120,220"
+ }
+ ]
+ },
+ {
+ "nodeType": "2",
+ "nodeCode": "03c4d2bc-58b5-4408-a2e4-65afb046f169",
+ "nodeName": "缁撴潫",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "1140,220|1140,220",
+ "version": "1",
+ "skipList": []
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4",
+ "nodeName": "鐧惧垎涔�60绁ㄧ",
+ "permissionFlag": "${userList}",
+ "nodeRatio": "60.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,addSign,subSign\"}]",
+ "coordinate": "700,120|700,120",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4",
+ "nextNodeCode": "1a20169e-3d82-4926-a151-e2daad28de1b",
+ "nextNodeType": "4",
+ "coordinate": "750,120;860,120;860,195"
+ }
+ ]
+ }
+ ],
+ "flowCode": "leave5",
+ "flowName": "璇峰亣鐢宠-骞惰浼氱缃戝叧",
+ "modelValue": "CLASSICS",
+ "category": "103",
+ "version": "1",
+ "formCustom": "N",
+ "formPath": "/workflow/leaveEdit/index",
+ "listenerType": null,
+ "listenerPath": null
+}
diff --git a/RuoYi-Vue-Plus/script/leave/leave6.json b/RuoYi-Vue-Plus/script/leave/leave6.json
new file mode 100755
index 0000000..bb282b0
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/leave/leave6.json
@@ -0,0 +1,368 @@
+{
+ "nodeList": [
+ {
+ "nodeType": "0",
+ "nodeCode": "122b89a5-7c6f-40a3-aa09-7a263f902054",
+ "nodeName": "寮�濮�",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "240,300|240,300",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "122b89a5-7c6f-40a3-aa09-7a263f902054",
+ "nextNodeCode": "c25a0e86-fdd1-4f03-8e22-14db70389dbd",
+ "coordinate": "260,300;350,300"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "c25a0e86-fdd1-4f03-8e22-14db70389dbd",
+ "nodeName": "鐢宠浜�",
+ "permissionFlag": "",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file\"}]",
+ "coordinate": "400,300|400,300",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "c25a0e86-fdd1-4f03-8e22-14db70389dbd",
+ "nextNodeCode": "07ecda1d-7a0a-47b5-8a91-6186c9473742",
+ "coordinate": "450,300;510,300"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "2bfa3919-78cf-4bc1-b59b-df463a4546f9",
+ "nodeName": "鍓粡鐞�",
+ "permissionFlag": "role:1@@role:3@@role:4",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]",
+ "coordinate": "860,200|860,200",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "2bfa3919-78cf-4bc1-b59b-df463a4546f9",
+ "nextNodeCode": "394e1cc8-b8b2-4189-9f81-44448e88ac32",
+ "coordinate": "910,200;1000,200;1000,275"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "ec17f60e-94e0-4d96-a3ce-3417e9d32d60",
+ "nodeName": "缁勯暱",
+ "permissionFlag": "1",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]",
+ "coordinate": "860,400|860,400",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "ec17f60e-94e0-4d96-a3ce-3417e9d32d60",
+ "nextNodeCode": "394e1cc8-b8b2-4189-9f81-44448e88ac32",
+ "coordinate": "910,400;1000,400;1000,325"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "07ecda1d-7a0a-47b5-8a91-6186c9473742",
+ "nodeName": "鍓粍闀�",
+ "permissionFlag": "1",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,transfer,copy,pop\"}]",
+ "coordinate": "560,300|560,300",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "07ecda1d-7a0a-47b5-8a91-6186c9473742",
+ "nextNodeCode": "48117e2c-6328-406b-b102-c4a9d115bb13",
+ "coordinate": "610,300;675,300"
+ }
+ ]
+ },
+ {
+ "nodeType": "3",
+ "nodeCode": "48117e2c-6328-406b-b102-c4a9d115bb13",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "700,300",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": "default@@${leaveDays > 2}",
+ "skipName": "澶т簬涓ゅぉ",
+ "nowNodeCode": "48117e2c-6328-406b-b102-c4a9d115bb13",
+ "nextNodeCode": "2bfa3919-78cf-4bc1-b59b-df463a4546f9",
+ "nextNodeType": "1",
+ "coordinate": "700,275;700,200;810,200|700,237"
+ },
+ {
+ "skipType": "PASS",
+ "skipCondition": "spel@@#{@testLeaveServiceImpl.eval(#leaveDays)}",
+ "skipName": null,
+ "nowNodeCode": "48117e2c-6328-406b-b102-c4a9d115bb13",
+ "nextNodeCode": "ec17f60e-94e0-4d96-a3ce-3417e9d32d60",
+ "nextNodeType": "1",
+ "coordinate": "700,325;700,400;810,400"
+ }
+ ]
+ },
+ {
+ "nodeType": "3",
+ "nodeCode": "394e1cc8-b8b2-4189-9f81-44448e88ac32",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "1000,300",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "394e1cc8-b8b2-4189-9f81-44448e88ac32",
+ "nextNodeCode": "9c93a195-cff2-4e17-ab0a-a4f264191496",
+ "coordinate": "1025,300;1130,300"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "9c93a195-cff2-4e17-ab0a-a4f264191496",
+ "nodeName": "缁忕悊浼氱",
+ "permissionFlag": "1@@3",
+ "nodeRatio": "100.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,pop,addSign,subSign\"}]",
+ "coordinate": "1180,300|1180,300",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "9c93a195-cff2-4e17-ab0a-a4f264191496",
+ "nextNodeCode": "a1a42056-afd1-4e90-88bc-36cbf5a66992",
+ "coordinate": "1230,300;1315,300"
+ }
+ ]
+ },
+ {
+ "nodeType": "4",
+ "nodeCode": "a1a42056-afd1-4e90-88bc-36cbf5a66992",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "1340,300",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "a1a42056-afd1-4e90-88bc-36cbf5a66992",
+ "nextNodeCode": "fcfdd9f6-f526-4c1a-b71d-88afa31aebc5",
+ "coordinate": "1340,325;1340,400;1430,400"
+ },
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "a1a42056-afd1-4e90-88bc-36cbf5a66992",
+ "nextNodeCode": "350dfa0c-a77c-4efa-8527-10efa02d8be4",
+ "coordinate": "1340,275;1340,200;1430,200"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "350dfa0c-a77c-4efa-8527-10efa02d8be4",
+ "nodeName": "鎬荤粡鐞�",
+ "permissionFlag": "3@@1",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]",
+ "coordinate": "1480,200|1480,200",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "350dfa0c-a77c-4efa-8527-10efa02d8be4",
+ "nextNodeCode": "c36a46ef-04f9-463f-bad7-4b395c818519",
+ "coordinate": "1530,200;1640,200;1640,275"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "fcfdd9f6-f526-4c1a-b71d-88afa31aebc5",
+ "nodeName": "鍓�荤粡鐞�",
+ "permissionFlag": "1@@3",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]",
+ "coordinate": "1480,400|1480,400",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "fcfdd9f6-f526-4c1a-b71d-88afa31aebc5",
+ "nextNodeCode": "c36a46ef-04f9-463f-bad7-4b395c818519",
+ "coordinate": "1530,400;1640,400;1640,325"
+ }
+ ]
+ },
+ {
+ "nodeType": "4",
+ "nodeCode": "c36a46ef-04f9-463f-bad7-4b395c818519",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "1640,300",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "c36a46ef-04f9-463f-bad7-4b395c818519",
+ "nextNodeCode": "3fcea762-b53a-4ae1-8365-7bec90444828",
+ "coordinate": "1665,300;1770,300"
+ }
+ ]
+ },
+ {
+ "nodeType": "1",
+ "nodeCode": "3fcea762-b53a-4ae1-8365-7bec90444828",
+ "nodeName": "钁d簨",
+ "permissionFlag": "1",
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": "",
+ "listenerPath": "",
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]",
+ "coordinate": "1820,300|1820,300",
+ "version": "1",
+ "skipList": [
+ {
+ "skipType": "PASS",
+ "skipCondition": null,
+ "skipName": null,
+ "nowNodeCode": "3fcea762-b53a-4ae1-8365-7bec90444828",
+ "nextNodeCode": "9cfbfd3e-6c04-41d6-9fc2-6787a7d2cd31",
+ "coordinate": "1870,300;1960,300"
+ }
+ ]
+ },
+ {
+ "nodeType": "2",
+ "nodeCode": "9cfbfd3e-6c04-41d6-9fc2-6787a7d2cd31",
+ "nodeName": "缁撴潫",
+ "permissionFlag": null,
+ "nodeRatio": "0.000",
+ "anyNodeSkip": null,
+ "listenerType": null,
+ "listenerPath": null,
+ "formCustom": "N",
+ "formPath": null,
+ "ext": "[]",
+ "coordinate": "1980,300|1980,300",
+ "version": "1",
+ "skipList": []
+ }
+ ],
+ "flowCode": "leave6",
+ "flowName": "璇峰亣鐢宠-鎺掍粬骞惰浼氱",
+ "modelValue": "CLASSICS",
+ "category": "103",
+ "version": "1",
+ "formCustom": "N",
+ "formPath": "/workflow/leaveEdit/index",
+ "listenerType": null,
+ "listenerPath": null
+}
diff --git a/RuoYi-Vue-Plus/script/sql/oracle/oracle_ry_job.sql b/RuoYi-Vue-Plus/script/sql/oracle/oracle_ry_job.sql
new file mode 100755
index 0000000..2a7f11f
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/oracle/oracle_ry_job.sql
@@ -0,0 +1,940 @@
+/*
+ SnailJob Database Transfer Tool
+ Source Server Type : MySQL
+ Target Server Type : Oracle
+ Date: 2025-06-21 23:33:11
+*/
+
+
+-- sj_namespace
+CREATE TABLE sj_namespace
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ name varchar2(64) NULL,
+ unique_id varchar2(64) NULL,
+ description varchar2(256) DEFAULT '' NULL,
+ deleted smallint DEFAULT 0 NOT NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_namespace
+ ADD CONSTRAINT pk_sj_namespace PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_namespace_01 ON sj_namespace (name);
+
+COMMENT ON COLUMN sj_namespace.id IS '涓婚敭';
+COMMENT ON COLUMN sj_namespace.name IS '鍚嶇О';
+COMMENT ON COLUMN sj_namespace.unique_id IS '鍞竴id';
+COMMENT ON COLUMN sj_namespace.description IS '鎻忚堪';
+COMMENT ON COLUMN sj_namespace.deleted IS '閫昏緫鍒犻櫎 1銆佸垹闄�';
+COMMENT ON COLUMN sj_namespace.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_namespace.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_namespace IS '鍛藉悕绌洪棿';
+
+INSERT INTO sj_namespace(name, unique_id, description, deleted, create_dt, update_dt) VALUES ('Development', 'dev', '', 0, sysdate, sysdate);
+INSERT INTO sj_namespace(name, unique_id, description, deleted, create_dt, update_dt) VALUES ('Production', 'prod', '', 0, sysdate, sysdate);
+
+-- sj_group_config
+CREATE TABLE sj_group_config
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) DEFAULT '' NULL,
+ description varchar2(256) DEFAULT '' NULL,
+ token varchar2(64) DEFAULT 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT' NULL,
+ group_status smallint DEFAULT 0 NOT NULL,
+ version number NOT NULL,
+ group_partition number NOT NULL,
+ id_generator_mode smallint DEFAULT 1 NOT NULL,
+ init_scene smallint DEFAULT 0 NOT NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_group_config
+ ADD CONSTRAINT pk_sj_group_config PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX uk_sj_group_config_01 ON sj_group_config (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_group_config.id IS '涓婚敭';
+COMMENT ON COLUMN sj_group_config.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_group_config.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_group_config.description IS '缁勬弿杩�';
+COMMENT ON COLUMN sj_group_config.token IS 'token';
+COMMENT ON COLUMN sj_group_config.group_status IS '缁勭姸鎬� 0銆佹湭鍚敤 1銆佸惎鐢�';
+COMMENT ON COLUMN sj_group_config.version IS '鐗堟湰鍙�';
+COMMENT ON COLUMN sj_group_config.group_partition IS '鍒嗗尯';
+COMMENT ON COLUMN sj_group_config.id_generator_mode IS '鍞竴id鐢熸垚妯″紡 榛樿鍙锋妯″紡';
+COMMENT ON COLUMN sj_group_config.init_scene IS '鏄惁鍒濆鍖栧満鏅� 0:鍚� 1:鏄�';
+COMMENT ON COLUMN sj_group_config.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_group_config.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_group_config IS '缁勯厤缃�';
+
+INSERT INTO sj_group_config (namespace_id, group_name, description, token, group_status, version, group_partition, id_generator_mode, init_scene, create_dt, update_dt) VALUES ('dev', 'ruoyi_group', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, sysdate, sysdate);
+INSERT INTO sj_group_config (namespace_id, group_name, description, token, group_status, version, group_partition, id_generator_mode, init_scene, create_dt, update_dt) VALUES ('prod', 'ruoyi_group', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, sysdate, sysdate);
+
+-- sj_notify_config
+CREATE TABLE sj_notify_config
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) NULL,
+ notify_name varchar2(64) DEFAULT '' NULL,
+ system_task_type smallint DEFAULT 3 NOT NULL,
+ notify_status smallint DEFAULT 0 NOT NULL,
+ recipient_ids varchar2(128) NULL,
+ notify_threshold number DEFAULT 0 NOT NULL,
+ notify_scene smallint DEFAULT 0 NOT NULL,
+ rate_limiter_status smallint DEFAULT 0 NOT NULL,
+ rate_limiter_threshold number DEFAULT 0 NOT NULL,
+ description varchar2(256) DEFAULT '' NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_notify_config
+ ADD CONSTRAINT pk_sj_notify_config PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_notify_config_01 ON sj_notify_config (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_notify_config.id IS '涓婚敭';
+COMMENT ON COLUMN sj_notify_config.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_notify_config.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_notify_config.notify_name IS '閫氱煡鍚嶇О';
+COMMENT ON COLUMN sj_notify_config.system_task_type IS '浠诲姟绫诲瀷 1. 閲嶈瘯浠诲姟 2. 閲嶈瘯鍥炶皟 3銆丣OB浠诲姟 4銆乄ORKFLOW浠诲姟';
+COMMENT ON COLUMN sj_notify_config.notify_status IS '閫氱煡鐘舵�� 0銆佹湭鍚敤 1銆佸惎鐢�';
+COMMENT ON COLUMN sj_notify_config.recipient_ids IS '鎺ユ敹浜篿d鍒楄〃';
+COMMENT ON COLUMN sj_notify_config.notify_threshold IS '閫氱煡闃堝��';
+COMMENT ON COLUMN sj_notify_config.notify_scene IS '閫氱煡鍦烘櫙';
+COMMENT ON COLUMN sj_notify_config.rate_limiter_status IS '闄愭祦鐘舵�� 0銆佹湭鍚敤 1銆佸惎鐢�';
+COMMENT ON COLUMN sj_notify_config.rate_limiter_threshold IS '姣忕闄愭祦闃堝��';
+COMMENT ON COLUMN sj_notify_config.description IS '鎻忚堪';
+COMMENT ON COLUMN sj_notify_config.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_notify_config.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_notify_config IS '閫氱煡閰嶇疆';
+
+-- sj_notify_recipient
+CREATE TABLE sj_notify_recipient
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ recipient_name varchar2(64) NULL,
+ notify_type smallint DEFAULT 0 NOT NULL,
+ notify_attribute varchar2(512) NULL,
+ description varchar2(256) DEFAULT '' NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_notify_recipient
+ ADD CONSTRAINT pk_sj_notify_recipient PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_notify_recipient_01 ON sj_notify_recipient (namespace_id);
+
+COMMENT ON COLUMN sj_notify_recipient.id IS '涓婚敭';
+COMMENT ON COLUMN sj_notify_recipient.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_notify_recipient.recipient_name IS '鎺ユ敹浜哄悕绉�';
+COMMENT ON COLUMN sj_notify_recipient.notify_type IS '閫氱煡绫诲瀷 1銆侀拤閽� 2銆侀偖浠� 3銆佷紒涓氬井淇� 4 椋炰功 5 webhook';
+COMMENT ON COLUMN sj_notify_recipient.notify_attribute IS '閰嶇疆灞炴��';
+COMMENT ON COLUMN sj_notify_recipient.description IS '鎻忚堪';
+COMMENT ON COLUMN sj_notify_recipient.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_notify_recipient.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_notify_recipient IS '鍛婅閫氱煡鎺ユ敹浜�';
+
+-- sj_retry_dead_letter
+CREATE TABLE sj_retry_dead_letter
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) NULL,
+ group_id number NOT NULL,
+ scene_name varchar2(64) NULL,
+ scene_id number NOT NULL,
+ idempotent_id varchar2(64) NULL,
+ biz_no varchar2(64) DEFAULT '' NULL,
+ executor_name varchar2(512) DEFAULT '' NULL,
+ serializer_name varchar2(32) DEFAULT 'jackson' NULL,
+ args_str clob NULL,
+ ext_attrs clob NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_retry_dead_letter
+ ADD CONSTRAINT pk_sj_retry_dead_letter PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_retry_dead_letter_01 ON sj_retry_dead_letter (namespace_id, group_name, scene_name);
+CREATE INDEX idx_sj_retry_dead_letter_02 ON sj_retry_dead_letter (idempotent_id);
+CREATE INDEX idx_sj_retry_dead_letter_03 ON sj_retry_dead_letter (biz_no);
+CREATE INDEX idx_sj_retry_dead_letter_04 ON sj_retry_dead_letter (create_dt);
+
+COMMENT ON COLUMN sj_retry_dead_letter.id IS '涓婚敭';
+COMMENT ON COLUMN sj_retry_dead_letter.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_retry_dead_letter.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_retry_dead_letter.group_id IS '缁処d';
+COMMENT ON COLUMN sj_retry_dead_letter.scene_name IS '鍦烘櫙鍚嶇О';
+COMMENT ON COLUMN sj_retry_dead_letter.scene_id IS '鍦烘櫙ID';
+COMMENT ON COLUMN sj_retry_dead_letter.idempotent_id IS '骞傜瓑id';
+COMMENT ON COLUMN sj_retry_dead_letter.biz_no IS '涓氬姟缂栧彿';
+COMMENT ON COLUMN sj_retry_dead_letter.executor_name IS '鎵ц鍣ㄥ悕绉�';
+COMMENT ON COLUMN sj_retry_dead_letter.serializer_name IS '鎵ц鏂规硶鍙傛暟搴忓垪鍖栧櫒鍚嶇О';
+COMMENT ON COLUMN sj_retry_dead_letter.args_str IS '鎵ц鏂规硶鍙傛暟';
+COMMENT ON COLUMN sj_retry_dead_letter.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_retry_dead_letter.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON TABLE sj_retry_dead_letter IS '姝讳俊闃熷垪琛�';
+
+-- sj_retry
+CREATE TABLE sj_retry
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) NULL,
+ group_id number NOT NULL,
+ scene_name varchar2(64) NULL,
+ scene_id number NOT NULL,
+ idempotent_id varchar2(64) NULL,
+ biz_no varchar2(64) DEFAULT '' NULL,
+ executor_name varchar2(512) DEFAULT '' NULL,
+ args_str clob NULL,
+ ext_attrs clob NULL,
+ serializer_name varchar2(32) DEFAULT 'jackson' NULL,
+ next_trigger_at number NOT NULL,
+ retry_count number DEFAULT 0 NOT NULL,
+ retry_status smallint DEFAULT 0 NOT NULL,
+ task_type smallint DEFAULT 1 NOT NULL,
+ bucket_index number DEFAULT 0 NOT NULL,
+ parent_id number DEFAULT 0 NOT NULL,
+ deleted number DEFAULT 0 NOT NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_retry
+ ADD CONSTRAINT pk_sj_retry PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX uk_sj_retry_01 ON sj_retry (scene_id, task_type, idempotent_id, deleted);
+
+CREATE INDEX idx_sj_retry_01 ON sj_retry (biz_no);
+CREATE INDEX idx_sj_retry_02 ON sj_retry (idempotent_id);
+CREATE INDEX idx_sj_retry_03 ON sj_retry (retry_status, bucket_index);
+CREATE INDEX idx_sj_retry_04 ON sj_retry (parent_id);
+CREATE INDEX idx_sj_retry_05 ON sj_retry (create_dt);
+
+COMMENT ON COLUMN sj_retry.id IS '涓婚敭';
+COMMENT ON COLUMN sj_retry.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_retry.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_retry.group_id IS '缁処d';
+COMMENT ON COLUMN sj_retry.scene_name IS '鍦烘櫙鍚嶇О';
+COMMENT ON COLUMN sj_retry.scene_id IS '鍦烘櫙ID';
+COMMENT ON COLUMN sj_retry.idempotent_id IS '骞傜瓑id';
+COMMENT ON COLUMN sj_retry.biz_no IS '涓氬姟缂栧彿';
+COMMENT ON COLUMN sj_retry.executor_name IS '鎵ц鍣ㄥ悕绉�';
+COMMENT ON COLUMN sj_retry.args_str IS '鎵ц鏂规硶鍙傛暟';
+COMMENT ON COLUMN sj_retry.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_retry.serializer_name IS '鎵ц鏂规硶鍙傛暟搴忓垪鍖栧櫒鍚嶇О';
+COMMENT ON COLUMN sj_retry.next_trigger_at IS '涓嬫瑙﹀彂鏃堕棿';
+COMMENT ON COLUMN sj_retry.retry_count IS '閲嶈瘯娆℃暟';
+COMMENT ON COLUMN sj_retry.retry_status IS '閲嶈瘯鐘舵�� 0銆侀噸璇曚腑 1銆佹垚鍔� 2銆佹渶澶ч噸璇曟鏁�';
+COMMENT ON COLUMN sj_retry.task_type IS '浠诲姟绫诲瀷 1銆侀噸璇曟暟鎹� 2銆佸洖璋冩暟鎹�';
+COMMENT ON COLUMN sj_retry.bucket_index IS 'bucket';
+COMMENT ON COLUMN sj_retry.parent_id IS '鐖惰妭鐐筰d';
+COMMENT ON COLUMN sj_retry.deleted IS '閫昏緫鍒犻櫎';
+COMMENT ON COLUMN sj_retry.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_retry.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_retry IS '閲嶈瘯淇℃伅琛�';
+
+-- sj_retry_task
+CREATE TABLE sj_retry_task
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) NULL,
+ scene_name varchar2(64) NULL,
+ retry_id number NOT NULL,
+ ext_attrs clob NULL,
+ task_status smallint DEFAULT 1 NOT NULL,
+ task_type smallint DEFAULT 1 NOT NULL,
+ operation_reason smallint DEFAULT 0 NOT NULL,
+ client_info varchar2(128) DEFAULT NULL NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_retry_task
+ ADD CONSTRAINT pk_sj_retry_task PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_retry_task_01 ON sj_retry_task (namespace_id, group_name, scene_name);
+CREATE INDEX idx_sj_retry_task_02 ON sj_retry_task (task_status);
+CREATE INDEX idx_sj_retry_task_03 ON sj_retry_task (create_dt);
+CREATE INDEX idx_sj_retry_task_04 ON sj_retry_task (retry_id);
+
+COMMENT ON COLUMN sj_retry_task.id IS '涓婚敭';
+COMMENT ON COLUMN sj_retry_task.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_retry_task.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_retry_task.scene_name IS '鍦烘櫙鍚嶇О';
+COMMENT ON COLUMN sj_retry_task.retry_id IS '閲嶈瘯淇℃伅Id';
+COMMENT ON COLUMN sj_retry_task.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_retry_task.task_status IS '閲嶈瘯鐘舵��';
+COMMENT ON COLUMN sj_retry_task.task_type IS '浠诲姟绫诲瀷 1銆侀噸璇曟暟鎹� 2銆佸洖璋冩暟鎹�';
+COMMENT ON COLUMN sj_retry_task.operation_reason IS '鎿嶄綔鍘熷洜';
+COMMENT ON COLUMN sj_retry_task.client_info IS '瀹㈡埛绔湴鍧� clientId#ip:port';
+COMMENT ON COLUMN sj_retry_task.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_retry_task.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_retry_task IS '閲嶈瘯浠诲姟琛�';
+
+-- sj_retry_task_log_message
+CREATE TABLE sj_retry_task_log_message
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) NULL,
+ retry_id number NOT NULL,
+ retry_task_id number NOT NULL,
+ message clob NULL,
+ log_num number DEFAULT 1 NOT NULL,
+ real_time number DEFAULT 0 NOT NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_retry_task_log_message
+ ADD CONSTRAINT pk_sj_retry_task_log_message PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_retry_task_log_message_01 ON sj_retry_task_log_message (namespace_id, group_name, retry_task_id);
+CREATE INDEX idx_sj_retry_task_log_message_02 ON sj_retry_task_log_message (create_dt);
+
+COMMENT ON COLUMN sj_retry_task_log_message.id IS '涓婚敭';
+COMMENT ON COLUMN sj_retry_task_log_message.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_retry_task_log_message.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_retry_task_log_message.retry_id IS '閲嶈瘯淇℃伅Id';
+COMMENT ON COLUMN sj_retry_task_log_message.retry_task_id IS '閲嶈瘯浠诲姟Id';
+COMMENT ON COLUMN sj_retry_task_log_message.message IS '寮傚父淇℃伅';
+COMMENT ON COLUMN sj_retry_task_log_message.log_num IS '鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_retry_task_log_message.real_time IS '涓婃姤鏃堕棿';
+COMMENT ON COLUMN sj_retry_task_log_message.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON TABLE sj_retry_task_log_message IS '浠诲姟璋冨害鏃ュ織淇℃伅璁板綍琛�';
+
+-- sj_retry_scene_config
+CREATE TABLE sj_retry_scene_config
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ scene_name varchar2(64) NULL,
+ group_name varchar2(64) NULL,
+ scene_status smallint DEFAULT 0 NOT NULL,
+ max_retry_count number DEFAULT 5 NOT NULL,
+ back_off smallint DEFAULT 1 NOT NULL,
+ trigger_interval varchar2(16) DEFAULT '' NULL,
+ notify_ids varchar2(128) DEFAULT '' NULL,
+ deadline_request number DEFAULT 60000 NOT NULL,
+ executor_timeout number DEFAULT 5 NOT NULL,
+ route_key smallint DEFAULT 4 NOT NULL,
+ block_strategy smallint DEFAULT 1 NOT NULL,
+ cb_status smallint DEFAULT 0 NOT NULL,
+ cb_trigger_type smallint DEFAULT 1 NOT NULL,
+ cb_max_count number DEFAULT 16 NOT NULL,
+ cb_trigger_interval varchar2(16) DEFAULT '' NULL,
+ owner_id number DEFAULT NULL NULL,
+ labels varchar2(512) DEFAULT '' NULL,
+ description varchar2(256) DEFAULT '' NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_retry_scene_config
+ ADD CONSTRAINT pk_sj_retry_scene_config PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX uk_sj_retry_scene_config_01 ON sj_retry_scene_config (namespace_id, group_name, scene_name);
+
+COMMENT ON COLUMN sj_retry_scene_config.id IS '涓婚敭';
+COMMENT ON COLUMN sj_retry_scene_config.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_retry_scene_config.scene_name IS '鍦烘櫙鍚嶇О';
+COMMENT ON COLUMN sj_retry_scene_config.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_retry_scene_config.scene_status IS '缁勭姸鎬� 0銆佹湭鍚敤 1銆佸惎鐢�';
+COMMENT ON COLUMN sj_retry_scene_config.max_retry_count IS '鏈�澶ч噸璇曟鏁�';
+COMMENT ON COLUMN sj_retry_scene_config.back_off IS '1銆侀粯璁ょ瓑绾� 2銆佸浐瀹氶棿闅旀椂闂� 3銆丆RON 琛ㄨ揪寮�';
+COMMENT ON COLUMN sj_retry_scene_config.trigger_interval IS '闂撮殧鏃堕暱';
+COMMENT ON COLUMN sj_retry_scene_config.notify_ids IS '閫氱煡鍛婅鍦烘櫙閰嶇疆id鍒楄〃';
+COMMENT ON COLUMN sj_retry_scene_config.deadline_request IS 'Deadline Request 璋冪敤閾捐秴鏃� 鍗曚綅姣';
+COMMENT ON COLUMN sj_retry_scene_config.executor_timeout IS '浠诲姟鎵ц瓒呮椂鏃堕棿锛屽崟浣嶇';
+COMMENT ON COLUMN sj_retry_scene_config.route_key IS '璺敱绛栫暐';
+COMMENT ON COLUMN sj_retry_scene_config.block_strategy IS '闃诲绛栫暐 1銆佷涪寮� 2銆佽鐩� 3銆佸苟琛�';
+COMMENT ON COLUMN sj_retry_scene_config.cb_status IS '鍥炶皟鐘舵�� 0銆佷笉寮�鍚� 1銆佸紑鍚�';
+COMMENT ON COLUMN sj_retry_scene_config.cb_trigger_type IS '1銆侀粯璁ょ瓑绾� 2銆佸浐瀹氶棿闅旀椂闂� 3銆丆RON 琛ㄨ揪寮�';
+COMMENT ON COLUMN sj_retry_scene_config.cb_max_count IS '鍥炶皟鐨勬渶澶ф墽琛屾鏁�';
+COMMENT ON COLUMN sj_retry_scene_config.cb_trigger_interval IS '鍥炶皟鐨勬渶澶ф墽琛屾鏁�';
+COMMENT ON COLUMN sj_retry_scene_config.owner_id IS '璐熻矗浜篿d';
+COMMENT ON COLUMN sj_retry_scene_config.labels IS '鏍囩';
+COMMENT ON COLUMN sj_retry_scene_config.description IS '鎻忚堪';
+COMMENT ON COLUMN sj_retry_scene_config.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_retry_scene_config.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_retry_scene_config IS '鍦烘櫙閰嶇疆';
+
+-- sj_server_node
+CREATE TABLE sj_server_node
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) NULL,
+ host_id varchar2(64) NULL,
+ host_ip varchar2(64) NULL,
+ host_port number NOT NULL,
+ expire_at date NOT NULL,
+ node_type smallint NOT NULL,
+ ext_attrs varchar2(256) DEFAULT '' NULL,
+ labels varchar2(512) DEFAULT '' NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_server_node
+ ADD CONSTRAINT pk_sj_server_node PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX uk_sj_server_node_01 ON sj_server_node (host_id, host_ip);
+
+CREATE INDEX idx_sj_server_node_01 ON sj_server_node (namespace_id, group_name);
+CREATE INDEX idx_sj_server_node_02 ON sj_server_node (expire_at, node_type);
+
+COMMENT ON COLUMN sj_server_node.id IS '涓婚敭';
+COMMENT ON COLUMN sj_server_node.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_server_node.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_server_node.host_id IS '涓绘満id';
+COMMENT ON COLUMN sj_server_node.host_ip IS '鏈哄櫒ip';
+COMMENT ON COLUMN sj_server_node.host_port IS '鏈哄櫒绔彛';
+COMMENT ON COLUMN sj_server_node.expire_at IS '杩囨湡鏃堕棿';
+COMMENT ON COLUMN sj_server_node.node_type IS '鑺傜偣绫诲瀷 1銆佸鎴风 2銆佹槸鏈嶅姟绔�';
+COMMENT ON COLUMN sj_server_node.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_server_node.labels IS '鏍囩';
+COMMENT ON COLUMN sj_server_node.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_server_node.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_server_node IS '鏈嶅姟鍣ㄨ妭鐐�';
+
+-- sj_distributed_lock
+CREATE TABLE sj_distributed_lock
+(
+ name varchar2(64) NOT NULL,
+ lock_until timestamp(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL,
+ locked_at timestamp(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL,
+ locked_by varchar2(255) NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_distributed_lock
+ ADD CONSTRAINT pk_sj_distributed_lock PRIMARY KEY (name);
+
+COMMENT ON COLUMN sj_distributed_lock.name IS '閿佸悕绉�';
+COMMENT ON COLUMN sj_distributed_lock.lock_until IS '閿佸畾鏃堕暱';
+COMMENT ON COLUMN sj_distributed_lock.locked_at IS '閿佸畾鏃堕棿';
+COMMENT ON COLUMN sj_distributed_lock.locked_by IS '閿佸畾鑰�';
+COMMENT ON COLUMN sj_distributed_lock.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_distributed_lock.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_distributed_lock IS '閿佸畾琛�';
+
+-- sj_system_user
+CREATE TABLE sj_system_user
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ username varchar2(64) NULL,
+ password varchar2(128) NULL,
+ role smallint DEFAULT 0 NOT NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_system_user
+ ADD CONSTRAINT pk_sj_system_user PRIMARY KEY (id);
+
+COMMENT ON COLUMN sj_system_user.id IS '涓婚敭';
+COMMENT ON COLUMN sj_system_user.username IS '璐﹀彿';
+COMMENT ON COLUMN sj_system_user.password IS '瀵嗙爜';
+COMMENT ON COLUMN sj_system_user.role IS '瑙掕壊锛�1-鏅�氱敤鎴枫��2-绠$悊鍛�';
+COMMENT ON COLUMN sj_system_user.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_system_user.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_system_user IS '绯荤粺鐢ㄦ埛琛�';
+
+INSERT INTO sj_system_user (username, password, role)
+VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2);
+
+-- sj_system_user_permission
+CREATE TABLE sj_system_user_permission
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ group_name varchar2(64) NULL,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ system_user_id number NOT NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_system_user_permission
+ ADD CONSTRAINT pk_sj_system_user_permission PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX uk_sj_system_user_permission_01 ON sj_system_user_permission (namespace_id, group_name, system_user_id);
+
+COMMENT ON COLUMN sj_system_user_permission.id IS '涓婚敭';
+COMMENT ON COLUMN sj_system_user_permission.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_system_user_permission.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_system_user_permission.system_user_id IS '绯荤粺鐢ㄦ埛id';
+COMMENT ON COLUMN sj_system_user_permission.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_system_user_permission.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_system_user_permission IS '绯荤粺鐢ㄦ埛鏉冮檺琛�';
+
+-- sj_job
+CREATE TABLE sj_job
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) NULL,
+ job_name varchar2(64) NULL,
+ args_str clob DEFAULT NULL NULL,
+ args_type smallint DEFAULT 1 NOT NULL,
+ next_trigger_at number NOT NULL,
+ job_status smallint DEFAULT 1 NOT NULL,
+ task_type smallint DEFAULT 1 NOT NULL,
+ route_key smallint DEFAULT 4 NOT NULL,
+ executor_type smallint DEFAULT 1 NOT NULL,
+ executor_info varchar2(255) DEFAULT NULL NULL,
+ trigger_type smallint NOT NULL,
+ trigger_interval varchar2(255) NULL,
+ block_strategy smallint DEFAULT 1 NOT NULL,
+ executor_timeout number DEFAULT 0 NOT NULL,
+ max_retry_times number DEFAULT 0 NOT NULL,
+ parallel_num number DEFAULT 1 NOT NULL,
+ retry_interval number DEFAULT 0 NOT NULL,
+ bucket_index number DEFAULT 0 NOT NULL,
+ resident smallint DEFAULT 0 NOT NULL,
+ notify_ids varchar2(128) DEFAULT '' NULL,
+ owner_id number DEFAULT NULL NULL,
+ labels varchar2(512) DEFAULT '' NULL,
+ description varchar2(256) DEFAULT '' NULL,
+ ext_attrs varchar2(256) DEFAULT '' NULL,
+ deleted smallint DEFAULT 0 NOT NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_job
+ ADD CONSTRAINT pk_sj_job PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_job_01 ON sj_job (namespace_id, group_name);
+CREATE INDEX idx_sj_job_02 ON sj_job (job_status, bucket_index);
+CREATE INDEX idx_sj_job_03 ON sj_job (create_dt);
+
+COMMENT ON COLUMN sj_job.id IS '涓婚敭';
+COMMENT ON COLUMN sj_job.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_job.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_job.job_name IS '鍚嶇О';
+COMMENT ON COLUMN sj_job.args_str IS '鎵ц鏂规硶鍙傛暟';
+COMMENT ON COLUMN sj_job.args_type IS '鍙傛暟绫诲瀷 ';
+COMMENT ON COLUMN sj_job.next_trigger_at IS '涓嬫瑙﹀彂鏃堕棿';
+COMMENT ON COLUMN sj_job.job_status IS '浠诲姟鐘舵�� 0銆佸叧闂��1銆佸紑鍚�';
+COMMENT ON COLUMN sj_job.task_type IS '浠诲姟绫诲瀷 1銆侀泦缇� 2銆佸箍鎾� 3銆佸垏鐗�';
+COMMENT ON COLUMN sj_job.route_key IS '璺敱绛栫暐';
+COMMENT ON COLUMN sj_job.executor_type IS '鎵ц鍣ㄧ被鍨�';
+COMMENT ON COLUMN sj_job.executor_info IS '鎵ц鍣ㄥ悕绉�';
+COMMENT ON COLUMN sj_job.trigger_type IS '瑙﹀彂绫诲瀷 1.CRON 琛ㄨ揪寮� 2. 鍥哄畾鏃堕棿';
+COMMENT ON COLUMN sj_job.trigger_interval IS '闂撮殧鏃堕暱';
+COMMENT ON COLUMN sj_job.block_strategy IS '闃诲绛栫暐 1銆佷涪寮� 2銆佽鐩� 3銆佸苟琛� 4銆佹仮澶�';
+COMMENT ON COLUMN sj_job.executor_timeout IS '浠诲姟鎵ц瓒呮椂鏃堕棿锛屽崟浣嶇';
+COMMENT ON COLUMN sj_job.max_retry_times IS '鏈�澶ч噸璇曟鏁�';
+COMMENT ON COLUMN sj_job.parallel_num IS '骞惰鏁�';
+COMMENT ON COLUMN sj_job.retry_interval IS '閲嶈瘯闂撮殧 ( s)';
+COMMENT ON COLUMN sj_job.bucket_index IS 'bucket';
+COMMENT ON COLUMN sj_job.resident IS '鏄惁鏄父椹讳换鍔�';
+COMMENT ON COLUMN sj_job.notify_ids IS '閫氱煡鍛婅鍦烘櫙閰嶇疆id鍒楄〃';
+COMMENT ON COLUMN sj_job.owner_id IS '璐熻矗浜篿d';
+COMMENT ON COLUMN sj_job.labels IS '鏍囩';
+COMMENT ON COLUMN sj_job.description IS '鎻忚堪';
+COMMENT ON COLUMN sj_job.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_job.deleted IS '閫昏緫鍒犻櫎 1銆佸垹闄�';
+COMMENT ON COLUMN sj_job.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_job.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_job IS '浠诲姟淇℃伅';
+
+INSERT INTO sj_job(namespace_id, group_name, job_name, args_str, args_type, next_trigger_at, job_status, task_type, route_key, executor_type, executor_info, trigger_type, trigger_interval, block_strategy,executor_timeout, max_retry_times, parallel_num, retry_interval, bucket_index, resident, notify_ids, owner_id, labels, description, ext_attrs, deleted, create_dt, update_dt) VALUES ('dev', 'ruoyi_group', 'demo-job', NULL, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '','', '', 0, sysdate, sysdate);
+
+-- sj_job_log_message
+CREATE TABLE sj_job_log_message
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) NULL,
+ job_id number NOT NULL,
+ task_batch_id number NOT NULL,
+ task_id number NOT NULL,
+ message clob NULL,
+ log_num number DEFAULT 1 NOT NULL,
+ real_time number DEFAULT 0 NOT NULL,
+ ext_attrs varchar2(256) DEFAULT '' NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_job_log_message
+ ADD CONSTRAINT pk_sj_job_log_message PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_job_log_message_01 ON sj_job_log_message (task_batch_id, task_id);
+CREATE INDEX idx_sj_job_log_message_02 ON sj_job_log_message (create_dt);
+CREATE INDEX idx_sj_job_log_message_03 ON sj_job_log_message (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_job_log_message.id IS '涓婚敭';
+COMMENT ON COLUMN sj_job_log_message.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_job_log_message.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_job_log_message.job_id IS '浠诲姟淇℃伅id';
+COMMENT ON COLUMN sj_job_log_message.task_batch_id IS '浠诲姟鎵规id';
+COMMENT ON COLUMN sj_job_log_message.task_id IS '璋冨害浠诲姟id';
+COMMENT ON COLUMN sj_job_log_message.message IS '璋冨害淇℃伅';
+COMMENT ON COLUMN sj_job_log_message.log_num IS '鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_job_log_message.real_time IS '涓婃姤鏃堕棿';
+COMMENT ON COLUMN sj_job_log_message.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_job_log_message.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON TABLE sj_job_log_message IS '璋冨害鏃ュ織';
+
+-- sj_job_task
+CREATE TABLE sj_job_task
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) NULL,
+ job_id number NOT NULL,
+ task_batch_id number NOT NULL,
+ parent_id number DEFAULT 0 NOT NULL,
+ task_status smallint DEFAULT 0 NOT NULL,
+ retry_count number DEFAULT 0 NOT NULL,
+ mr_stage smallint DEFAULT NULL NULL,
+ leaf smallint DEFAULT '1' NOT NULL,
+ task_name varchar2(255) DEFAULT '' NULL,
+ client_info varchar2(128) DEFAULT NULL NULL,
+ wf_context clob DEFAULT NULL NULL,
+ result_message clob NULL,
+ args_str clob DEFAULT NULL NULL,
+ args_type smallint DEFAULT 1 NOT NULL,
+ ext_attrs varchar2(256) DEFAULT '' NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_job_task
+ ADD CONSTRAINT pk_sj_job_task PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_job_task_01 ON sj_job_task (task_batch_id, task_status);
+CREATE INDEX idx_sj_job_task_02 ON sj_job_task (create_dt);
+CREATE INDEX idx_sj_job_task_03 ON sj_job_task (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_job_task.id IS '涓婚敭';
+COMMENT ON COLUMN sj_job_task.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_job_task.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_job_task.job_id IS '浠诲姟淇℃伅id';
+COMMENT ON COLUMN sj_job_task.task_batch_id IS '璋冨害浠诲姟id';
+COMMENT ON COLUMN sj_job_task.parent_id IS '鐖舵墽琛屽櫒id';
+COMMENT ON COLUMN sj_job_task.task_status IS '鎵ц鐨勭姸鎬� 0銆佸け璐� 1銆佹垚鍔�';
+COMMENT ON COLUMN sj_job_task.retry_count IS '閲嶈瘯娆℃暟';
+COMMENT ON COLUMN sj_job_task.mr_stage IS '鍔ㄦ�佸垎鐗囨墍澶勯樁娈� 1:map 2:reduce 3:mergeReduce';
+COMMENT ON COLUMN sj_job_task.leaf IS '鍙跺瓙鑺傜偣';
+COMMENT ON COLUMN sj_job_task.task_name IS '浠诲姟鍚嶇О';
+COMMENT ON COLUMN sj_job_task.client_info IS '瀹㈡埛绔湴鍧� clientId#ip:port';
+COMMENT ON COLUMN sj_job_task.wf_context IS '宸ヤ綔娴佸叏灞�涓婁笅鏂�';
+COMMENT ON COLUMN sj_job_task.result_message IS '鎵ц缁撴灉';
+COMMENT ON COLUMN sj_job_task.args_str IS '鎵ц鏂规硶鍙傛暟';
+COMMENT ON COLUMN sj_job_task.args_type IS '鍙傛暟绫诲瀷 ';
+COMMENT ON COLUMN sj_job_task.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_job_task.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_job_task.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_job_task IS '浠诲姟瀹炰緥';
+
+-- sj_job_task_batch
+CREATE TABLE sj_job_task_batch
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) NULL,
+ job_id number NOT NULL,
+ workflow_node_id number DEFAULT 0 NOT NULL,
+ parent_workflow_node_id number DEFAULT 0 NOT NULL,
+ workflow_task_batch_id number DEFAULT 0 NOT NULL,
+ task_batch_status smallint DEFAULT 0 NOT NULL,
+ operation_reason smallint DEFAULT 0 NOT NULL,
+ execution_at number DEFAULT 0 NOT NULL,
+ system_task_type smallint DEFAULT 3 NOT NULL,
+ parent_id varchar2(64) DEFAULT '' NULL,
+ ext_attrs varchar2(256) DEFAULT '' NULL,
+ deleted smallint DEFAULT 0 NOT NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_job_task_batch
+ ADD CONSTRAINT pk_sj_job_task_batch PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_job_task_batch_01 ON sj_job_task_batch (job_id, task_batch_status);
+CREATE INDEX idx_sj_job_task_batch_02 ON sj_job_task_batch (create_dt);
+CREATE INDEX idx_sj_job_task_batch_03 ON sj_job_task_batch (namespace_id, group_name);
+CREATE INDEX idx_sj_job_task_batch_04 ON sj_job_task_batch (workflow_task_batch_id, workflow_node_id);
+
+COMMENT ON COLUMN sj_job_task_batch.id IS '涓婚敭';
+COMMENT ON COLUMN sj_job_task_batch.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_job_task_batch.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_job_task_batch.job_id IS '浠诲姟id';
+COMMENT ON COLUMN sj_job_task_batch.workflow_node_id IS '宸ヤ綔娴佽妭鐐筰d';
+COMMENT ON COLUMN sj_job_task_batch.parent_workflow_node_id IS '宸ヤ綔娴佷换鍔$埗鎵规id';
+COMMENT ON COLUMN sj_job_task_batch.workflow_task_batch_id IS '宸ヤ綔娴佷换鍔℃壒娆d';
+COMMENT ON COLUMN sj_job_task_batch.task_batch_status IS '浠诲姟鎵规鐘舵�� 0銆佸け璐� 1銆佹垚鍔�';
+COMMENT ON COLUMN sj_job_task_batch.operation_reason IS '鎿嶄綔鍘熷洜';
+COMMENT ON COLUMN sj_job_task_batch.execution_at IS '浠诲姟鎵ц鏃堕棿';
+COMMENT ON COLUMN sj_job_task_batch.system_task_type IS '浠诲姟绫诲瀷 3銆丣OB浠诲姟 4銆乄ORKFLOW浠诲姟';
+COMMENT ON COLUMN sj_job_task_batch.parent_id IS '鐖惰妭鐐�';
+COMMENT ON COLUMN sj_job_task_batch.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_job_task_batch.deleted IS '閫昏緫鍒犻櫎 1銆佸垹闄�';
+COMMENT ON COLUMN sj_job_task_batch.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_job_task_batch.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_job_task_batch IS '浠诲姟鎵规';
+
+-- sj_job_summary
+CREATE TABLE sj_job_summary
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) DEFAULT '' NULL,
+ business_id number NOT NULL,
+ system_task_type smallint DEFAULT 3 NOT NULL,
+ trigger_at date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ success_num number DEFAULT 0 NOT NULL,
+ fail_num number DEFAULT 0 NOT NULL,
+ fail_reason varchar2(512) DEFAULT '' NULL,
+ stop_num number DEFAULT 0 NOT NULL,
+ stop_reason varchar2(512) DEFAULT '' NULL,
+ cancel_num number DEFAULT 0 NOT NULL,
+ cancel_reason varchar2(512) DEFAULT '' NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_job_summary
+ ADD CONSTRAINT pk_sj_job_summary PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX uk_sj_job_summary_01 ON sj_job_summary (trigger_at, system_task_type, business_id);
+
+CREATE INDEX idx_sj_job_summary_01 ON sj_job_summary (namespace_id, group_name, business_id);
+
+COMMENT ON COLUMN sj_job_summary.id IS '涓婚敭';
+COMMENT ON COLUMN sj_job_summary.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_job_summary.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_job_summary.business_id IS '涓氬姟id ( job_id鎴杦orkflow_id)';
+COMMENT ON COLUMN sj_job_summary.system_task_type IS '浠诲姟绫诲瀷 3銆丣OB浠诲姟 4銆乄ORKFLOW浠诲姟';
+COMMENT ON COLUMN sj_job_summary.trigger_at IS '缁熻鏃堕棿';
+COMMENT ON COLUMN sj_job_summary.success_num IS '鎵ц鎴愬姛-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_job_summary.fail_num IS '鎵ц澶辫触-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_job_summary.fail_reason IS '澶辫触鍘熷洜';
+COMMENT ON COLUMN sj_job_summary.stop_num IS '鎵ц澶辫触-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_job_summary.stop_reason IS '澶辫触鍘熷洜';
+COMMENT ON COLUMN sj_job_summary.cancel_num IS '鎵ц澶辫触-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_job_summary.cancel_reason IS '澶辫触鍘熷洜';
+COMMENT ON COLUMN sj_job_summary.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_job_summary.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_job_summary IS 'DashBoard_Job';
+
+-- sj_retry_summary
+CREATE TABLE sj_retry_summary
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) DEFAULT '' NULL,
+ scene_name varchar2(64) DEFAULT '' NULL,
+ trigger_at date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ running_num number DEFAULT 0 NOT NULL,
+ finish_num number DEFAULT 0 NOT NULL,
+ max_count_num number DEFAULT 0 NOT NULL,
+ suspend_num number DEFAULT 0 NOT NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_retry_summary
+ ADD CONSTRAINT pk_sj_retry_summary PRIMARY KEY (id);
+
+CREATE UNIQUE INDEX uk_sj_retry_summary_01 ON sj_retry_summary (namespace_id, group_name, scene_name, trigger_at);
+
+CREATE INDEX idx_sj_retry_summary_01 ON sj_retry_summary (trigger_at);
+
+COMMENT ON COLUMN sj_retry_summary.id IS '涓婚敭';
+COMMENT ON COLUMN sj_retry_summary.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_retry_summary.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_retry_summary.scene_name IS '鍦烘櫙鍚嶇О';
+COMMENT ON COLUMN sj_retry_summary.trigger_at IS '缁熻鏃堕棿';
+COMMENT ON COLUMN sj_retry_summary.running_num IS '閲嶈瘯涓�-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_retry_summary.finish_num IS '閲嶈瘯瀹屾垚-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_retry_summary.max_count_num IS '閲嶈瘯鍒拌揪鏈�澶ф鏁�-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_retry_summary.suspend_num IS '鏆傚仠閲嶈瘯-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_retry_summary.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_retry_summary.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_retry_summary IS 'DashBoard_Retry';
+
+-- sj_workflow
+CREATE TABLE sj_workflow
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ workflow_name varchar2(64) NULL,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) NULL,
+ workflow_status smallint DEFAULT 1 NOT NULL,
+ trigger_type smallint NOT NULL,
+ trigger_interval varchar2(255) NULL,
+ next_trigger_at number NOT NULL,
+ block_strategy smallint DEFAULT 1 NOT NULL,
+ executor_timeout number DEFAULT 0 NOT NULL,
+ description varchar2(256) DEFAULT '' NULL,
+ flow_info clob DEFAULT NULL NULL,
+ wf_context clob DEFAULT NULL NULL,
+ notify_ids varchar2(128) DEFAULT '' NULL,
+ bucket_index number DEFAULT 0 NOT NULL,
+ version number NOT NULL,
+ owner_id number DEFAULT NULL NULL,
+ ext_attrs varchar2(256) DEFAULT '' NULL,
+ deleted smallint DEFAULT 0 NOT NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_workflow
+ ADD CONSTRAINT pk_sj_workflow PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_workflow_01 ON sj_workflow (create_dt);
+CREATE INDEX idx_sj_workflow_02 ON sj_workflow (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_workflow.id IS '涓婚敭';
+COMMENT ON COLUMN sj_workflow.workflow_name IS '宸ヤ綔娴佸悕绉�';
+COMMENT ON COLUMN sj_workflow.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_workflow.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_workflow.workflow_status IS '宸ヤ綔娴佺姸鎬� 0銆佸叧闂��1銆佸紑鍚�';
+COMMENT ON COLUMN sj_workflow.trigger_type IS '瑙﹀彂绫诲瀷 1.CRON 琛ㄨ揪寮� 2. 鍥哄畾鏃堕棿';
+COMMENT ON COLUMN sj_workflow.trigger_interval IS '闂撮殧鏃堕暱';
+COMMENT ON COLUMN sj_workflow.next_trigger_at IS '涓嬫瑙﹀彂鏃堕棿';
+COMMENT ON COLUMN sj_workflow.block_strategy IS '闃诲绛栫暐 1銆佷涪寮� 2銆佽鐩� 3銆佸苟琛�';
+COMMENT ON COLUMN sj_workflow.executor_timeout IS '浠诲姟鎵ц瓒呮椂鏃堕棿锛屽崟浣嶇';
+COMMENT ON COLUMN sj_workflow.description IS '鎻忚堪';
+COMMENT ON COLUMN sj_workflow.flow_info IS '娴佺▼淇℃伅';
+COMMENT ON COLUMN sj_workflow.wf_context IS '涓婁笅鏂�';
+COMMENT ON COLUMN sj_workflow.notify_ids IS '閫氱煡鍛婅鍦烘櫙閰嶇疆id鍒楄〃';
+COMMENT ON COLUMN sj_workflow.bucket_index IS 'bucket';
+COMMENT ON COLUMN sj_workflow.version IS '鐗堟湰鍙�';
+COMMENT ON COLUMN sj_workflow.owner_id IS '璐熻矗浜篿d';
+COMMENT ON COLUMN sj_workflow.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_workflow.deleted IS '閫昏緫鍒犻櫎 1銆佸垹闄�';
+COMMENT ON COLUMN sj_workflow.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_workflow.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_workflow IS '宸ヤ綔娴�';
+
+-- sj_workflow_node
+CREATE TABLE sj_workflow_node
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ node_name varchar2(64) NULL,
+ group_name varchar2(64) NULL,
+ job_id number NOT NULL,
+ workflow_id number NOT NULL,
+ node_type smallint DEFAULT 1 NOT NULL,
+ expression_type smallint DEFAULT 0 NOT NULL,
+ fail_strategy smallint DEFAULT 1 NOT NULL,
+ workflow_node_status smallint DEFAULT 1 NOT NULL,
+ priority_level number DEFAULT 1 NOT NULL,
+ node_info clob DEFAULT NULL NULL,
+ version number NOT NULL,
+ ext_attrs varchar2(256) DEFAULT '' NULL,
+ deleted smallint DEFAULT 0 NOT NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_workflow_node
+ ADD CONSTRAINT pk_sj_workflow_node PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_workflow_node_01 ON sj_workflow_node (create_dt);
+CREATE INDEX idx_sj_workflow_node_02 ON sj_workflow_node (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_workflow_node.id IS '涓婚敭';
+COMMENT ON COLUMN sj_workflow_node.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_workflow_node.node_name IS '鑺傜偣鍚嶇О';
+COMMENT ON COLUMN sj_workflow_node.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_workflow_node.job_id IS '浠诲姟淇℃伅id';
+COMMENT ON COLUMN sj_workflow_node.workflow_id IS '宸ヤ綔娴両D';
+COMMENT ON COLUMN sj_workflow_node.node_type IS '1銆佷换鍔¤妭鐐� 2銆佹潯浠惰妭鐐�';
+COMMENT ON COLUMN sj_workflow_node.expression_type IS '1銆丼pEl銆�2銆丄viator 3銆丵L';
+COMMENT ON COLUMN sj_workflow_node.fail_strategy IS '澶辫触绛栫暐 1銆佽烦杩� 2銆侀樆濉�';
+COMMENT ON COLUMN sj_workflow_node.workflow_node_status IS '宸ヤ綔娴佽妭鐐圭姸鎬� 0銆佸叧闂��1銆佸紑鍚�';
+COMMENT ON COLUMN sj_workflow_node.priority_level IS '浼樺厛绾�';
+COMMENT ON COLUMN sj_workflow_node.node_info IS '鑺傜偣淇℃伅 ';
+COMMENT ON COLUMN sj_workflow_node.version IS '鐗堟湰鍙�';
+COMMENT ON COLUMN sj_workflow_node.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_workflow_node.deleted IS '閫昏緫鍒犻櫎 1銆佸垹闄�';
+COMMENT ON COLUMN sj_workflow_node.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_workflow_node.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_workflow_node IS '宸ヤ綔娴佽妭鐐�';
+
+-- sj_workflow_task_batch
+CREATE TABLE sj_workflow_task_batch
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) NULL,
+ workflow_id number NOT NULL,
+ task_batch_status smallint DEFAULT 0 NOT NULL,
+ operation_reason smallint DEFAULT 0 NOT NULL,
+ flow_info clob DEFAULT NULL NULL,
+ wf_context clob DEFAULT NULL NULL,
+ execution_at number DEFAULT 0 NOT NULL,
+ ext_attrs varchar2(256) DEFAULT '' NULL,
+ version number DEFAULT 1 NOT NULL,
+ deleted smallint DEFAULT 0 NOT NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_workflow_task_batch
+ ADD CONSTRAINT pk_sj_workflow_task_batch PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_workflow_task_batch_01 ON sj_workflow_task_batch (workflow_id, task_batch_status);
+CREATE INDEX idx_sj_workflow_task_batch_02 ON sj_workflow_task_batch (create_dt);
+CREATE INDEX idx_sj_workflow_task_batch_03 ON sj_workflow_task_batch (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_workflow_task_batch.id IS '涓婚敭';
+COMMENT ON COLUMN sj_workflow_task_batch.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_workflow_task_batch.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_workflow_task_batch.workflow_id IS '宸ヤ綔娴佷换鍔d';
+COMMENT ON COLUMN sj_workflow_task_batch.task_batch_status IS '浠诲姟鎵规鐘舵�� 0銆佸け璐� 1銆佹垚鍔�';
+COMMENT ON COLUMN sj_workflow_task_batch.operation_reason IS '鎿嶄綔鍘熷洜';
+COMMENT ON COLUMN sj_workflow_task_batch.flow_info IS '娴佺▼淇℃伅';
+COMMENT ON COLUMN sj_workflow_task_batch.wf_context IS '鍏ㄥ眬涓婁笅鏂�';
+COMMENT ON COLUMN sj_workflow_task_batch.execution_at IS '浠诲姟鎵ц鏃堕棿';
+COMMENT ON COLUMN sj_workflow_task_batch.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_workflow_task_batch.version IS '鐗堟湰鍙�';
+COMMENT ON COLUMN sj_workflow_task_batch.deleted IS '閫昏緫鍒犻櫎 1銆佸垹闄�';
+COMMENT ON COLUMN sj_workflow_task_batch.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_workflow_task_batch.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_workflow_task_batch IS '宸ヤ綔娴佹壒娆�';
+
+-- sj_job_executor
+CREATE TABLE sj_job_executor
+(
+ id number GENERATED ALWAYS AS IDENTITY,
+ namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
+ group_name varchar2(64) NULL,
+ executor_info varchar2(256) NULL,
+ executor_type varchar2(3) NULL,
+ create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+ALTER TABLE sj_job_executor
+ ADD CONSTRAINT pk_sj_job_executor PRIMARY KEY (id);
+
+CREATE INDEX idx_sj_job_executor_01 ON sj_job_executor (namespace_id, group_name);
+CREATE INDEX idx_sj_job_executor_02 ON sj_job_executor (create_dt);
+
+COMMENT ON COLUMN sj_job_executor.id IS '涓婚敭';
+COMMENT ON COLUMN sj_job_executor.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_job_executor.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_job_executor.executor_info IS '浠诲姟鎵ц鍣ㄥ悕绉�';
+COMMENT ON COLUMN sj_job_executor.executor_type IS '1:java 2:python 3:go';
+COMMENT ON COLUMN sj_job_executor.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_job_executor.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_job_executor IS '浠诲姟鎵ц鍣ㄤ俊鎭�';
diff --git a/RuoYi-Vue-Plus/script/sql/oracle/oracle_ry_vue_5.X.sql b/RuoYi-Vue-Plus/script/sql/oracle/oracle_ry_vue_5.X.sql
new file mode 100755
index 0000000..3a9cef4
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/oracle/oracle_ry_vue_5.X.sql
@@ -0,0 +1,1407 @@
+-- ----------------------------
+-- 绗笁鏂瑰钩鍙版巿鏉冭〃
+-- ----------------------------
+create table sys_social
+(
+ id number(20) not null,
+ user_id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ auth_id varchar2(255) not null,
+ source varchar2(255) not null,
+ open_id varchar2(255) default null,
+ user_name varchar2(30) not null,
+ nick_name varchar2(30) default '',
+ email varchar2(255) default '',
+ avatar varchar2(500) default '',
+ access_token varchar2(2000) not null,
+ expire_in number(20) default null,
+ refresh_token varchar2(2000) default null,
+ access_code varchar2(255) default null,
+ union_id varchar2(255) default null,
+ scope varchar2(255) default null,
+ token_type varchar2(255) default null,
+ id_token varchar2(2000) default null,
+ mac_algorithm varchar2(255) default null,
+ mac_key varchar2(255) default null,
+ code varchar2(255) default null,
+ oauth_token varchar2(255) default null,
+ oauth_token_secret varchar2(255) default null,
+ create_dept number(20),
+ create_by number(20),
+ create_time date,
+ update_by number(20),
+ update_time date,
+ del_flag char(1) default '0'
+);
+
+alter table sys_social add constraint pk_sys_social primary key (id);
+
+comment on table sys_social is '绀句細鍖栧叧绯昏〃';
+comment on column sys_social.id is '涓婚敭';
+comment on column sys_social.user_id is '鐢ㄦ埛ID';
+comment on column sys_social.tenant_id is '绉熸埛id';
+comment on column sys_social.auth_id is '骞冲彴+骞冲彴鍞竴id';
+comment on column sys_social.source is '鐢ㄦ埛鏉ユ簮';
+comment on column sys_social.open_id is '骞冲彴缂栧彿鍞竴id';
+comment on column sys_social.user_name is '鐧诲綍璐﹀彿';
+comment on column sys_social.nick_name is '鐢ㄦ埛鏄电О';
+comment on column sys_social.email is '鐢ㄦ埛閭';
+comment on column sys_social.avatar is '澶村儚鍦板潃';
+comment on column sys_social.access_token is '鐢ㄦ埛鐨勬巿鏉冧护鐗�';
+comment on column sys_social.expire_in is '鐢ㄦ埛鐨勬巿鏉冧护鐗岀殑鏈夋晥鏈燂紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.refresh_token is '鍒锋柊浠ょ墝锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�';
+comment on column sys_social.access_code is '骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.union_id is '鐢ㄦ埛鐨� unionid';
+comment on column sys_social.scope is '鎺堜簣鐨勬潈闄愶紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.token_type is '涓埆骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.id_token is 'id token锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�';
+comment on column sys_social.mac_algorithm is '灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.mac_key is '灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.code is '鐢ㄦ埛鐨勬巿鏉僣ode锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�';
+comment on column sys_social.oauth_token is 'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.oauth_token_secret is 'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_social.create_by is '鍒涘缓鑰�';
+comment on column sys_social.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_social.update_by is '鏇存柊鑰�';
+comment on column sys_social.update_time is '鏇存柊鏃堕棿';
+comment on column sys_social.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+
+-- ----------------------------
+-- 绉熸埛琛�
+-- ----------------------------
+create table sys_tenant (
+ id number(20) not null,
+ tenant_id varchar2(20) not null,
+ contact_user_name varchar2(20) default '',
+ contact_phone varchar2(20) default '',
+ company_name varchar2(30) default '',
+ license_number varchar2(30) default '',
+ address varchar2(200) default '',
+ intro varchar2(200) default '',
+ domain varchar2(200) default '',
+ remark varchar2(200) default '',
+ package_id number(20) default null,
+ expire_time date default null,
+ account_count number(4) default -1,
+ status char(1) default '0',
+ del_flag char(1) default '0',
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date
+);
+
+alter table sys_tenant add constraint pk_sys_tenant primary key (id);
+
+comment on table sys_tenant is '绉熸埛琛�';
+comment on column sys_tenant.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_tenant.contact_phone is '鑱旂郴鐢佃瘽';
+comment on column sys_tenant.company_name is '浼佷笟鍚嶇О';
+comment on column sys_tenant.company_name is '鑱旂郴浜�';
+comment on column sys_tenant.license_number is '缁熶竴绀句細淇$敤浠g爜';
+comment on column sys_tenant.address is '鍦板潃';
+comment on column sys_tenant.intro is '浼佷笟绠�浠�';
+comment on column sys_tenant.remark is '澶囨敞';
+comment on column sys_tenant.package_id is '绉熸埛濂楅缂栧彿';
+comment on column sys_tenant.expire_time is '杩囨湡鏃堕棿';
+comment on column sys_tenant.account_count is '鐢ㄦ埛鏁伴噺锛�-1涓嶉檺鍒讹級';
+comment on column sys_tenant.status is '绉熸埛鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_tenant.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+comment on column sys_tenant.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_tenant.create_by is '鍒涘缓鑰�';
+comment on column sys_tenant.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_tenant.update_by is '鏇存柊鑰�';
+comment on column sys_tenant.update_time is '鏇存柊鏃堕棿';
+
+-- ----------------------------
+-- 鍒濆鍖�-绉熸埛琛ㄦ暟鎹�
+-- ----------------------------
+
+insert into sys_tenant values(1, '000000', '绠$悊缁�', '15888888888', 'XXX鏈夐檺鍏徃', null, null, '澶氱鎴烽�氱敤鍚庡彴绠$悊绠$悊绯荤粺', null, null, null, null, -1, '0', '0', 103, 1, sysdate, null, null);
+
+
+-- ----------------------------
+-- 绉熸埛濂楅琛�
+-- ----------------------------
+create table sys_tenant_package (
+ package_id number(20) not null,
+ package_name varchar2(20) default '',
+ menu_ids varchar2(3000) default '',
+ remark varchar2(200) default '',
+ menu_check_strictly number(1) default 1,
+ status char(1) default '0',
+ del_flag char(1) default '0',
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date
+);
+
+alter table sys_tenant_package add constraint pk_sys_tenant_package primary key (package_id);
+
+comment on table sys_tenant_package is '绉熸埛濂楅琛�';
+comment on column sys_tenant_package.package_id is '绉熸埛濂楅id';
+comment on column sys_tenant_package.package_name is '濂楅鍚嶇О';
+comment on column sys_tenant_package.menu_ids is '鍏宠仈鑿滃崟id';
+comment on column sys_tenant_package.remark is '澶囨敞';
+comment on column sys_tenant_package.status is '鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_tenant_package.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+comment on column sys_tenant_package.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_tenant_package.create_by is '鍒涘缓鑰�';
+comment on column sys_tenant_package.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_tenant_package.update_by is '鏇存柊鑰�';
+comment on column sys_tenant_package.update_time is '鏇存柊鏃堕棿';
+
+
+-- ----------------------------
+-- 1銆侀儴闂ㄨ〃
+-- ----------------------------
+create table sys_dept (
+ dept_id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ parent_id number(20) default 0,
+ ancestors varchar2(500) default '',
+ dept_name varchar2(30) default '',
+ dept_category varchar2(100) default null,
+ order_num number(4) default 0,
+ leader number(20) default null,
+ phone varchar2(11) default null,
+ email varchar2(50) default null,
+ status char(1) default '0',
+ del_flag char(1) default '0',
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date
+);
+
+alter table sys_dept add constraint pk_sys_dept primary key (dept_id);
+
+comment on table sys_dept is '閮ㄩ棬琛�';
+comment on column sys_dept.dept_id is '閮ㄩ棬id';
+comment on column sys_dept.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_dept.parent_id is '鐖堕儴闂╥d';
+comment on column sys_dept.ancestors is '绁栫骇鍒楄〃';
+comment on column sys_dept.dept_name is '閮ㄩ棬鍚嶇О';
+comment on column sys_dept.dept_category is '閮ㄩ棬绫诲埆缂栫爜';
+comment on column sys_dept.order_num is '鏄剧ず椤哄簭';
+comment on column sys_dept.leader is '璐熻矗浜�';
+comment on column sys_dept.phone is '鑱旂郴鐢佃瘽';
+comment on column sys_dept.email is '閭';
+comment on column sys_dept.status is '閮ㄩ棬鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_dept.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+comment on column sys_dept.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_dept.create_by is '鍒涘缓鑰�';
+comment on column sys_dept.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_dept.update_by is '鏇存柊鑰�';
+comment on column sys_dept.update_time is '鏇存柊鏃堕棿';
+
+-- ----------------------------
+-- 鍒濆鍖�-閮ㄩ棬琛ㄦ暟鎹�
+-- ----------------------------
+
+insert into sys_dept values(100, '000000', 0, '0', 'XXX绉戞妧', null,0, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate, null, null);
+insert into sys_dept values(101, '000000', 100, '0,100', '娣卞湷鎬诲叕鍙�', null,1, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate, null, null);
+insert into sys_dept values(102, '000000', 100, '0,100', '闀挎矙鍒嗗叕鍙�', null,2, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate, null, null);
+insert into sys_dept values(103, '000000', 101, '0,100,101', '鐮斿彂閮ㄩ棬', null,1, 1, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate, null, null);
+insert into sys_dept values(104, '000000', 101, '0,100,101', '甯傚満閮ㄩ棬', null,2, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate, null, null);
+insert into sys_dept values(105, '000000', 101, '0,100,101', '娴嬭瘯閮ㄩ棬', null,3, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate, null, null);
+insert into sys_dept values(106, '000000', 101, '0,100,101', '璐㈠姟閮ㄩ棬', null,4, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate, null, null);
+insert into sys_dept values(107, '000000', 101, '0,100,101', '杩愮淮閮ㄩ棬', null,5, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate, null, null);
+insert into sys_dept values(108, '000000', 102, '0,100,102', '甯傚満閮ㄩ棬', null,1, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate, null, null);
+insert into sys_dept values(109, '000000', 102, '0,100,102', '璐㈠姟閮ㄩ棬', null,2, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate, null, null);
+
+
+-- ----------------------------
+-- 2銆佺敤鎴蜂俊鎭〃
+-- ----------------------------
+create table sys_user (
+ user_id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ dept_id number(20) default null,
+ user_name varchar2(40) not null,
+ nick_name varchar2(40) not null,
+ user_type varchar2(10) default 'sys_user',
+ email varchar2(50) default '',
+ phonenumber varchar2(11) default '',
+ sex char(1) default '0',
+ avatar number(20) default null,
+ password varchar2(100) default '',
+ status char(1) default '0',
+ del_flag char(1) default '0',
+ login_ip varchar2(128) default '',
+ login_date date,
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date,
+ remark varchar2(500) default ''
+);
+
+alter table sys_user add constraint pk_sys_user primary key (user_id);
+
+comment on table sys_user is '鐢ㄦ埛淇℃伅琛�';
+comment on column sys_user.user_id is '鐢ㄦ埛ID';
+comment on column sys_user.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_user.dept_id is '閮ㄩ棬ID';
+comment on column sys_user.user_name is '鐢ㄦ埛璐﹀彿';
+comment on column sys_user.nick_name is '鐢ㄦ埛鏄电О';
+comment on column sys_user.user_type is '鐢ㄦ埛绫诲瀷锛坰ys_user绯荤粺鐢ㄦ埛锛�';
+comment on column sys_user.email is '鐢ㄦ埛閭';
+comment on column sys_user.phonenumber is '鎵嬫満鍙风爜';
+comment on column sys_user.sex is '鐢ㄦ埛鎬у埆锛�0鐢� 1濂� 2鏈煡锛�';
+comment on column sys_user.avatar is '澶村儚璺緞';
+comment on column sys_user.password is '瀵嗙爜';
+comment on column sys_user.status is '璐﹀彿鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_user.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+comment on column sys_user.login_ip is '鏈�鍚庣櫥褰旾P';
+comment on column sys_user.login_date is '鏈�鍚庣櫥褰曟椂闂�';
+comment on column sys_user.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_user.create_by is '鍒涘缓鑰�';
+comment on column sys_user.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_user.update_by is '鏇存柊鑰�';
+comment on column sys_user.update_time is '鏇存柊鏃堕棿';
+comment on column sys_user.remark is '澶囨敞';
+
+-- ----------------------------
+-- 鍒濆鍖�-鐢ㄦ埛淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+insert into sys_user values(1, '000000', 103, 'admin', '鐤媯鐨勭嫯瀛怢i', 'sys_user', 'crazyLionLi@163.com', '15888888888', '1', null, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate, 103, 1, sysdate, null, null, '绠$悊鍛�');
+insert into sys_user values(3, '000000', 108, 'test', '鏈儴闂ㄥ強浠ヤ笅 瀵嗙爜666666', 'sys_user', '', '', '0', null, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', sysdate, 103, 1, sysdate, null, null, '');
+insert into sys_user values(4, '000000', 102, 'test1', '浠呮湰浜� 瀵嗙爜666666', 'sys_user', '', '', '0', null, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', sysdate, 103, 1, sysdate, null, null, '');
+
+-- ----------------------------
+-- 3銆佸矖浣嶄俊鎭〃
+-- ----------------------------
+create table sys_post (
+ post_id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ dept_id number(20) not null,
+ post_code varchar2(64) not null,
+ post_category varchar2(64) default null,
+ post_name varchar2(50) not null,
+ post_sort number(4) not null,
+ status char(1) not null,
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date,
+ remark varchar2(500)
+);
+
+alter table sys_post add constraint pk_sys_post primary key (post_id);
+
+comment on table sys_post is '宀椾綅淇℃伅琛�';
+comment on column sys_post.post_id is '宀椾綅ID';
+comment on column sys_post.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_post.dept_id is '閮ㄩ棬id';
+comment on column sys_post.post_code is '宀椾綅缂栫爜';
+comment on column sys_post.post_category is '宀椾綅绫诲埆缂栫爜';
+comment on column sys_post.post_name is '宀椾綅鍚嶇О';
+comment on column sys_post.post_sort is '鏄剧ず椤哄簭';
+comment on column sys_post.status is '鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_post.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_post.create_by is '鍒涘缓鑰�';
+comment on column sys_post.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_post.update_by is '鏇存柊鑰�';
+comment on column sys_post.update_time is '鏇存柊鏃堕棿';
+comment on column sys_post.remark is '澶囨敞';
+
+-- ----------------------------
+-- 鍒濆鍖�-宀椾綅淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+insert into sys_post values(1, '000000', 103, 'ceo', null, '钁d簨闀�', 1, '0', 103, 1, sysdate, null, null, '');
+insert into sys_post values(2, '000000', 100, 'se', null, '椤圭洰缁忕悊', 2, '0', 103, 1, sysdate, null, null, '');
+insert into sys_post values(3, '000000', 100, 'hr', null, '浜哄姏璧勬簮', 3, '0', 103, 1, sysdate, null, null, '');
+insert into sys_post values(4, '000000', 100, 'user', null, '鏅�氬憳宸�', 4, '0', 103, 1, sysdate, null, null, '');
+
+
+-- ----------------------------
+-- 4銆佽鑹蹭俊鎭〃
+-- ----------------------------
+create table sys_role (
+ role_id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ role_name varchar2(30) not null,
+ role_key varchar2(100) not null,
+ role_sort number(4) not null,
+ data_scope char(1) default '1',
+ menu_check_strictly number(1) default 1,
+ dept_check_strictly number(1) default 1,
+ status char(1) not null,
+ del_flag char(1) default '0',
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date,
+ remark varchar2(500) default null
+);
+
+alter table sys_role add constraint pk_sys_role primary key (role_id);
+
+comment on table sys_role is '瑙掕壊淇℃伅琛�';
+comment on column sys_role.role_id is '瑙掕壊ID';
+comment on column sys_role.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_role.role_name is '瑙掕壊鍚嶇О';
+comment on column sys_role.role_key is '瑙掕壊鏉冮檺瀛楃涓�';
+comment on column sys_role.role_sort is '鏄剧ず椤哄簭';
+comment on column sys_role.data_scope is '鏁版嵁鑼冨洿锛�1锛氬叏閮ㄦ暟鎹潈闄� 2锛氳嚜瀹氭暟鎹潈闄� 3锛氭湰閮ㄩ棬鏁版嵁鏉冮檺 4锛氭湰閮ㄩ棬鍙婁互涓嬫暟鎹潈闄� 5锛氫粎鏈汉鏁版嵁鏉冮檺 6锛氶儴闂ㄥ強浠ヤ笅鎴栨湰浜烘暟鎹潈闄愶級';
+comment on column sys_role.menu_check_strictly is '鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�';
+comment on column sys_role.dept_check_strictly is '閮ㄩ棬鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�';
+comment on column sys_role.status is '瑙掕壊鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_role.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+comment on column sys_role.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_role.create_by is '鍒涘缓鑰�';
+comment on column sys_role.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_role.update_by is '鏇存柊鑰�';
+comment on column sys_role.update_time is '鏇存柊鏃堕棿';
+comment on column sys_role.remark is '澶囨敞';
+
+-- ----------------------------
+-- 鍒濆鍖�-瑙掕壊淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+insert into sys_role values('1', '000000', '瓒呯骇绠$悊鍛�', 'superadmin', 1, 1, 1, 1, '0', '0', 103, 1, sysdate, null, null, '瓒呯骇绠$悊鍛�');
+insert into sys_role values('3', '000000', '鏈儴闂ㄥ強浠ヤ笅', 'test1', 3, 4, 1, 1, '0', '0', 103, 1, sysdate, null, null, null);
+insert into sys_role values('4', '000000', '浠呮湰浜�', 'test2', 4, 5, 1, 1, '0', '0', 103, 1, sysdate, null, null, null);
+
+-- ----------------------------
+-- 5銆佽彍鍗曟潈闄愯〃
+-- ----------------------------
+create table sys_menu (
+ menu_id number(20) not null,
+ menu_name varchar2(50) not null,
+ parent_id number(20) default 0,
+ order_num number(4) default 0,
+ path varchar2(200) default '',
+ component varchar2(255) default null,
+ query_param varchar2(255) default null,
+ is_frame number(1) default 1,
+ is_cache number(1) default 0,
+ menu_type char(1) default '',
+ visible char(1) default 0,
+ status char(1) default 0,
+ perms varchar2(100) default null,
+ icon varchar2(100) default '#',
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date ,
+ remark varchar2(500) default ''
+);
+
+alter table sys_menu add constraint pk_sys_menu primary key (menu_id);
+
+comment on table sys_menu is '鑿滃崟鏉冮檺琛�';
+comment on column sys_menu.menu_id is '鑿滃崟ID';
+comment on column sys_menu.menu_name is '鑿滃崟鍚嶇О';
+comment on column sys_menu.parent_id is '鐖惰彍鍗旾D';
+comment on column sys_menu.order_num is '鏄剧ず椤哄簭';
+comment on column sys_menu.path is '璇锋眰鍦板潃';
+comment on column sys_menu.component is '璺敱鍦板潃';
+comment on column sys_menu.query_param is '璺敱鍙傛暟';
+comment on column sys_menu.is_frame is '鏄惁涓哄閾撅紙0鏄� 1鍚︼級';
+comment on column sys_menu.is_cache is '鏄惁缂撳瓨锛�0缂撳瓨 1涓嶇紦瀛橈級';
+comment on column sys_menu.menu_type is '鑿滃崟绫诲瀷锛圡鐩綍 C鑿滃崟 F鎸夐挳锛�';
+comment on column sys_menu.visible is '鏄剧ず鐘舵�侊紙0鏄剧ず 1闅愯棌锛�';
+comment on column sys_menu.status is '鑿滃崟鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_menu.perms is '鏉冮檺鏍囪瘑';
+comment on column sys_menu.icon is '鑿滃崟鍥炬爣';
+comment on column sys_menu.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_menu.create_by is '鍒涘缓鑰�';
+comment on column sys_menu.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_menu.update_by is '鏇存柊鑰�';
+comment on column sys_menu.update_time is '鏇存柊鏃堕棿';
+comment on column sys_menu.remark is '澶囨敞';
+
+-- ----------------------------
+-- 鍒濆鍖�-鑿滃崟淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+-- 涓�绾ц彍鍗�
+insert into sys_menu values('1', '绯荤粺绠$悊', '0', '1', 'system', null, '', 1, 0, 'M', '0', '0', '', 'system', 103, 1, sysdate, null, null, '绯荤粺绠$悊鐩綍');
+insert into sys_menu values('6', '绉熸埛绠$悊', '0', '2', 'tenant', null, '', 1, 0, 'M', '0', '0', '', 'chart', 103, 1, sysdate, null, null, '绉熸埛绠$悊鐩綍');
+insert into sys_menu values('2', '绯荤粺鐩戞帶', '0', '3', 'monitor', null, '', 1, 0, 'M', '0', '0', '', 'monitor', 103, 1, sysdate, null, null, '绯荤粺鐩戞帶鐩綍');
+insert into sys_menu values('3', '绯荤粺宸ュ叿', '0', '4', 'tool', null, '', 1, 0, 'M', '0', '0', '', 'tool', 103, 1, sysdate, null, null, '绯荤粺宸ュ叿鐩綍');
+insert into sys_menu values('4', 'PLUS瀹樼綉', '0', '5', 'https://gitee.com/dromara/RuoYi-Vue-Plus', null, '', 0, 0, 'M', '0', '0', '', 'guide', 103, 1, sysdate, null, null, 'RuoYi-Vue-Plus瀹樼綉鍦板潃');
+insert into sys_menu values('5', '娴嬭瘯鑿滃崟', '0', '5', 'demo', null, '', 1, 0, 'M', '0', '0', null, 'star', 103, 1, sysdate, null, null, '');
+-- 浜岀骇鑿滃崟
+insert into sys_menu values('100', '鐢ㄦ埛绠$悊', '1', '1', 'user', 'system/user/index', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 103, 1, sysdate, null, null, '鐢ㄦ埛绠$悊鑿滃崟');
+insert into sys_menu values('101', '瑙掕壊绠$悊', '1', '2', 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 103, 1, sysdate, null, null, '瑙掕壊绠$悊鑿滃崟');
+insert into sys_menu values('102', '鑿滃崟绠$悊', '1', '3', 'menu', 'system/menu/index', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 103, 1, sysdate, null, null, '鑿滃崟绠$悊鑿滃崟');
+insert into sys_menu values('103', '閮ㄩ棬绠$悊', '1', '4', 'dept', 'system/dept/index', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 103, 1, sysdate, null, null, '閮ㄩ棬绠$悊鑿滃崟');
+insert into sys_menu values('104', '宀椾綅绠$悊', '1', '5', 'post', 'system/post/index', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 103, 1, sysdate, null, null, '宀椾綅绠$悊鑿滃崟');
+insert into sys_menu values('105', '瀛楀吀绠$悊', '1', '6', 'dict', 'system/dict/index', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 103, 1, sysdate, null, null, '瀛楀吀绠$悊鑿滃崟');
+insert into sys_menu values('106', '鍙傛暟璁剧疆', '1', '7', 'config', 'system/config/index', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 103, 1, sysdate, null, null, '鍙傛暟璁剧疆鑿滃崟');
+insert into sys_menu values('107', '閫氱煡鍏憡', '1', '8', 'notice', 'system/notice/index', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 103, 1, sysdate, null, null, '閫氱煡鍏憡鑿滃崟');
+insert into sys_menu values('108', '鏃ュ織绠$悊', '1', '9', 'log', '', '', 1, 0, 'M', '0', '0', '', 'log', 103, 1, sysdate, null, null, '鏃ュ織绠$悊鑿滃崟');
+insert into sys_menu values('109', '鍦ㄧ嚎鐢ㄦ埛', '2', '1', 'online', 'monitor/online/index', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 103, 1, sysdate, null, null, '鍦ㄧ嚎鐢ㄦ埛鑿滃崟');
+insert into sys_menu values('113', '缂撳瓨鐩戞帶', '2', '5', 'cache', 'monitor/cache/index', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 103, 1, sysdate, null, null, '缂撳瓨鐩戞帶鑿滃崟');
+insert into sys_menu values('115', '浠g爜鐢熸垚', '3', '2', 'gen', 'tool/gen/index', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 103, 1, sysdate, null, null, '浠g爜鐢熸垚鑿滃崟');
+insert into sys_menu values('121', '绉熸埛绠$悊', '6', '1', 'tenant', 'system/tenant/index', '', 1, 0, 'C', '0', '0', 'system:tenant:list', 'list', 103, 1, sysdate, null, null, '绉熸埛绠$悊鑿滃崟');
+insert into sys_menu values('122', '绉熸埛濂楅绠$悊', '6', '2', 'tenantPackage', 'system/tenantPackage/index', '', 1, 0, 'C', '0', '0', 'system:tenantPackage:list', 'form', 103, 1, sysdate, null, null, '绉熸埛濂楅绠$悊鑿滃崟');
+insert into sys_menu values('123', '瀹㈡埛绔鐞�', '1', '11', 'client', 'system/client/index', '', 1, 0, 'C', '0', '0', 'system:client:list', 'international', 103, 1, sysdate, null, null, '瀹㈡埛绔鐞嗚彍鍗�');
+insert into sys_menu values('116', '淇敼鐢熸垚閰嶇疆', '3', '2', 'gen-edit/index/:tableId', 'tool/gen/editTable', '', 1, 1, 'C', '1', '0', 'tool:gen:edit', '#', 103, 1, sysdate, null, null, '/tool/gen');
+insert into sys_menu values('130', '鍒嗛厤鐢ㄦ埛', '1', '2', 'role-auth/user/:roleId', 'system/role/authUser', '', 1, 1, 'C', '1', '0', 'system:role:edit', '#', 103, 1, sysdate, null, null, '/system/role');
+insert into sys_menu values('131', '鍒嗛厤瑙掕壊', '1', '1', 'user-auth/role/:userId', 'system/user/authRole', '', 1, 1, 'C', '1', '0', 'system:user:edit', '#', 103, 1, sysdate, null, null, '/system/user');
+insert into sys_menu values('132', '瀛楀吀鏁版嵁', '1', '6', 'dict-data/index/:dictId', 'system/dict/data', '', 1, 1, 'C', '1', '0', 'system:dict:list', '#', 103, 1, sysdate, null, null, '/system/dict');
+insert into sys_menu values('133', '鏂囦欢閰嶇疆绠$悊', '1', '10', 'oss-config/index', 'system/oss/config', '', 1, 1, 'C', '1', '0', 'system:ossConfig:list', '#', 103, 1, sysdate, null, null, '/system/oss');
+
+-- springboot-admin鐩戞帶
+insert into sys_menu values('117', 'Admin鐩戞帶', '2', '5', 'Admin', 'monitor/admin/index', '', 1, 0, 'C', '0', '0', 'monitor:admin:list', 'dashboard', 103, 1, sysdate, null, null, 'Admin鐩戞帶鑿滃崟');
+-- oss鑿滃崟
+insert into sys_menu values('118', '鏂囦欢绠$悊', '1', '10', 'oss', 'system/oss/index', '', 1, 0, 'C', '0', '0', 'system:oss:list', 'upload', 103, 1, sysdate, null, null, '鏂囦欢绠$悊鑿滃崟');
+-- snail-job server鎺у埗鍙�
+insert into sys_menu values('120', '浠诲姟璋冨害涓績', '2', '5', 'snailjob', 'monitor/snailjob/index', '', 1, 0, 'C', '0', '0', 'monitor:snailjob:list', 'job', 103, 1, sysdate, null, null, 'snailjob鎺у埗鍙拌彍鍗�');
+
+-- 涓夌骇鑿滃崟
+insert into sys_menu values('500', '鎿嶄綔鏃ュ織', '108', '1', 'operlog', 'monitor/operlog/index', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 103, 1, sysdate, null, null, '鎿嶄綔鏃ュ織鑿滃崟');
+insert into sys_menu values('501', '鐧诲綍鏃ュ織', '108', '2', 'logininfor', 'monitor/logininfor/index', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 103, 1, sysdate, null, null, '鐧诲綍鏃ュ織鑿滃崟');
+-- 鐢ㄦ埛绠$悊鎸夐挳
+insert into sys_menu values('1001', '鐢ㄦ埛鏌ヨ', '100', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1002', '鐢ㄦ埛鏂板', '100', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1003', '鐢ㄦ埛淇敼', '100', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1004', '鐢ㄦ埛鍒犻櫎', '100', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1005', '鐢ㄦ埛瀵煎嚭', '100', '5', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1006', '鐢ㄦ埛瀵煎叆', '100', '6', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1007', '閲嶇疆瀵嗙爜', '100', '7', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 103, 1, sysdate, null, null, '');
+-- 瑙掕壊绠$悊鎸夐挳
+insert into sys_menu values('1008', '瑙掕壊鏌ヨ', '101', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1009', '瑙掕壊鏂板', '101', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1010', '瑙掕壊淇敼', '101', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1011', '瑙掕壊鍒犻櫎', '101', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1012', '瑙掕壊瀵煎嚭', '101', '5', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 103, 1, sysdate, null, null, '');
+-- 鑿滃崟绠$悊鎸夐挳
+insert into sys_menu values('1013', '鑿滃崟鏌ヨ', '102', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1014', '鑿滃崟鏂板', '102', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1015', '鑿滃崟淇敼', '102', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1016', '鑿滃崟鍒犻櫎', '102', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 103, 1, sysdate, null, null, '');
+-- 閮ㄩ棬绠$悊鎸夐挳
+insert into sys_menu values('1017', '閮ㄩ棬鏌ヨ', '103', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1018', '閮ㄩ棬鏂板', '103', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1019', '閮ㄩ棬淇敼', '103', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1020', '閮ㄩ棬鍒犻櫎', '103', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 103, 1, sysdate, null, null, '');
+-- 宀椾綅绠$悊鎸夐挳
+insert into sys_menu values('1021', '宀椾綅鏌ヨ', '104', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1022', '宀椾綅鏂板', '104', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1023', '宀椾綅淇敼', '104', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1024', '宀椾綅鍒犻櫎', '104', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1025', '宀椾綅瀵煎嚭', '104', '5', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 103, 1, sysdate, null, null, '');
+-- 瀛楀吀绠$悊鎸夐挳
+insert into sys_menu values('1026', '瀛楀吀鏌ヨ', '105', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1027', '瀛楀吀鏂板', '105', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1028', '瀛楀吀淇敼', '105', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1029', '瀛楀吀鍒犻櫎', '105', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1030', '瀛楀吀瀵煎嚭', '105', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 103, 1, sysdate, null, null, '');
+-- 鍙傛暟璁剧疆鎸夐挳
+insert into sys_menu values('1031', '鍙傛暟鏌ヨ', '106', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1032', '鍙傛暟鏂板', '106', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1033', '鍙傛暟淇敼', '106', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1034', '鍙傛暟鍒犻櫎', '106', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1035', '鍙傛暟瀵煎嚭', '106', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 103, 1, sysdate, null, null, '');
+-- 閫氱煡鍏憡鎸夐挳
+insert into sys_menu values('1036', '鍏憡鏌ヨ', '107', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1037', '鍏憡鏂板', '107', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1038', '鍏憡淇敼', '107', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1039', '鍏憡鍒犻櫎', '107', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 103, 1, sysdate, null, null, '');
+-- 鎿嶄綔鏃ュ織鎸夐挳
+insert into sys_menu values('1040', '鎿嶄綔鏌ヨ', '500', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1041', '鎿嶄綔鍒犻櫎', '500', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1042', '鏃ュ織瀵煎嚭', '500', '4', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 103, 1, sysdate, null, null, '');
+-- 鐧诲綍鏃ュ織鎸夐挳
+insert into sys_menu values('1043', '鐧诲綍鏌ヨ', '501', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1044', '鐧诲綍鍒犻櫎', '501', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1045', '鏃ュ織瀵煎嚭', '501', '3', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1050', '璐︽埛瑙i攣', '501', '4', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 103, 1, sysdate, null, null, '');
+-- 鍦ㄧ嚎鐢ㄦ埛鎸夐挳
+insert into sys_menu values('1046', '鍦ㄧ嚎鏌ヨ', '109', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1047', '鎵归噺寮洪��', '109', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1048', '鍗曟潯寮洪��', '109', '3', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 103, 1, sysdate, null, null, '');
+-- 浠g爜鐢熸垚鎸夐挳
+insert into sys_menu values('1055', '鐢熸垚鏌ヨ', '115', '1', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1056', '鐢熸垚淇敼', '115', '2', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1057', '鐢熸垚鍒犻櫎', '115', '3', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1058', '瀵煎叆浠g爜', '115', '2', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1059', '棰勮浠g爜', '115', '4', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1060', '鐢熸垚浠g爜', '115', '5', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 103, 1, sysdate, null, null, '');
+-- oss鐩稿叧鎸夐挳
+insert into sys_menu values('1600', '鏂囦欢鏌ヨ', '118', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:oss:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1601', '鏂囦欢涓婁紶', '118', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:oss:upload', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1602', '鏂囦欢涓嬭浇', '118', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:oss:download', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1603', '鏂囦欢鍒犻櫎', '118', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:oss:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1620', '閰嶇疆鍒楄〃', '118', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:list', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1621', '閰嶇疆娣诲姞', '118', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1622', '閰嶇疆缂栬緫', '118', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1623', '閰嶇疆鍒犻櫎', '118', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:remove', '#', 103, 1, sysdate, null, null, '');
+-- 绉熸埛绠$悊鐩稿叧鎸夐挳
+insert into sys_menu values('1606', '绉熸埛鏌ヨ', '121', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenant:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1607', '绉熸埛鏂板', '121', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenant:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1608', '绉熸埛淇敼', '121', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenant:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1609', '绉熸埛鍒犻櫎', '121', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenant:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1610', '绉熸埛瀵煎嚭', '121', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenant:export', '#', 103, 1, sysdate, null, null, '');
+-- 绉熸埛濂楅绠$悊鐩稿叧鎸夐挳
+insert into sys_menu values('1611', '绉熸埛濂楅鏌ヨ', '122', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1612', '绉熸埛濂楅鏂板', '122', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1613', '绉熸埛濂楅淇敼', '122', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1614', '绉熸埛濂楅鍒犻櫎', '122', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1615', '绉熸埛濂楅瀵煎嚭', '122', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:export', '#', 103, 1, sysdate, null, null, '');
+-- 瀹㈡埛绔鐞嗘寜閽�
+insert into sys_menu values('1061', '瀹㈡埛绔鐞嗘煡璇�', '123', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1062', '瀹㈡埛绔鐞嗘柊澧�', '123', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1063', '瀹㈡埛绔鐞嗕慨鏀�', '123', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1064', '瀹㈡埛绔鐞嗗垹闄�', '123', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1065', '瀹㈡埛绔鐞嗗鍑�', '123', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:export', '#', 103, 1, sysdate, null, null, '');
+-- 娴嬭瘯鑿滃崟
+insert into sys_menu values('1500', '娴嬭瘯鍗曡〃', '5', '1', 'demo', 'demo/demo/index', '', 1, 0, 'C', '0', '0', 'demo:demo:list', '#', 103, 1, sysdate, null, null, '娴嬭瘯鍗曡〃鑿滃崟');
+insert into sys_menu values('1501', '娴嬭瘯鍗曡〃鏌ヨ', '1500', '1', '#', '', '', 1, 0, 'F', '0', '0', 'demo:demo:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1502', '娴嬭瘯鍗曡〃鏂板', '1500', '2', '#', '', '', 1, 0, 'F', '0', '0', 'demo:demo:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1503', '娴嬭瘯鍗曡〃淇敼', '1500', '3', '#', '', '', 1, 0, 'F', '0', '0', 'demo:demo:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1504', '娴嬭瘯鍗曡〃鍒犻櫎', '1500', '4', '#', '', '', 1, 0, 'F', '0', '0', 'demo:demo:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1505', '娴嬭瘯鍗曡〃瀵煎嚭', '1500', '5', '#', '', '', 1, 0, 'F', '0', '0', 'demo:demo:export', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1506', '娴嬭瘯鏍戣〃', '5', '1', 'tree', 'demo/tree/index', '', 1, 0, 'C', '0', '0', 'demo:tree:list', '#', 103, 1, sysdate, null, null, '娴嬭瘯鏍戣〃鑿滃崟');
+insert into sys_menu values('1507', '娴嬭瘯鏍戣〃鏌ヨ', '1506', '1', '#', '', '', 1, 0, 'F', '0', '0', 'demo:tree:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1508', '娴嬭瘯鏍戣〃鏂板', '1506', '2', '#', '', '', 1, 0, 'F', '0', '0', 'demo:tree:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1509', '娴嬭瘯鏍戣〃淇敼', '1506', '3', '#', '', '', 1, 0, 'F', '0', '0', 'demo:tree:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1510', '娴嬭瘯鏍戣〃鍒犻櫎', '1506', '4', '#', '', '', 1, 0, 'F', '0', '0', 'demo:tree:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1511', '娴嬭瘯鏍戣〃瀵煎嚭', '1506', '5', '#', '', '', 1, 0, 'F', '0', '0', 'demo:tree:export', '#', 103, 1, sysdate, null, null, '');
+
+
+-- ----------------------------
+-- 6銆佺敤鎴峰拰瑙掕壊鍏宠仈琛� 鐢ㄦ埛N-1瑙掕壊
+-- ----------------------------
+create table sys_user_role (
+ user_id number(20) not null,
+ role_id number(20) not null
+);
+
+alter table sys_user_role add constraint pk_sys_user_role primary key (user_id, role_id);
+
+comment on table sys_user_role is '鐢ㄦ埛鍜岃鑹插叧鑱旇〃';
+comment on column sys_user_role.user_id is '鐢ㄦ埛ID';
+comment on column sys_user_role.role_id is '瑙掕壊ID';
+
+-- ----------------------------
+-- 鍒濆鍖�-鐢ㄦ埛鍜岃鑹插叧鑱旇〃鏁版嵁
+-- ----------------------------
+insert into sys_user_role values ('1', '1');
+insert into sys_user_role values ('3', '3');
+insert into sys_user_role values ('4', '4');
+
+-- ----------------------------
+-- 7銆佽鑹插拰鑿滃崟鍏宠仈琛� 瑙掕壊1-N鑿滃崟
+-- ----------------------------
+create table sys_role_menu (
+ role_id number(20) not null,
+ menu_id number(20) not null
+);
+
+alter table sys_role_menu add constraint pk_sys_role_menu primary key (role_id, menu_id);
+
+comment on table sys_role_menu is '瑙掕壊鍜岃彍鍗曞叧鑱旇〃';
+comment on column sys_role_menu.role_id is '瑙掕壊ID';
+comment on column sys_role_menu.menu_id is '鑿滃崟ID';
+
+-- ----------------------------
+-- 鍒濆鍖�-瑙掕壊鍜岃彍鍗曞叧鑱旇〃鏁版嵁
+-- ----------------------------
+insert into sys_role_menu values ('3', '1');
+insert into sys_role_menu values ('3', '5');
+insert into sys_role_menu values ('3', '100');
+insert into sys_role_menu values ('3', '101');
+insert into sys_role_menu values ('3', '102');
+insert into sys_role_menu values ('3', '103');
+insert into sys_role_menu values ('3', '104');
+insert into sys_role_menu values ('3', '105');
+insert into sys_role_menu values ('3', '106');
+insert into sys_role_menu values ('3', '107');
+insert into sys_role_menu values ('3', '108');
+insert into sys_role_menu values ('3', '118');
+insert into sys_role_menu values ('3', '123');
+insert into sys_role_menu values ('3', '130');
+insert into sys_role_menu values ('3', '131');
+insert into sys_role_menu values ('3', '132');
+insert into sys_role_menu values ('3', '133');
+insert into sys_role_menu values ('3', '500');
+insert into sys_role_menu values ('3', '501');
+insert into sys_role_menu values ('3', '1001');
+insert into sys_role_menu values ('3', '1002');
+insert into sys_role_menu values ('3', '1003');
+insert into sys_role_menu values ('3', '1004');
+insert into sys_role_menu values ('3', '1005');
+insert into sys_role_menu values ('3', '1006');
+insert into sys_role_menu values ('3', '1007');
+insert into sys_role_menu values ('3', '1008');
+insert into sys_role_menu values ('3', '1009');
+insert into sys_role_menu values ('3', '1010');
+insert into sys_role_menu values ('3', '1011');
+insert into sys_role_menu values ('3', '1012');
+insert into sys_role_menu values ('3', '1013');
+insert into sys_role_menu values ('3', '1014');
+insert into sys_role_menu values ('3', '1015');
+insert into sys_role_menu values ('3', '1016');
+insert into sys_role_menu values ('3', '1017');
+insert into sys_role_menu values ('3', '1018');
+insert into sys_role_menu values ('3', '1019');
+insert into sys_role_menu values ('3', '1020');
+insert into sys_role_menu values ('3', '1021');
+insert into sys_role_menu values ('3', '1022');
+insert into sys_role_menu values ('3', '1023');
+insert into sys_role_menu values ('3', '1024');
+insert into sys_role_menu values ('3', '1025');
+insert into sys_role_menu values ('3', '1026');
+insert into sys_role_menu values ('3', '1027');
+insert into sys_role_menu values ('3', '1028');
+insert into sys_role_menu values ('3', '1029');
+insert into sys_role_menu values ('3', '1030');
+insert into sys_role_menu values ('3', '1031');
+insert into sys_role_menu values ('3', '1032');
+insert into sys_role_menu values ('3', '1033');
+insert into sys_role_menu values ('3', '1034');
+insert into sys_role_menu values ('3', '1035');
+insert into sys_role_menu values ('3', '1036');
+insert into sys_role_menu values ('3', '1037');
+insert into sys_role_menu values ('3', '1038');
+insert into sys_role_menu values ('3', '1039');
+insert into sys_role_menu values ('3', '1040');
+insert into sys_role_menu values ('3', '1041');
+insert into sys_role_menu values ('3', '1042');
+insert into sys_role_menu values ('3', '1043');
+insert into sys_role_menu values ('3', '1044');
+insert into sys_role_menu values ('3', '1045');
+insert into sys_role_menu values ('3', '1050');
+insert into sys_role_menu values ('3', '1061');
+insert into sys_role_menu values ('3', '1062');
+insert into sys_role_menu values ('3', '1063');
+insert into sys_role_menu values ('3', '1064');
+insert into sys_role_menu values ('3', '1065');
+insert into sys_role_menu values ('3', '1500');
+insert into sys_role_menu values ('3', '1501');
+insert into sys_role_menu values ('3', '1502');
+insert into sys_role_menu values ('3', '1503');
+insert into sys_role_menu values ('3', '1504');
+insert into sys_role_menu values ('3', '1505');
+insert into sys_role_menu values ('3', '1506');
+insert into sys_role_menu values ('3', '1507');
+insert into sys_role_menu values ('3', '1508');
+insert into sys_role_menu values ('3', '1509');
+insert into sys_role_menu values ('3', '1510');
+insert into sys_role_menu values ('3', '1511');
+insert into sys_role_menu values ('3', '1600');
+insert into sys_role_menu values ('3', '1601');
+insert into sys_role_menu values ('3', '1602');
+insert into sys_role_menu values ('3', '1603');
+insert into sys_role_menu values ('3', '1620');
+insert into sys_role_menu values ('3', '1621');
+insert into sys_role_menu values ('3', '1622');
+insert into sys_role_menu values ('3', '1623');
+insert into sys_role_menu values ('3', '11616');
+insert into sys_role_menu values ('3', '11618');
+insert into sys_role_menu values ('3', '11619');
+insert into sys_role_menu values ('3', '11622');
+insert into sys_role_menu values ('3', '11623');
+insert into sys_role_menu values ('3', '11629');
+insert into sys_role_menu values ('3', '11632');
+insert into sys_role_menu values ('3', '11633');
+insert into sys_role_menu values ('3', '11638');
+insert into sys_role_menu values ('3', '11639');
+insert into sys_role_menu values ('3', '11640');
+insert into sys_role_menu values ('3', '11641');
+insert into sys_role_menu values ('3', '11642');
+insert into sys_role_menu values ('3', '11643');
+insert into sys_role_menu values ('3', '11701');
+insert into sys_role_menu values ('4', '5');
+insert into sys_role_menu values ('4', '1500');
+insert into sys_role_menu values ('4', '1501');
+insert into sys_role_menu values ('4', '1502');
+insert into sys_role_menu values ('4', '1503');
+insert into sys_role_menu values ('4', '1504');
+insert into sys_role_menu values ('4', '1505');
+insert into sys_role_menu values ('4', '1506');
+insert into sys_role_menu values ('4', '1507');
+insert into sys_role_menu values ('4', '1508');
+insert into sys_role_menu values ('4', '1509');
+insert into sys_role_menu values ('4', '1510');
+insert into sys_role_menu values ('4', '1511');
+
+-- ----------------------------
+-- 8銆佽鑹插拰閮ㄩ棬鍏宠仈琛� 瑙掕壊1-N閮ㄩ棬
+-- ----------------------------
+create table sys_role_dept (
+ role_id number(20) not null,
+ dept_id number(20) not null
+);
+
+alter table sys_role_dept add constraint pk_sys_role_dept primary key (role_id, dept_id);
+
+comment on table sys_role_dept is '瑙掕壊鍜岄儴闂ㄥ叧鑱旇〃';
+comment on column sys_role_dept.role_id is '瑙掕壊ID';
+comment on column sys_role_dept.dept_id is '閮ㄩ棬ID';
+
+
+-- ----------------------------
+-- 9銆佺敤鎴蜂笌宀椾綅鍏宠仈琛� 鐢ㄦ埛1-N宀椾綅
+-- ----------------------------
+create table sys_user_post (
+ user_id number(20) not null,
+ post_id number(20) not null
+);
+
+alter table sys_user_post add constraint pk_sys_user_post primary key (user_id, post_id);
+
+comment on table sys_user_post is '鐢ㄦ埛涓庡矖浣嶅叧鑱旇〃';
+comment on column sys_user_post.user_id is '鐢ㄦ埛ID';
+comment on column sys_user_post.post_id is '宀椾綅ID';
+
+-- ----------------------------
+-- 鍒濆鍖�-鐢ㄦ埛涓庡矖浣嶅叧鑱旇〃鏁版嵁
+-- ----------------------------
+insert into sys_user_post values ('1', '1');
+
+-- ----------------------------
+-- 10銆佹搷浣滄棩蹇楄褰�
+-- ----------------------------
+create table sys_oper_log (
+ oper_id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ title varchar2(50) default '',
+ business_type number(2) default 0,
+ method varchar2(100) default '',
+ request_method varchar2(10) default '',
+ operator_type number(1) default 0,
+ oper_name varchar2(50) default '',
+ dept_name varchar2(50) default '',
+ oper_url varchar2(255) default '',
+ oper_ip varchar2(128) default '',
+ oper_location varchar2(255) default '',
+ oper_param varchar2(4000) default '',
+ json_result varchar2(4000) default '',
+ status number(1) default 0,
+ error_msg varchar2(4000) default '',
+ oper_time date,
+ cost_time number(20) default 0
+);
+
+alter table sys_oper_log add constraint pk_sys_oper_log primary key (oper_id);
+create index idx_sys_oper_log_bt on sys_oper_log (business_type);
+create index idx_sys_oper_log_s on sys_oper_log (status);
+create index idx_sys_oper_log_ot on sys_oper_log (oper_time);
+
+comment on table sys_oper_log is '鎿嶄綔鏃ュ織璁板綍';
+comment on column sys_oper_log.oper_id is '鏃ュ織涓婚敭';
+comment on column sys_oper_log.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_oper_log.title is '妯″潡鏍囬';
+comment on column sys_oper_log.business_type is '涓氬姟绫诲瀷锛�0鍏跺畠 1鏂板 2淇敼 3鍒犻櫎锛�';
+comment on column sys_oper_log.method is '鏂规硶鍚嶇О';
+comment on column sys_oper_log.request_method is '璇锋眰鏂瑰紡';
+comment on column sys_oper_log.operator_type is '鎿嶄綔绫诲埆锛�0鍏跺畠 1鍚庡彴鐢ㄦ埛 2鎵嬫満绔敤鎴凤級';
+comment on column sys_oper_log.oper_name is '鎿嶄綔浜哄憳';
+comment on column sys_oper_log.dept_name is '閮ㄩ棬鍚嶇О';
+comment on column sys_oper_log.oper_url is '璇锋眰URL';
+comment on column sys_oper_log.oper_ip is '涓绘満鍦板潃';
+comment on column sys_oper_log.oper_location is '鎿嶄綔鍦扮偣';
+comment on column sys_oper_log.oper_param is '璇锋眰鍙傛暟';
+comment on column sys_oper_log.json_result is '杩斿洖鍙傛暟';
+comment on column sys_oper_log.status is '鎿嶄綔鐘舵�侊紙0姝e父 1寮傚父锛�';
+comment on column sys_oper_log.error_msg is '閿欒娑堟伅';
+comment on column sys_oper_log.oper_time is '鎿嶄綔鏃堕棿';
+comment on column sys_oper_log.cost_time is '娑堣�楁椂闂�';
+
+
+-- ----------------------------
+-- 11銆佸瓧鍏哥被鍨嬭〃
+-- ----------------------------
+create table sys_dict_type (
+ dict_id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ dict_name varchar2(100) default '',
+ dict_type varchar2(100) default '',
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date,
+ remark varchar2(500) default null
+);
+
+alter table sys_dict_type add constraint pk_sys_dict_type primary key (dict_id);
+create unique index sys_dict_type_index1 on sys_dict_type (tenant_id, dict_type);
+
+comment on table sys_dict_type is '瀛楀吀绫诲瀷琛�';
+comment on column sys_dict_type.dict_id is '瀛楀吀涓婚敭';
+comment on column sys_dict_type.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_dict_type.dict_name is '瀛楀吀鍚嶇О';
+comment on column sys_dict_type.dict_type is '瀛楀吀绫诲瀷';
+comment on column sys_dict_type.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_dict_type.create_by is '鍒涘缓鑰�';
+comment on column sys_dict_type.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_dict_type.update_by is '鏇存柊鑰�';
+comment on column sys_dict_type.update_time is '鏇存柊鏃堕棿';
+comment on column sys_dict_type.remark is '澶囨敞';
+
+insert into sys_dict_type values(1, '000000', '鐢ㄦ埛鎬у埆', 'sys_user_sex', 103, 1, sysdate, null, null, '鐢ㄦ埛鎬у埆鍒楄〃');
+insert into sys_dict_type values(2, '000000', '鑿滃崟鐘舵��', 'sys_show_hide', 103, 1, sysdate, null, null, '鑿滃崟鐘舵�佸垪琛�');
+insert into sys_dict_type values(3, '000000', '绯荤粺寮�鍏�', 'sys_normal_disable', 103, 1, sysdate, null, null, '绯荤粺寮�鍏冲垪琛�');
+insert into sys_dict_type values(6, '000000', '绯荤粺鏄惁', 'sys_yes_no', 103, 1, sysdate, null, null, '绯荤粺鏄惁鍒楄〃');
+insert into sys_dict_type values(7, '000000', '閫氱煡绫诲瀷', 'sys_notice_type', 103, 1, sysdate, null, null, '閫氱煡绫诲瀷鍒楄〃');
+insert into sys_dict_type values(8, '000000', '閫氱煡鐘舵��', 'sys_notice_status', 103, 1, sysdate, null, null, '閫氱煡鐘舵�佸垪琛�');
+insert into sys_dict_type values(9, '000000', '鎿嶄綔绫诲瀷', 'sys_oper_type', 103, 1, sysdate, null, null, '鎿嶄綔绫诲瀷鍒楄〃');
+insert into sys_dict_type values(10, '000000', '绯荤粺鐘舵��', 'sys_common_status', 103, 1, sysdate, null, null, '鐧诲綍鐘舵�佸垪琛�');
+insert into sys_dict_type values(11, '000000', '鎺堟潈绫诲瀷', 'sys_grant_type', 103, 1, sysdate, null, null, '璁よ瘉鎺堟潈绫诲瀷');
+insert into sys_dict_type values(12, '000000', '璁惧绫诲瀷', 'sys_device_type', 103, 1, sysdate, null, null, '瀹㈡埛绔澶囩被鍨�');
+
+
+-- ----------------------------
+-- 12銆佸瓧鍏告暟鎹〃
+-- ----------------------------
+create table sys_dict_data (
+ dict_code number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ dict_sort number(4) default 0,
+ dict_label varchar2(100) default '',
+ dict_value varchar2(100) default '',
+ dict_type varchar2(100) default '',
+ css_class varchar2(100) default null,
+ list_class varchar2(100) default null,
+ is_default char(1) default 'N',
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date,
+ remark varchar2(500) default null
+);
+
+alter table sys_dict_data add constraint pk_sys_dict_data primary key (dict_code);
+
+comment on table sys_dict_data is '瀛楀吀鏁版嵁琛�';
+comment on column sys_dict_data.dict_code is '瀛楀吀涓婚敭';
+comment on column sys_dict_data.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_dict_data.dict_sort is '瀛楀吀鎺掑簭';
+comment on column sys_dict_data.dict_label is '瀛楀吀鏍囩';
+comment on column sys_dict_data.dict_value is '瀛楀吀閿��';
+comment on column sys_dict_data.dict_type is '瀛楀吀绫诲瀷';
+comment on column sys_dict_data.css_class is '鏍峰紡灞炴�э紙鍏朵粬鏍峰紡鎵╁睍锛�';
+comment on column sys_dict_data.list_class is '琛ㄦ牸鍥炴樉鏍峰紡';
+comment on column sys_dict_data.is_default is '鏄惁榛樿锛圷鏄� N鍚︼級';
+comment on column sys_dict_data.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_dict_data.create_by is '鍒涘缓鑰�';
+comment on column sys_dict_data.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_dict_data.update_by is '鏇存柊鑰�';
+comment on column sys_dict_data.update_time is '鏇存柊鏃堕棿';
+comment on column sys_dict_data.remark is '澶囨敞';
+
+insert into sys_dict_data values(1, '000000', 1, '鐢�', '0', 'sys_user_sex', '', '', 'Y', 103, 1, sysdate, null, null, '鎬у埆鐢�');
+insert into sys_dict_data values(2, '000000', 2, '濂�', '1', 'sys_user_sex', '', '', 'N', 103, 1, sysdate, null, null, '鎬у埆濂�');
+insert into sys_dict_data values(3, '000000', 3, '鏈煡', '2', 'sys_user_sex', '', '', 'N', 103, 1, sysdate, null, null, '鎬у埆鏈煡');
+insert into sys_dict_data values(4, '000000', 1, '鏄剧ず', '0', 'sys_show_hide', '', 'primary', 'Y', 103, 1, sysdate, null, null, '鏄剧ず鑿滃崟');
+insert into sys_dict_data values(5, '000000', 2, '闅愯棌', '1', 'sys_show_hide', '', 'danger', 'N', 103, 1, sysdate, null, null, '闅愯棌鑿滃崟');
+insert into sys_dict_data values(6, '000000', 1, '姝e父', '0', 'sys_normal_disable', '', 'primary', 'Y', 103, 1, sysdate, null, null, '姝e父鐘舵��');
+insert into sys_dict_data values(7, '000000', 2, '鍋滅敤', '1', 'sys_normal_disable', '', 'danger', 'N', 103, 1, sysdate, null, null, '鍋滅敤鐘舵��');
+insert into sys_dict_data values(12, '000000', 1, '鏄�', 'Y', 'sys_yes_no', '', 'primary', 'Y', 103, 1, sysdate, null, null, '绯荤粺榛樿鏄�');
+insert into sys_dict_data values(13, '000000', 2, '鍚�', 'N', 'sys_yes_no', '', 'danger', 'N', 103, 1, sysdate, null, null, '绯荤粺榛樿鍚�');
+insert into sys_dict_data values(14, '000000', 1, '閫氱煡', '1', 'sys_notice_type', '', 'warning', 'Y', 103, 1, sysdate, null, null, '閫氱煡');
+insert into sys_dict_data values(15, '000000', 2, '鍏憡', '2', 'sys_notice_type', '', 'success', 'N', 103, 1, sysdate, null, null, '鍏憡');
+insert into sys_dict_data values(16, '000000', 1, '姝e父', '0', 'sys_notice_status', '', 'primary', 'Y', 103, 1, sysdate, null, null, '姝e父鐘舵��');
+insert into sys_dict_data values(17, '000000', 2, '鍏抽棴', '1', 'sys_notice_status', '', 'danger', 'N', 103, 1, sysdate, null, null, '鍏抽棴鐘舵��');
+insert into sys_dict_data values(29, '000000', 99, '鍏朵粬', '0', 'sys_oper_type', '', 'info', 'N', 103, 1, sysdate, null, null, '鍏朵粬鎿嶄綔');
+insert into sys_dict_data values(18, '000000', 1, '鏂板', '1', 'sys_oper_type', '', 'info', 'N', 103, 1, sysdate, null, null, '鏂板鎿嶄綔');
+insert into sys_dict_data values(19, '000000', 2, '淇敼', '2', 'sys_oper_type', '', 'info', 'N', 103, 1, sysdate, null, null, '淇敼鎿嶄綔');
+insert into sys_dict_data values(20, '000000', 3, '鍒犻櫎', '3', 'sys_oper_type', '', 'danger', 'N', 103, 1, sysdate, null, null, '鍒犻櫎鎿嶄綔');
+insert into sys_dict_data values(21, '000000', 4, '鎺堟潈', '4', 'sys_oper_type', '', 'primary', 'N', 103, 1, sysdate, null, null, '鎺堟潈鎿嶄綔');
+insert into sys_dict_data values(22, '000000', 5, '瀵煎嚭', '5', 'sys_oper_type', '', 'warning', 'N', 103, 1, sysdate, null, null, '瀵煎嚭鎿嶄綔');
+insert into sys_dict_data values(23, '000000', 6, '瀵煎叆', '6', 'sys_oper_type', '', 'warning', 'N', 103, 1, sysdate, null, null, '瀵煎叆鎿嶄綔');
+insert into sys_dict_data values(24, '000000', 7, '寮洪��', '7', 'sys_oper_type', '', 'danger', 'N', 103, 1, sysdate, null, null, '寮洪��鎿嶄綔');
+insert into sys_dict_data values(25, '000000', 8, '鐢熸垚浠g爜', '8', 'sys_oper_type', '', 'warning', 'N', 103, 1, sysdate, null, null, '鐢熸垚鎿嶄綔');
+insert into sys_dict_data values(26, '000000', 9, '娓呯┖鏁版嵁', '9', 'sys_oper_type', '', 'danger', 'N', 103, 1, sysdate, null, null, '娓呯┖鎿嶄綔');
+insert into sys_dict_data values(27, '000000', 1, '鎴愬姛', '0', 'sys_common_status', '', 'primary', 'N', 103, 1, sysdate, null, null, '姝e父鐘舵��');
+insert into sys_dict_data values(28, '000000', 2, '澶辫触', '1', 'sys_common_status', '', 'danger', 'N', 103, 1, sysdate, null, null, '鍋滅敤鐘舵��');
+insert into sys_dict_data values(30, '000000', 0, '瀵嗙爜璁よ瘉', 'password', 'sys_grant_type', '', 'default', 'N', 103, 1, sysdate, null, null, '瀵嗙爜璁よ瘉');
+insert into sys_dict_data values(31, '000000', 0, '鐭俊璁よ瘉', 'sms', 'sys_grant_type', '', 'default', 'N', 103, 1, sysdate, null, null, '鐭俊璁よ瘉');
+insert into sys_dict_data values(32, '000000', 0, '閭欢璁よ瘉', 'email', 'sys_grant_type', '', 'default', 'N', 103, 1, sysdate, null, null, '閭欢璁よ瘉');
+insert into sys_dict_data values(33, '000000', 0, '灏忕▼搴忚璇�', 'xcx', 'sys_grant_type', '', 'default', 'N', 103, 1, sysdate, null, null, '灏忕▼搴忚璇�');
+insert into sys_dict_data values(34, '000000', 0, '涓夋柟鐧诲綍璁よ瘉', 'social', 'sys_grant_type', '', 'default', 'N', 103, 1, sysdate, null, null, '涓夋柟鐧诲綍璁よ瘉');
+insert into sys_dict_data values(35, '000000', 0, 'PC', 'pc', 'sys_device_type', '', 'default', 'N', 103, 1, sysdate, null, null, 'PC');
+insert into sys_dict_data values(36, '000000', 0, '瀹夊崜', 'android', 'sys_device_type', '', 'default', 'N', 103, 1, sysdate, null, null, '瀹夊崜');
+insert into sys_dict_data values(37, '000000', 0, 'iOS', 'ios', 'sys_device_type', '', 'default', 'N', 103, 1, sysdate, null, null, 'iOS');
+insert into sys_dict_data values(38, '000000', 0, '灏忕▼搴�', 'xcx', 'sys_device_type', '', 'default', 'N', 103, 1, sysdate, null, null, '灏忕▼搴�');
+
+
+-- ----------------------------
+-- 13銆佸弬鏁伴厤缃〃
+-- ----------------------------
+create table sys_config (
+ config_id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ config_name varchar2(100) default '',
+ config_key varchar2(100) default '',
+ config_value varchar2(100) default '',
+ config_type char(1) default 'N',
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date,
+ remark varchar2(500) default null
+);
+alter table sys_config add constraint pk_sys_config primary key (config_id);
+
+comment on table sys_config is '鍙傛暟閰嶇疆琛�';
+comment on column sys_config.config_id is '鍙傛暟涓婚敭';
+comment on column sys_config.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_config.config_name is '鍙傛暟鍚嶇О';
+comment on column sys_config.config_key is '鍙傛暟閿悕';
+comment on column sys_config.config_value is '鍙傛暟閿��';
+comment on column sys_config.config_type is '绯荤粺鍐呯疆锛圷鏄� N鍚︼級';
+comment on column sys_config.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_config.create_by is '鍒涘缓鑰�';
+comment on column sys_config.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_config.update_by is '鏇存柊鑰�';
+comment on column sys_config.update_time is '鏇存柊鏃堕棿';
+comment on column sys_config.remark is '澶囨敞';
+
+insert into sys_config values(1, '000000', '涓绘鏋堕〉-榛樿鐨偆鏍峰紡鍚嶇О', 'sys.index.skinName', 'skin-blue', 'Y', 103, 1, sysdate, null, null, '钃濊壊 skin-blue銆佺豢鑹� skin-green銆佺传鑹� skin-purple銆佺孩鑹� skin-red銆侀粍鑹� skin-yellow' );
+insert into sys_config values(2, '000000', '鐢ㄦ埛绠$悊-璐﹀彿鍒濆瀵嗙爜', 'sys.user.initPassword', '123456', 'Y', 103, 1, sysdate, null, null, '鍒濆鍖栧瘑鐮� 123456' );
+insert into sys_config values(3, '000000', '涓绘鏋堕〉-渚ц竟鏍忎富棰�', 'sys.index.sideTheme', 'theme-dark', 'Y', 103, 1, sysdate, null, null, '娣辫壊涓婚theme-dark锛屾祬鑹蹭富棰榯heme-light' );
+insert into sys_config values(5, '000000', '璐﹀彿鑷姪-鏄惁寮�鍚敤鎴锋敞鍐屽姛鑳�', 'sys.account.registerUser', 'false', 'Y', 103, 1, sysdate, null, null, '鏄惁寮�鍚敞鍐岀敤鎴峰姛鑳斤紙true寮�鍚紝false鍏抽棴锛�');
+insert into sys_config values(11, '000000', 'OSS棰勮鍒楄〃璧勬簮寮�鍏�', 'sys.oss.previewListResource', 'true', 'Y', 103, 1, sysdate, null, null, 'true:寮�鍚�, false:鍏抽棴');
+
+
+-- ----------------------------
+-- 14銆佺郴缁熻闂褰�
+-- ----------------------------
+create table sys_logininfor (
+ info_id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ user_name varchar2(50) default '',
+ client_key varchar2(32) default '',
+ device_type varchar2(32) default '',
+ ipaddr varchar2(128) default '',
+ login_location varchar2(255) default '',
+ browser varchar2(50) default '',
+ os varchar2(50) default '',
+ status char(1) default '0',
+ msg varchar2(255) default '',
+ login_time date
+);
+
+alter table sys_logininfor add constraint pk_sys_logininfor primary key (info_id);
+create index idx_sys_logininfor_s on sys_logininfor (status);
+create index idx_sys_logininfor_lt on sys_logininfor (login_time);
+
+comment on table sys_logininfor is '绯荤粺璁块棶璁板綍';
+comment on column sys_logininfor.info_id is '璁块棶ID';
+comment on column sys_logininfor.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_logininfor.user_name is '鐧诲綍璐﹀彿';
+comment on column sys_logininfor.client_key is '瀹㈡埛绔�';
+comment on column sys_logininfor.device_type is '璁惧绫诲瀷';
+comment on column sys_logininfor.ipaddr is '鐧诲綍IP鍦板潃';
+comment on column sys_logininfor.login_location is '鐧诲綍鍦扮偣';
+comment on column sys_logininfor.browser is '娴忚鍣ㄧ被鍨�';
+comment on column sys_logininfor.os is '鎿嶄綔绯荤粺';
+comment on column sys_logininfor.status is '鐧诲綍鐘舵�侊紙0鎴愬姛 1澶辫触锛�';
+comment on column sys_logininfor.msg is '鎻愮ず娑堟伅';
+comment on column sys_logininfor.login_time is '璁块棶鏃堕棿';
+
+
+-- ----------------------------
+-- 17銆侀�氱煡鍏憡琛�
+-- ----------------------------
+create table sys_notice (
+ notice_id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ notice_title varchar2(50) not null,
+ notice_type char(1) not null,
+ notice_content clob default null,
+ status char(1) default '0',
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date,
+ remark varchar2(255) default null
+);
+
+alter table sys_notice add constraint pk_sys_notice primary key (notice_id);
+
+comment on table sys_notice is '閫氱煡鍏憡琛�';
+comment on column sys_notice.notice_id is '鍏憡涓婚敭';
+comment on column sys_notice.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_notice.notice_title is '鍏憡鏍囬';
+comment on column sys_notice.notice_type is '鍏憡绫诲瀷锛�1閫氱煡 2鍏憡锛�';
+comment on column sys_notice.notice_content is '鍏憡鍐呭';
+comment on column sys_notice.status is '鍏憡鐘舵�侊紙0姝e父 1鍏抽棴锛�';
+comment on column sys_notice.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_notice.create_by is '鍒涘缓鑰�';
+comment on column sys_notice.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_notice.update_by is '鏇存柊鑰�';
+comment on column sys_notice.update_time is '鏇存柊鏃堕棿';
+comment on column sys_notice.remark is '澶囨敞';
+
+-- ----------------------------
+-- 鍒濆鍖�-鍏憡淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+insert into sys_notice values('1', '000000', '娓╅Θ鎻愰啋锛�2018-07-01 鏂扮増鏈彂甯冨暒', '2', '鏂扮増鏈唴瀹�', '0', 103, 1, sysdate, null, null, '绠$悊鍛�');
+insert into sys_notice values('2', '000000', '缁存姢閫氱煡锛�2018-07-01 绯荤粺鍑屾櫒缁存姢', '1', '缁存姢鍐呭', '0', 103, 1, sysdate, null, null, '绠$悊鍛�');
+
+
+-- ----------------------------
+-- 18銆佷唬鐮佺敓鎴愪笟鍔¤〃
+-- ----------------------------
+create table gen_table (
+ table_id number(20) not null,
+ data_name varchar2(200) default '',
+ table_name varchar2(200) default '',
+ table_comment varchar2(500) default '',
+ sub_table_name varchar2(64) default null,
+ sub_table_fk_name varchar2(64) default null,
+ class_name varchar2(100) default '',
+ tpl_category varchar2(200) default 'crud',
+ package_name varchar2(100),
+ module_name varchar2(30),
+ business_name varchar2(30),
+ function_name varchar2(50),
+ function_author varchar2(50),
+ gen_type char(1) default '0',
+ gen_path varchar2(200) default '/',
+ options varchar2(1000),
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date,
+ remark varchar2(500) default null
+);
+
+alter table gen_table add constraint pk_gen_table primary key (table_id);
+
+comment on table gen_table is '浠g爜鐢熸垚涓氬姟琛�';
+comment on column gen_table.table_id is '缂栧彿';
+comment on column gen_table.data_name is '鏁版嵁婧愬悕绉�';
+comment on column gen_table.table_name is '琛ㄥ悕绉�';
+comment on column gen_table.table_comment is '琛ㄦ弿杩�';
+comment on column gen_table.sub_table_name is '鍏宠仈瀛愯〃鐨勮〃鍚�';
+comment on column gen_table.sub_table_fk_name is '瀛愯〃鍏宠仈鐨勫閿悕';
+comment on column gen_table.class_name is '瀹炰綋绫诲悕绉�';
+comment on column gen_table.tpl_category is '浣跨敤鐨勬ā鏉匡紙crud鍗曡〃鎿嶄綔 tree鏍戣〃鎿嶄綔锛�';
+comment on column gen_table.package_name is '鐢熸垚鍖呰矾寰�';
+comment on column gen_table.module_name is '鐢熸垚妯″潡鍚�';
+comment on column gen_table.business_name is '鐢熸垚涓氬姟鍚�';
+comment on column gen_table.function_name is '鐢熸垚鍔熻兘鍚�';
+comment on column gen_table.function_author is '鐢熸垚鍔熻兘浣滆��';
+comment on column gen_table.gen_type is '鐢熸垚浠g爜鏂瑰紡锛�0zip鍘嬬缉鍖� 1鑷畾涔夎矾寰勶級';
+comment on column gen_table.gen_path is '鐢熸垚璺緞锛堜笉濉粯璁ら」鐩矾寰勶級';
+comment on column gen_table.options is '鍏跺畠鐢熸垚閫夐」';
+comment on column gen_table.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column gen_table.create_by is '鍒涘缓鑰�';
+comment on column gen_table.create_time is '鍒涘缓鏃堕棿';
+comment on column gen_table.update_by is '鏇存柊鑰�';
+comment on column gen_table.update_time is '鏇存柊鏃堕棿';
+comment on column gen_table.remark is '澶囨敞';
+
+
+-- ----------------------------
+-- 19銆佷唬鐮佺敓鎴愪笟鍔¤〃瀛楁
+-- ----------------------------
+create table gen_table_column (
+ column_id number(20) not null,
+ table_id number(20),
+ column_name varchar2(200),
+ column_comment varchar2(500),
+ column_type varchar2(100),
+ java_type varchar2(500),
+ java_field varchar2(200),
+ is_pk char(1),
+ is_increment char(1),
+ is_required char(1),
+ is_insert char(1),
+ is_edit char(1),
+ is_list char(1),
+ is_query char(1),
+ query_type varchar2(200) default 'EQ',
+ html_type varchar2(200),
+ dict_type varchar2(200) default '',
+ sort number(4),
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date ,
+ update_by number(20) default null,
+ update_time date
+);
+
+alter table gen_table_column add constraint pk_gen_table_column primary key (column_id);
+
+comment on table gen_table_column is '浠g爜鐢熸垚涓氬姟琛ㄥ瓧娈�';
+comment on column gen_table_column.column_id is '缂栧彿';
+comment on column gen_table_column.table_id is '褰掑睘琛ㄧ紪鍙�';
+comment on column gen_table_column.column_name is '鍒楀悕绉�';
+comment on column gen_table_column.column_comment is '鍒楁弿杩�';
+comment on column gen_table_column.column_type is '鍒楃被鍨�';
+comment on column gen_table_column.java_type is 'JAVA绫诲瀷';
+comment on column gen_table_column.java_field is 'JAVA瀛楁鍚�';
+comment on column gen_table_column.is_pk is '鏄惁涓婚敭锛�1鏄級';
+comment on column gen_table_column.is_increment is '鏄惁鑷锛�1鏄級';
+comment on column gen_table_column.is_required is '鏄惁蹇呭~锛�1鏄級';
+comment on column gen_table_column.is_insert is '鏄惁涓烘彃鍏ュ瓧娈碉紙1鏄級';
+comment on column gen_table_column.is_edit is '鏄惁缂栬緫瀛楁锛�1鏄級';
+comment on column gen_table_column.is_list is '鏄惁鍒楄〃瀛楁锛�1鏄級';
+comment on column gen_table_column.is_query is '鏄惁鏌ヨ瀛楁锛�1鏄級';
+comment on column gen_table_column.query_type is '鏌ヨ鏂瑰紡锛堢瓑浜庛�佷笉绛変簬銆佸ぇ浜庛�佸皬浜庛�佽寖鍥达級';
+comment on column gen_table_column.html_type is '鏄剧ず绫诲瀷锛堟枃鏈銆佹枃鏈煙銆佷笅鎷夋銆佸閫夋銆佸崟閫夋銆佹棩鏈熸帶浠讹級';
+comment on column gen_table_column.dict_type is '瀛楀吀绫诲瀷';
+comment on column gen_table_column.sort is '鎺掑簭';
+comment on column gen_table_column.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column gen_table_column.create_by is '鍒涘缓鑰�';
+comment on column gen_table_column.create_time is '鍒涘缓鏃堕棿';
+comment on column gen_table_column.update_by is '鏇存柊鑰�';
+comment on column gen_table_column.update_time is '鏇存柊鏃堕棿';
+
+
+-- ----------------------------
+-- OSS瀵硅薄瀛樺偍琛�
+-- ----------------------------
+create table sys_oss (
+ oss_id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ file_name varchar2(255) not null,
+ original_name varchar2(255) not null,
+ file_suffix varchar2(10) not null,
+ url varchar2(500) not null,
+ service varchar2(20) default 'minio' not null,
+ ext1 varchar2(500) default '',
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date
+);
+
+alter table sys_oss add constraint pk_sys_oss primary key (oss_id);
+
+comment on table sys_oss is 'OSS瀵硅薄瀛樺偍琛�';
+comment on column sys_oss.oss_id is '瀵硅薄瀛樺偍涓婚敭';
+comment on column sys_oss.tenant_id is '绉熸埛缂栫爜';
+comment on column sys_oss.file_name is '鏂囦欢鍚�';
+comment on column sys_oss.original_name is '鍘熷悕';
+comment on column sys_oss.file_suffix is '鏂囦欢鍚庣紑鍚�';
+comment on column sys_oss.url is 'URL鍦板潃';
+comment on column sys_oss.service is '鏈嶅姟鍟�';
+comment on column sys_oss.ext1 is '鎵╁睍瀛楁';
+comment on column sys_oss.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_oss.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_oss.create_by is '涓婁紶鑰�';
+comment on column sys_oss.update_time is '鏇存柊鏃堕棿';
+comment on column sys_oss.update_by is '鏇存柊鑰�';
+
+
+-- ----------------------------
+-- OSS瀵硅薄瀛樺偍鍔ㄦ�侀厤缃〃
+-- ----------------------------
+create table sys_oss_config (
+ oss_config_id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ config_key varchar2(20) not null,
+ access_key varchar2(255) default '',
+ secret_key varchar2(255) default '',
+ bucket_name varchar2(255) default '',
+ prefix varchar2(255) default '',
+ endpoint varchar2(255) default '',
+ domain varchar2(255) default '',
+ is_https char(1) default 'N',
+ region varchar2(255) default '',
+ access_policy char(1) default '1' not null,
+ status char(1) default '1',
+ ext1 varchar2(255) default '',
+ remark varchar2(500) default null,
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date
+);
+
+alter table sys_oss_config add constraint pk_sys_oss_config primary key (oss_config_id);
+
+comment on table sys_oss_config is '瀵硅薄瀛樺偍閰嶇疆琛�';
+comment on column sys_oss_config.oss_config_id is '涓婚敭';
+comment on column sys_oss_config.tenant_id is '绉熸埛缂栫爜';
+comment on column sys_oss_config.config_key is '閰嶇疆key';
+comment on column sys_oss_config.access_key is 'accesskey';
+comment on column sys_oss_config.secret_key is '绉橀挜';
+comment on column sys_oss_config.bucket_name is '妗跺悕绉�';
+comment on column sys_oss_config.prefix is '鍓嶇紑';
+comment on column sys_oss_config.endpoint is '璁块棶绔欑偣';
+comment on column sys_oss_config.domain is '鑷畾涔夊煙鍚�';
+comment on column sys_oss_config.is_https is '鏄惁https锛圷=鏄�,N=鍚︼級';
+comment on column sys_oss_config.region is '鍩�';
+comment on column sys_oss_config.access_policy is '妗舵潈闄愮被鍨�(0=private 1=public 2=custom)';
+comment on column sys_oss_config.status is '鏄惁榛樿锛�0=鏄�,1=鍚︼級';
+comment on column sys_oss_config.ext1 is '鎵╁睍瀛楁';
+comment on column sys_oss_config.remark is '澶囨敞';
+comment on column sys_oss_config.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_oss_config.create_by is '鍒涘缓鑰�';
+comment on column sys_oss_config.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_oss_config.update_by is '鏇存柊鑰�';
+comment on column sys_oss_config.update_time is '鏇存柊鏃堕棿';
+
+insert into sys_oss_config values (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '','N', '', '1', '0', '', NULL, 103, 1, sysdate, 1, sysdate);
+insert into sys_oss_config values (2, '000000', 'qiniu', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 's3-cn-north-1.qiniucs.com', '','N', '', '1', '1', '', NULL, 103, 1, sysdate, 1, sysdate);
+insert into sys_oss_config values (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 'oss-cn-beijing.aliyuncs.com', '','N', '', '1', '1', '', NULL, 103, 1, sysdate, 1, sysdate);
+insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi-1250000000', '', 'cos.ap-beijing.myqcloud.com', '','N', 'ap-beijing', '1', '1', '', NULL, 103, 1, sysdate, 1, sysdate);
+insert into sys_oss_config values (5, '000000', 'image', 'ruoyi', 'ruoyi123', 'ruoyi', 'image', '127.0.0.1:9000', '','N', '', '1', '1', '', NULL, 103, 1, sysdate, 1, sysdate);
+
+-- ----------------------------
+-- 绯荤粺鎺堟潈琛�
+-- ----------------------------
+create table sys_client (
+ id number(20) not null,
+ client_id varchar2(64) default null,
+ client_key varchar2(32) default null,
+ client_secret varchar2(255) default null,
+ grant_type varchar2(255) default null,
+ device_type varchar2(32) default null,
+ active_timeout number(11) default 1800,
+ timeout number(11) default 604800,
+ status char(1) default '0',
+ del_flag char(1) default '0',
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date
+);
+
+alter table sys_client add constraint pk_sys_client primary key (id);
+
+comment on table sys_client is '绯荤粺鎺堟潈琛�';
+comment on column sys_client.id is '涓婚敭';
+comment on column sys_client.client_id is '瀹㈡埛绔痠d';
+comment on column sys_client.client_key is '瀹㈡埛绔痥ey';
+comment on column sys_client.client_secret is '瀹㈡埛绔閽�';
+comment on column sys_client.grant_type is '鎺堟潈绫诲瀷';
+comment on column sys_client.device_type is '璁惧绫诲瀷';
+comment on column sys_client.active_timeout is 'token娲昏穬瓒呮椂鏃堕棿';
+comment on column sys_client.timeout is 'token鍥哄畾瓒呮椂';
+comment on column sys_client.status is '鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_client.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+comment on column sys_client.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_client.create_by is '鍒涘缓鑰�';
+comment on column sys_client.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_client.update_by is '鏇存柊鑰�';
+comment on column sys_client.update_time is '鏇存柊鏃堕棿';
+
+insert into sys_client values (1, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 103, 1, sysdate, 1, sysdate);
+insert into sys_client values (2, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'android', 1800, 604800, 0, 0, 103, 1, sysdate, 1, sysdate);
+
+create table test_demo (
+ id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ dept_id number(20) default null,
+ user_id number(20) default null,
+ order_num number(10) default 0,
+ test_key varchar2(255) default null,
+ value varchar2(255) default null,
+ version number(10) default 0,
+ create_dept number(20) default null,
+ create_time date,
+ create_by number(20) default null,
+ update_time date,
+ update_by number(20) default null,
+ del_flag number(2) default 0
+);
+
+alter table test_demo add constraint pk_test_demo primary key (id);
+
+comment on table test_demo is '娴嬭瘯鍗曡〃';
+comment on column test_demo.id is '涓婚敭';
+comment on column test_demo.tenant_id is '绉熸埛缂栧彿';
+comment on column test_demo.dept_id is '閮ㄩ棬id';
+comment on column test_demo.user_id is '鐢ㄦ埛id';
+comment on column test_demo.order_num is '鎺掑簭鍙�';
+comment on column test_demo.test_key is 'key閿�';
+comment on column test_demo.value is '鍊�';
+comment on column test_demo.version is '鐗堟湰';
+comment on column test_demo.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column test_demo.create_time is '鍒涘缓鏃堕棿';
+comment on column test_demo.create_by is '鍒涘缓浜�';
+comment on column test_demo.update_time is '鏇存柊鏃堕棿';
+comment on column test_demo.update_by is '鏇存柊浜�';
+comment on column test_demo.del_flag is '鍒犻櫎鏍囧織';
+
+create table test_tree (
+ id number(20) not null,
+ tenant_id varchar2(20) default '000000',
+ parent_id number(20) default 0,
+ dept_id number(20) default null,
+ user_id number(20) default null,
+ tree_name varchar2(255) default null,
+ version number(10) default 0,
+ create_dept number(20) default null,
+ create_time date,
+ create_by number(20) default null,
+ update_time date,
+ update_by number(20) default null,
+ del_flag number(2) default 0
+);
+
+alter table test_tree add constraint pk_test_tree primary key (id);
+
+comment on table test_tree is '娴嬭瘯鏍戣〃';
+comment on column test_tree.id is '涓婚敭';
+comment on column test_tree.tenant_id is '绉熸埛缂栧彿';
+comment on column test_tree.parent_id is '鐖秈d';
+comment on column test_tree.dept_id is '閮ㄩ棬id';
+comment on column test_tree.user_id is '鐢ㄦ埛id';
+comment on column test_tree.tree_name is '鍊�';
+comment on column test_tree.version is '鐗堟湰';
+comment on column test_tree.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column test_tree.create_time is '鍒涘缓鏃堕棿';
+comment on column test_tree.create_by is '鍒涘缓浜�';
+comment on column test_tree.update_time is '鏇存柊鏃堕棿';
+comment on column test_tree.update_by is '鏇存柊浜�';
+comment on column test_tree.del_flag is '鍒犻櫎鏍囧織';
+
+insert into test_demo values (1, '000000', 102, 4, 1, '娴嬭瘯鏁版嵁鏉冮檺', '娴嬭瘯', 0, 103, sysdate, 1, null, null, 0);
+insert into test_demo values (2, '000000', 102, 3, 2, '瀛愯妭鐐�1', '111', 0, 103, sysdate, 1, null, null, 0);
+insert into test_demo values (3, '000000', 102, 3, 3, '瀛愯妭鐐�2', '222', 0, 103, sysdate, 1, null, null, 0);
+insert into test_demo values (4, '000000', 108, 4, 4, '娴嬭瘯鏁版嵁', 'demo', 0, 103, sysdate, 1, null, null, 0);
+insert into test_demo values (5, '000000', 108, 3, 13, '瀛愯妭鐐�11', '1111', 0, 103, sysdate, 1, null, null, 0);
+insert into test_demo values (6, '000000', 108, 3, 12, '瀛愯妭鐐�22', '2222', 0, 103, sysdate, 1, null, null, 0);
+insert into test_demo values (7, '000000', 108, 3, 11, '瀛愯妭鐐�33', '3333', 0, 103, sysdate, 1, null, null, 0);
+insert into test_demo values (8, '000000', 108, 3, 10, '瀛愯妭鐐�44', '4444', 0, 103, sysdate, 1, null, null, 0);
+insert into test_demo values (9, '000000', 108, 3, 9, '瀛愯妭鐐�55', '5555', 0, 103, sysdate, 1, null, null, 0);
+insert into test_demo values (10, '000000', 108, 3, 8, '瀛愯妭鐐�66', '6666', 0, 103, sysdate, 1, null, null, 0);
+insert into test_demo values (11, '000000', 108, 3, 7, '瀛愯妭鐐�77', '7777', 0, 103, sysdate, 1, null, null, 0);
+insert into test_demo values (12, '000000', 108, 3, 6, '瀛愯妭鐐�88', '8888', 0, 103, sysdate, 1, null, null, 0);
+insert into test_demo values (13, '000000', 108, 3, 5, '瀛愯妭鐐�99', '9999', 0, 103, sysdate, 1, null, null, 0);
+
+insert into test_tree values (1, '000000', 0, 102, 4, '娴嬭瘯鏁版嵁鏉冮檺', 0, 103, sysdate, 1, null, null, 0);
+insert into test_tree values (2, '000000', 1, 102, 3, '瀛愯妭鐐�1', 0, 103, sysdate, 1, null, null, 0);
+insert into test_tree values (3, '000000', 2, 102, 3, '瀛愯妭鐐�2', 0, 103, sysdate, 1, null, null, 0);
+insert into test_tree values (4, '000000', 0, 108, 4, '娴嬭瘯鏍�1', 0, 103, sysdate, 1, null, null, 0);
+insert into test_tree values (5, '000000', 4, 108, 3, '瀛愯妭鐐�11', 0, 103, sysdate, 1, null, null, 0);
+insert into test_tree values (6, '000000', 4, 108, 3, '瀛愯妭鐐�22', 0, 103, sysdate, 1, null, null, 0);
+insert into test_tree values (7, '000000', 4, 108, 3, '瀛愯妭鐐�33', 0, 103, sysdate, 1, null, null, 0);
+insert into test_tree values (8, '000000', 5, 108, 3, '瀛愯妭鐐�44', 0, 103, sysdate, 1, null, null, 0);
+insert into test_tree values (9, '000000', 6, 108, 3, '瀛愯妭鐐�55', 0, 103, sysdate, 1, null, null, 0);
+insert into test_tree values (10, '000000', 7, 108, 3, '瀛愯妭鐐�66', 0, 103, sysdate, 1, null, null, 0);
+insert into test_tree values (11, '000000', 7, 108, 3, '瀛愯妭鐐�77', 0, 103, sysdate, 1, null, null, 0);
+insert into test_tree values (12, '000000', 10, 108, 3, '瀛愯妭鐐�88', 0, 103, sysdate, 1, null, null, 0);
+insert into test_tree values (13, '000000', 10, 108, 3, '瀛愯妭鐐�99', 0, 103, sysdate, 1, null, null, 0);
+
+
+-- ----------------------------
+-- 閽╁瓙 锛岀敤浜巗ession杩炴帴涔嬪悗 鑷姩璁剧疆榛樿鐨刣ate绫诲瀷鏍煎紡鍖� 绠�鍖栨椂闂存煡璇�
+-- 濡傞渶璁剧疆鍏跺畠閰嶇疆 鍙湪姝ら挬瀛愬唴浠绘剰澧炲姞澶勭悊璇彞
+-- 渚嬪锛� SELECT * FROM sys_user WHERE create_time BETWEEN '2022-03-01 00:00:00' AND '2022-04-01 00:00:00'
+-- ----------------------------
+create or replace trigger login_trg
+after logon on database
+begin
+execute immediate 'alter session set nls_date_format=''YYYY-MM-DD HH24:MI:SS''';
+end;
diff --git a/RuoYi-Vue-Plus/script/sql/oracle/oracle_ry_workflow.sql b/RuoYi-Vue-Plus/script/sql/oracle/oracle_ry_workflow.sql
new file mode 100755
index 0000000..7f5ab22
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/oracle/oracle_ry_workflow.sql
@@ -0,0 +1,528 @@
+-- ----------------------------
+-- 0銆亀arm-flow-all.sql锛屽湴鍧�锛歨ttps://gitee.com/dromara/warm-flow/blob/master/sql/oracle/oracle-wram-flow-all.sql
+-- ----------------------------
+create table FLOW_DEFINITION
+(
+ ID NUMBER(20) not null,
+ FLOW_CODE VARCHAR2(40) not null,
+ FLOW_NAME VARCHAR2(100) not null,
+ MODEL_VALUE VARCHAR2(40) default 'CLASSICS' not null,
+ CATEGORY VARCHAR2(100),
+ VERSION VARCHAR2(20) not null,
+ IS_PUBLISH NUMBER(1) default 0 not null,
+ FORM_CUSTOM VARCHAR2(1) default 'N',
+ FORM_PATH VARCHAR2(100),
+ ACTIVITY_STATUS NUMBER(1) default 1,
+ LISTENER_TYPE VARCHAR2(100),
+ LISTENER_PATH VARCHAR2(500),
+ EXT VARCHAR2(500),
+ CREATE_TIME DATE,
+ CREATE_BY VARCHAR2(64) default '',
+ UPDATE_TIME DATE,
+ UPDATE_BY VARCHAR2(64) default '',
+ DEL_FLAG VARCHAR2(1) default '0',
+ TENANT_ID VARCHAR2(40)
+);
+
+alter table FLOW_DEFINITION
+ add constraint PK_FLOW_DEFINITION primary key (ID);
+
+comment on table FLOW_DEFINITION is '娴佺▼瀹氫箟琛�';
+comment on column FLOW_DEFINITION.ID is '涓婚敭id';
+comment on column FLOW_DEFINITION.FLOW_CODE is '娴佺▼缂栫爜';
+comment on column FLOW_DEFINITION.FLOW_NAME is '娴佺▼鍚嶇О';
+comment on column FLOW_DEFINITION.MODEL_VALUE is '璁捐鍣ㄦā鍨嬶紙CLASSICS缁忓吀妯″瀷 MIMIC浠块拤閽夋ā鍨嬶級';
+comment on column FLOW_DEFINITION.CATEGORY is '娴佺▼绫诲埆';
+comment on column FLOW_DEFINITION.VERSION is '娴佺▼鐗堟湰';
+comment on column FLOW_DEFINITION.IS_PUBLISH is '鏄惁鍙戝竷 (0鏈彂甯� 1宸插彂甯� 9澶辨晥)';
+comment on column FLOW_DEFINITION.FORM_CUSTOM is '瀹℃壒琛ㄥ崟鏄惁鑷畾涔� (Y鏄� N鍚�)';
+comment on column FLOW_DEFINITION.FORM_PATH is '瀹℃壒琛ㄥ崟璺緞';
+comment on column FLOW_DEFINITION.ACTIVITY_STATUS is '娴佺▼婵�娲荤姸鎬侊紙0鎸傝捣 1婵�娲伙級';
+comment on column FLOW_DEFINITION.LISTENER_TYPE is '鐩戝惉鍣ㄧ被鍨�';
+comment on column FLOW_DEFINITION.LISTENER_PATH is '鐩戝惉鍣ㄨ矾寰�';
+comment on column FLOW_DEFINITION.EXT is '鎵╁睍瀛楁锛岄鐣欑粰涓氬姟绯荤粺浣跨敤';
+comment on column FLOW_DEFINITION.CREATE_TIME is '鍒涘缓鏃堕棿';
+comment on column FLOW_DEFINITION.CREATE_BY is '鍒涘缓浜�';
+comment on column FLOW_DEFINITION.UPDATE_TIME is '鏇存柊鏃堕棿';
+comment on column FLOW_DEFINITION.UPDATE_BY is '鏇存柊浜�';
+comment on column FLOW_DEFINITION.DEL_FLAG is '鍒犻櫎鏍囧織';
+comment on column FLOW_DEFINITION.TENANT_ID is '绉熸埛id';
+
+create table FLOW_NODE
+(
+ ID NUMBER(20) not null,
+ NODE_TYPE NUMBER(1) not null,
+ DEFINITION_ID NUMBER(20) not null,
+ NODE_CODE VARCHAR2(100) not null,
+ NODE_NAME VARCHAR2(100),
+ PERMISSION_FLAG VARCHAR2(200),
+ NODE_RATIO VARCHAR2(200),
+ COORDINATE VARCHAR2(100),
+ ANY_NODE_SKIP VARCHAR2(100),
+ LISTENER_TYPE VARCHAR2(100),
+ LISTENER_PATH VARCHAR2(500),
+ FORM_CUSTOM VARCHAR2(1) default 'N',
+ FORM_PATH VARCHAR2(100),
+ VERSION VARCHAR2(20),
+ CREATE_TIME DATE,
+ CREATE_BY VARCHAR2(64) default '',
+ UPDATE_TIME DATE,
+ UPDATE_BY VARCHAR2(64) default '',
+ EXT CLOB,
+ DEL_FLAG VARCHAR2(1) default '0',
+ TENANT_ID VARCHAR2(40)
+);
+
+alter table FLOW_NODE
+ add constraint PK_FLOW_NODE primary key (ID);
+
+comment on table FLOW_NODE is '娴佺▼鑺傜偣琛�';
+comment on column FLOW_NODE.ID is '涓婚敭id';
+comment on column FLOW_NODE.NODE_TYPE is '鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�';
+comment on column FLOW_NODE.DEFINITION_ID is '瀵瑰簲flow_definition琛ㄧ殑id';
+comment on column FLOW_NODE.NODE_CODE is '娴佺▼鑺傜偣缂栫爜';
+comment on column FLOW_NODE.NODE_NAME is '娴佺▼鑺傜偣鍚嶇О';
+comment on column FLOW_NODE.NODE_RATIO is '娴佺▼绛剧讲姣斾緥鍊�';
+comment on column FLOW_NODE.COORDINATE is '鍧愭爣';
+comment on column FLOW_NODE.ANY_NODE_SKIP is '浠绘剰缁撶偣璺宠浆';
+comment on column FLOW_NODE.LISTENER_TYPE is '鐩戝惉鍣ㄧ被鍨�';
+comment on column FLOW_NODE.LISTENER_PATH is '鐩戝惉鍣ㄨ矾寰�';
+comment on column FLOW_NODE.FORM_CUSTOM is '瀹℃壒琛ㄥ崟鏄惁鑷畾涔� (Y鏄� N鍚�)';
+comment on column FLOW_NODE.FORM_PATH is '瀹℃壒琛ㄥ崟璺緞';
+comment on column FLOW_NODE.VERSION is '鐗堟湰';
+comment on column FLOW_NODE.CREATE_TIME is '鍒涘缓鏃堕棿';
+comment on column FLOW_NODE.CREATE_BY is '鍒涘缓浜�';
+comment on column FLOW_NODE.UPDATE_TIME is '鏇存柊鏃堕棿';
+comment on column FLOW_NODE.UPDATE_BY is '鏇存柊浜�';
+comment on column FLOW_NODE.EXT is '鑺傜偣鎵╁睍灞炴��';
+comment on column FLOW_NODE.DEL_FLAG is '鍒犻櫎鏍囧織';
+comment on column FLOW_NODE.TENANT_ID is '绉熸埛id';
+comment on column FLOW_NODE.PERMISSION_FLAG is '鏉冮檺鏍囪瘑锛堟潈闄愮被鍨�:鏉冮檺鏍囪瘑锛屽彲浠ュ涓紝鐢ˊ@闅斿紑)';
+
+create table FLOW_SKIP
+(
+ ID NUMBER(20) not null,
+ DEFINITION_ID NUMBER(20) not null,
+ NOW_NODE_CODE VARCHAR2(100) not null,
+ NOW_NODE_TYPE NUMBER(1),
+ NEXT_NODE_CODE VARCHAR2(100) not null,
+ NEXT_NODE_TYPE NUMBER(1),
+ SKIP_NAME VARCHAR2(100),
+ SKIP_TYPE VARCHAR2(40),
+ SKIP_CONDITION VARCHAR2(200),
+ COORDINATE VARCHAR2(100),
+ CREATE_TIME DATE,
+ CREATE_BY VARCHAR2(64) default '',
+ UPDATE_TIME DATE,
+ UPDATE_BY VARCHAR2(64) default '',
+ DEL_FLAG VARCHAR2(1) default '0',
+ TENANT_ID VARCHAR2(40)
+);
+
+alter table FLOW_SKIP
+ add constraint PK_FLOW_SKIP primary key (ID);
+
+comment on table FLOW_SKIP is '鑺傜偣璺宠浆鍏宠仈琛�';
+comment on column FLOW_SKIP.ID is '涓婚敭id';
+comment on column FLOW_SKIP.DEFINITION_ID is '娴佺▼瀹氫箟id';
+comment on column FLOW_SKIP.NOW_NODE_CODE is '褰撳墠娴佺▼鑺傜偣绫诲瀷 (0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧)';
+comment on column FLOW_SKIP.NOW_NODE_TYPE is '涓嬩竴涓祦绋嬭妭鐐圭被鍨� (0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧)';
+comment on column FLOW_SKIP.NEXT_NODE_CODE is '涓嬩竴涓祦绋嬭妭鐐圭紪鐮�';
+comment on column FLOW_SKIP.NEXT_NODE_TYPE is '涓嬩竴涓祦绋嬭妭鐐圭被鍨� (0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧)';
+comment on column FLOW_SKIP.SKIP_NAME is '璺宠浆鍚嶇О';
+comment on column FLOW_SKIP.SKIP_TYPE is '璺宠浆绫诲瀷 (PASS瀹℃壒閫氳繃 REJECT閫�鍥�)';
+comment on column FLOW_SKIP.SKIP_CONDITION is '璺宠浆鏉′欢';
+comment on column FLOW_SKIP.COORDINATE is '鍧愭爣';
+comment on column FLOW_SKIP.CREATE_TIME is '鍒涘缓鏃堕棿';
+comment on column FLOW_SKIP.CREATE_BY is '鍒涘缓浜�';
+comment on column FLOW_SKIP.UPDATE_TIME is '鏇存柊鏃堕棿';
+comment on column FLOW_SKIP.UPDATE_BY is '鏇存柊浜�';
+comment on column FLOW_SKIP.DEL_FLAG is '鍒犻櫎鏍囧織';
+comment on column FLOW_SKIP.TENANT_ID is '绉熸埛id';
+
+create table FLOW_INSTANCE
+(
+ ID NUMBER not null,
+ DEFINITION_ID NUMBER not null,
+ BUSINESS_ID VARCHAR2(40) not null,
+ NODE_TYPE NUMBER(1),
+ NODE_CODE VARCHAR2(100),
+ NODE_NAME VARCHAR2(100),
+ VARIABLE CLOB,
+ FLOW_STATUS VARCHAR2(20),
+ ACTIVITY_STATUS NUMBER(1) default 1,
+ DEF_JSON CLOB,
+ CREATE_TIME DATE,
+ CREATE_BY VARCHAR2(64) default '',
+ UPDATE_TIME DATE,
+ UPDATE_BY VARCHAR2(64) default '',
+ EXT VARCHAR2(500),
+ DEL_FLAG VARCHAR2(1) default '0',
+ TENANT_ID VARCHAR2(40)
+);
+
+alter table FLOW_INSTANCE
+ add constraint PK_FLOW_INSTANCE primary key (ID);
+
+comment on table FLOW_INSTANCE is '娴佺▼瀹炰緥琛�';
+comment on column FLOW_INSTANCE.ID is '涓婚敭id';
+comment on column FLOW_INSTANCE.DEFINITION_ID is '瀵瑰簲flow_definition琛ㄧ殑id';
+comment on column FLOW_INSTANCE.BUSINESS_ID is '涓氬姟id';
+comment on column FLOW_INSTANCE.NODE_TYPE is '寮�濮嬭妭鐐圭被鍨� (0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧)';
+comment on column FLOW_INSTANCE.NODE_CODE is '寮�濮嬭妭鐐圭紪鐮�';
+comment on column FLOW_INSTANCE.NODE_NAME is '寮�濮嬭妭鐐瑰悕绉�';
+comment on column FLOW_INSTANCE.VARIABLE is '浠诲姟鍙橀噺';
+comment on column FLOW_INSTANCE.FLOW_STATUS is '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�';
+comment on column FLOW_INSTANCE.ACTIVITY_STATUS is '娴佺▼婵�娲荤姸鎬侊紙0鎸傝捣 1婵�娲伙級';
+comment on column FLOW_INSTANCE.DEF_JSON is '娴佺▼瀹氫箟json';
+comment on column FLOW_INSTANCE.CREATE_TIME is '鍒涘缓鏃堕棿';
+comment on column FLOW_INSTANCE.CREATE_BY is '鍒涘缓浜�';
+comment on column FLOW_INSTANCE.UPDATE_TIME is '鏇存柊鏃堕棿';
+comment on column FLOW_INSTANCE.UPDATE_BY is '鏇存柊浜�';
+comment on column FLOW_INSTANCE.EXT is '鎵╁睍瀛楁锛岄鐣欑粰涓氬姟绯荤粺浣跨敤';
+comment on column FLOW_INSTANCE.DEL_FLAG is '鍒犻櫎鏍囧織';
+comment on column FLOW_INSTANCE.TENANT_ID is '绉熸埛id';
+
+create table FLOW_TASK
+(
+ ID NUMBER(20) not null,
+ DEFINITION_ID NUMBER(20) not null,
+ INSTANCE_ID NUMBER(20) not null,
+ NODE_CODE VARCHAR2(100),
+ NODE_NAME VARCHAR2(100),
+ NODE_TYPE NUMBER(1),
+ FLOW_STATUS VARCHAR2(20),
+ FORM_CUSTOM VARCHAR2(1) default 'N',
+ FORM_PATH VARCHAR2(100),
+ CREATE_TIME DATE,
+ CREATE_BY VARCHAR2(64) default '',
+ UPDATE_TIME DATE,
+ UPDATE_BY VARCHAR2(64) default '',
+ DEL_FLAG VARCHAR2(1) default '0',
+ TENANT_ID VARCHAR2(40)
+);
+
+alter table FLOW_TASK
+ add constraint PK_FLOW_TASK primary key (ID);
+
+comment on table FLOW_TASK is '寰呭姙浠诲姟琛�';
+comment on column FLOW_TASK.ID is '涓婚敭id';
+comment on column FLOW_TASK.DEFINITION_ID is '瀵瑰簲flow_definition琛ㄧ殑id';
+comment on column FLOW_TASK.INSTANCE_ID is '瀵瑰簲flow_instance琛ㄧ殑id';
+comment on column FLOW_TASK.NODE_CODE is '鑺傜偣缂栫爜';
+comment on column FLOW_TASK.NODE_NAME is '鑺傜偣鍚嶇О';
+comment on column FLOW_TASK.NODE_TYPE is '鑺傜偣绫诲瀷 (0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧)';
+comment on column FLOW_TASK.FLOW_STATUS is '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�';
+comment on column FLOW_TASK.FORM_CUSTOM is '瀹℃壒琛ㄥ崟鏄惁鑷畾涔� (Y鏄� N鍚�)';
+comment on column FLOW_TASK.FORM_PATH is '瀹℃壒琛ㄥ崟璺緞';
+comment on column FLOW_TASK.CREATE_TIME is '鍒涘缓鏃堕棿';
+comment on column FLOW_TASK.CREATE_BY is '鍒涘缓浜�';
+comment on column FLOW_TASK.UPDATE_TIME is '鏇存柊鏃堕棿';
+comment on column FLOW_TASK.UPDATE_BY is '鏇存柊浜�';
+comment on column FLOW_TASK.DEL_FLAG is '鍒犻櫎鏍囧織';
+comment on column FLOW_TASK.TENANT_ID is '绉熸埛id';
+
+create table FLOW_HIS_TASK
+(
+ ID NUMBER(20) not null,
+ DEFINITION_ID NUMBER(20) not null,
+ INSTANCE_ID NUMBER(20) not null,
+ TASK_ID NUMBER(20) not null,
+ NODE_CODE VARCHAR2(100),
+ NODE_NAME VARCHAR2(100),
+ NODE_TYPE NUMBER(1),
+ TARGET_NODE_CODE VARCHAR2(200),
+ TARGET_NODE_NAME VARCHAR2(200),
+ APPROVER VARCHAR2(40),
+ COOPERATE_TYPE NUMBER(1) default 0,
+ COLLABORATOR VARCHAR2(500),
+ SKIP_TYPE VARCHAR2(10),
+ FLOW_STATUS VARCHAR2(20),
+ FORM_CUSTOM VARCHAR2(1) default 'N',
+ FORM_PATH VARCHAR2(100),
+ MESSAGE VARCHAR2(500),
+ VARIABLE CLOB,
+ EXT CLOB,
+ CREATE_TIME DATE,
+ UPDATE_TIME DATE,
+ DEL_FLAG VARCHAR2(1) default '0',
+ TENANT_ID VARCHAR2(40)
+
+);
+
+alter table FLOW_HIS_TASK
+ add constraint PK_FLOW_HIS_TASK primary key (ID);
+
+comment on table FLOW_HIS_TASK is '鍘嗗彶浠诲姟璁板綍琛�';
+comment on column FLOW_HIS_TASK.ID is '涓婚敭id';
+comment on column FLOW_HIS_TASK.DEFINITION_ID is '瀵瑰簲flow_definition琛ㄧ殑id';
+comment on column FLOW_HIS_TASK.INSTANCE_ID is '瀵瑰簲flow_instance琛ㄧ殑id';
+comment on column FLOW_HIS_TASK.TASK_ID is '瀵瑰簲flow_task琛ㄧ殑id';
+comment on column FLOW_HIS_TASK.NODE_CODE is '寮�濮嬭妭鐐圭紪鐮�';
+comment on column FLOW_HIS_TASK.NODE_NAME is '寮�濮嬭妭鐐瑰悕绉�';
+comment on column FLOW_HIS_TASK.NODE_TYPE is '寮�濮嬭妭鐐圭被鍨� (0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧)';
+comment on column FLOW_HIS_TASK.TARGET_NODE_CODE is '鐩爣鑺傜偣缂栫爜';
+comment on column FLOW_HIS_TASK.TARGET_NODE_NAME is '鐩爣鑺傜偣鍚嶇О';
+comment on column FLOW_HIS_TASK.SKIP_TYPE is '娴佽浆绫诲瀷锛圥ASS閫氳繃 REJECT閫�鍥� NONE鏃犲姩浣滐級';
+comment on column FLOW_HIS_TASK.FLOW_STATUS is '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�';
+comment on column FLOW_HIS_TASK.FORM_CUSTOM is '瀹℃壒琛ㄥ崟鏄惁鑷畾涔� (Y鏄� N鍚�)';
+comment on column FLOW_HIS_TASK.FORM_PATH is '瀹℃壒琛ㄥ崟璺緞';
+comment on column FLOW_HIS_TASK.MESSAGE is '瀹℃壒鎰忚';
+comment on column FLOW_HIS_TASK.VARIABLE is '浠诲姟鍙橀噺';
+comment on column FLOW_HIS_TASK.EXT is '鎵╁睍瀛楁锛岄鐣欑粰涓氬姟绯荤粺浣跨敤';
+comment on column FLOW_HIS_TASK.CREATE_TIME is '浠诲姟寮�濮嬫椂闂�';
+comment on column FLOW_HIS_TASK.UPDATE_TIME is '瀹℃壒瀹屾垚鏃堕棿';
+comment on column FLOW_HIS_TASK.DEL_FLAG is '鍒犻櫎鏍囧織';
+comment on column FLOW_HIS_TASK.TENANT_ID is '绉熸埛id';
+comment on column FLOW_HIS_TASK.APPROVER is '瀹℃壒鑰�';
+comment on column FLOW_HIS_TASK.COOPERATE_TYPE is '鍗忎綔鏂瑰紡(1瀹℃壒 2杞姙 3濮旀淳 4浼氱 5绁ㄧ 6鍔犵 7鍑忕)';
+comment on column FLOW_HIS_TASK.COLLABORATOR is '鍗忎綔浜�';
+
+create table FLOW_USER
+(
+ ID NUMBER(20) not null,
+ TYPE VARCHAR2(1) not null,
+ PROCESSED_BY VARCHAR2(80),
+ ASSOCIATED NUMBER(20) not null,
+ CREATE_TIME DATE,
+ CREATE_BY VARCHAR2(64) default '',
+ UPDATE_TIME DATE,
+ UPDATE_BY VARCHAR2(64) default '',
+ DEL_FLAG VARCHAR2(1) default '0',
+ TENANT_ID VARCHAR2(40)
+);
+
+alter table FLOW_USER
+ add constraint PK_FLOW_USER primary key (ID);
+
+comment on table FLOW_USER is '寰呭姙浠诲姟琛�';
+comment on column FLOW_USER.ID is '涓婚敭id';
+comment on column FLOW_USER.TYPE is '浜哄憳绫诲瀷锛�1寰呭姙浠诲姟鐨勫鎵逛汉鏉冮檺 2寰呭姙浠诲姟鐨勮浆鍔炰汉鏉冮檺 3寰呭姙浠诲姟鐨勫鎵樹汉鏉冮檺锛�';
+comment on column FLOW_USER.PROCESSED_BY is '鏉冮檺浜�)';
+comment on column FLOW_USER.ASSOCIATED is '浠诲姟琛╥d';
+comment on column FLOW_USER.CREATE_TIME is '鍒涘缓鏃堕棿';
+comment on column FLOW_USER.CREATE_BY is '鍒涘缓浜�';
+comment on column FLOW_USER.UPDATE_TIME is '鏇存柊鏃堕棿';
+comment on column FLOW_USER.UPDATE_BY is '鏇存柊浜�';
+comment on column FLOW_USER.DEL_FLAG is '鍒犻櫎鏍囧織';
+comment on column FLOW_USER.TENANT_ID is '绉熸埛id';
+
+create index USER_PROCESSED_TYPE on FLOW_USER (PROCESSED_BY, TYPE);
+create index USER_ASSOCIATED_IDX on FLOW_USER (ASSOCIATED);
+
+-- ----------------------------
+-- 娴佺▼鍒嗙被琛�
+-- ----------------------------
+CREATE TABLE flow_category
+(
+ category_id NUMBER(20) NOT NULL,
+ tenant_id VARCHAR2(20) DEFAULT '000000',
+ parent_id NUMBER(20) DEFAULT 0,
+ ancestors VARCHAR2(500) DEFAULT '',
+ category_name VARCHAR2(30) NOT NULL,
+ order_num NUMBER(4) DEFAULT 0,
+ del_flag CHAR(1) DEFAULT '0',
+ create_dept NUMBER(20),
+ create_by NUMBER(20),
+ create_time DATE,
+ update_by NUMBER(20),
+ update_time DATE
+);
+
+alter table flow_category add constraint pk_flow_category primary key (category_id);
+
+COMMENT ON TABLE flow_category IS '娴佺▼鍒嗙被';
+COMMENT ON COLUMN flow_category.category_id IS '娴佺▼鍒嗙被ID';
+COMMENT ON COLUMN flow_category.tenant_id IS '绉熸埛缂栧彿';
+COMMENT ON COLUMN flow_category.parent_id IS '鐖舵祦绋嬪垎绫籭d';
+COMMENT ON COLUMN flow_category.ancestors IS '绁栫骇鍒楄〃';
+COMMENT ON COLUMN flow_category.category_name IS '娴佺▼鍒嗙被鍚嶇О';
+COMMENT ON COLUMN flow_category.order_num IS '鏄剧ず椤哄簭';
+COMMENT ON COLUMN flow_category.del_flag IS '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+COMMENT ON COLUMN flow_category.create_dept IS '鍒涘缓閮ㄩ棬';
+COMMENT ON COLUMN flow_category.create_by IS '鍒涘缓鑰�';
+COMMENT ON COLUMN flow_category.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_category.update_by IS '鏇存柊鑰�';
+COMMENT ON COLUMN flow_category.update_time IS '鏇存柊鏃堕棿';
+
+INSERT INTO flow_category VALUES (100, '000000', 0, '0', 'OA瀹℃壒', 0, '0', 103, 1, SYSDATE, NULL, NULL);
+INSERT INTO flow_category VALUES (101, '000000', 100, '0,100', '鍋囧嫟绠$悊', 0, '0', 103, 1, SYSDATE, NULL, NULL);
+INSERT INTO flow_category VALUES (102, '000000', 100, '0,100', '浜轰簨绠$悊', 1, '0', 103, 1, SYSDATE, NULL, NULL);
+INSERT INTO flow_category VALUES (103, '000000', 101, '0,100,101', '璇峰亣', 0, '0', 103, 1, SYSDATE, NULL, NULL);
+INSERT INTO flow_category VALUES (104, '000000', 101, '0,100,101', '鍑哄樊', 1, '0', 103, 1, SYSDATE, NULL, NULL);
+INSERT INTO flow_category VALUES (105, '000000', 101, '0,100,101', '鍔犵彮', 2, '0', 103, 1, SYSDATE, NULL, NULL);
+INSERT INTO flow_category VALUES (106, '000000', 101, '0,100,101', '鎹㈢彮', 3, '0', 103, 1, SYSDATE, NULL, NULL);
+INSERT INTO flow_category VALUES (107, '000000', 101, '0,100,101', '澶栧嚭', 4, '0', 103, 1, SYSDATE, NULL, NULL);
+INSERT INTO flow_category VALUES (108, '000000', 102, '0,100,102', '杞', 1, '0', 103, 1, SYSDATE, NULL, NULL);
+INSERT INTO flow_category VALUES (109, '000000', 102, '0,100,102', '绂昏亴', 2, '0', 103, 1, SYSDATE, NULL, NULL);
+
+-- ----------------------------
+-- 娴佺▼spel琛ㄨ揪寮忓畾涔夎〃
+-- ----------------------------
+CREATE TABLE flow_spel (
+ id NUMBER(20) NOT NULL,
+ component_name VARCHAR2(255),
+ method_name VARCHAR2(255),
+ method_params VARCHAR2(255),
+ view_spel VARCHAR2(255),
+ remark VARCHAR2(255),
+ status CHAR(1) DEFAULT '0',
+ del_flag CHAR(1) DEFAULT '0',
+ create_dept NUMBER(20),
+ create_by NUMBER(20),
+ create_time DATE,
+ update_by NUMBER(20),
+ update_time DATE
+);
+
+alter table flow_spel add constraint pk_flow_spel primary key (id);
+
+COMMENT ON TABLE flow_spel IS '娴佺▼spel琛ㄨ揪寮忓畾涔夎〃';
+COMMENT ON COLUMN flow_spel.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_spel.component_name IS '缁勪欢鍚嶇О';
+COMMENT ON COLUMN flow_spel.method_name IS '鏂规硶鍚�';
+COMMENT ON COLUMN flow_spel.method_params IS '鍙傛暟';
+COMMENT ON COLUMN flow_spel.view_spel IS '棰勮spel琛ㄨ揪寮�';
+COMMENT ON COLUMN flow_spel.remark IS '澶囨敞';
+COMMENT ON COLUMN flow_spel.status IS '鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+COMMENT ON COLUMN flow_spel.del_flag IS '鍒犻櫎鏍囧織';
+COMMENT ON COLUMN flow_spel.create_dept IS '鍒涘缓閮ㄩ棬';
+COMMENT ON COLUMN flow_spel.create_by IS '鍒涘缓鑰�';
+COMMENT ON COLUMN flow_spel.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_spel.update_by IS '鏇存柊鑰�';
+COMMENT ON COLUMN flow_spel.update_time IS '鏇存柊鏃堕棿';
+
+INSERT INTO flow_spel VALUES (1, 'spelRuleComponent', 'selectDeptLeaderById', 'initiatorDeptId', '#{@spelRuleComponent.selectDeptLeaderById(#initiatorDeptId)}', '鏍规嵁閮ㄩ棬id鑾峰彇閮ㄩ棬璐熻矗浜�', '0', '0', 103, 1, SYSDATE, 1, SYSDATE);
+INSERT INTO flow_spel VALUES (2, NULL, NULL, 'initiator', '${initiator}', '娴佺▼鍙戣捣浜�', '0', '0', 103, 1, SYSDATE, 1, SYSDATE);
+
+-- ----------------------------
+-- 娴佺▼瀹炰緥涓氬姟鎵╁睍琛�
+-- ----------------------------
+CREATE TABLE flow_instance_biz_ext (
+ id NUMBER(20),
+ tenant_id VARCHAR2(20) DEFAULT '000000',
+ create_dept NUMBER(20),
+ create_by NUMBER(20),
+ create_time TIMESTAMP,
+ update_by NUMBER(20),
+ update_time TIMESTAMP,
+ business_code VARCHAR2(255),
+ business_title VARCHAR2(1000),
+ del_flag CHAR(1) DEFAULT '0',
+ instance_id NUMBER(20),
+ business_id VARCHAR2(255)
+);
+
+alter table flow_instance_biz_ext add constraint pk_fi_biz_ext primary key (id);
+
+COMMENT ON TABLE flow_instance_biz_ext IS '娴佺▼瀹炰緥涓氬姟鎵╁睍琛�';
+COMMENT ON COLUMN flow_instance_biz_ext.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_instance_biz_ext.tenant_id IS '绉熸埛缂栧彿';
+COMMENT ON COLUMN flow_instance_biz_ext.create_dept IS '鍒涘缓閮ㄩ棬';
+COMMENT ON COLUMN flow_instance_biz_ext.create_by IS '鍒涘缓鑰�';
+COMMENT ON COLUMN flow_instance_biz_ext.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_instance_biz_ext.update_by IS '鏇存柊鑰�';
+COMMENT ON COLUMN flow_instance_biz_ext.update_time IS '鏇存柊鏃堕棿';
+COMMENT ON COLUMN flow_instance_biz_ext.business_code IS '涓氬姟缂栫爜';
+COMMENT ON COLUMN flow_instance_biz_ext.business_title IS '涓氬姟鏍囬';
+COMMENT ON COLUMN flow_instance_biz_ext.del_flag IS '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+COMMENT ON COLUMN flow_instance_biz_ext.instance_id IS '娴佺▼瀹炰緥Id';
+COMMENT ON COLUMN flow_instance_biz_ext.business_id IS '涓氬姟Id';
+
+-- ----------------------------
+-- 璇峰亣鍗曚俊鎭�
+-- ----------------------------
+CREATE TABLE test_leave
+(
+ id NUMBER (20) NOT NULL,
+ tenant_id VARCHAR2 (20) DEFAULT '000000',
+ apply_code VARCHAR2 (50) NOT NULL,
+ leave_type VARCHAR2 (255) NOT NULL,
+ start_date DATE NOT NULL,
+ end_date DATE NOT NULL,
+ leave_days NUMBER (10) NOT NULL,
+ remark VARCHAR2 (255),
+ status VARCHAR2 (255),
+ create_dept NUMBER (20),
+ create_by NUMBER (20),
+ create_time DATE,
+ update_by NUMBER (20),
+ update_time DATE
+);
+
+alter table test_leave add constraint pk_test_leave primary key (id);
+
+COMMENT ON TABLE test_leave IS '璇峰亣鐢宠琛�';
+COMMENT ON COLUMN test_leave.id IS 'ID';
+COMMENT ON COLUMN test_leave.tenant_id IS '绉熸埛缂栧彿';
+COMMENT ON COLUMN test_leave.apply_code IS '鐢宠缂栧彿';
+COMMENT ON COLUMN test_leave.leave_type IS '璇峰亣绫诲瀷';
+COMMENT ON COLUMN test_leave.start_date IS '寮�濮嬫椂闂�';
+COMMENT ON COLUMN test_leave.end_date IS '缁撴潫鏃堕棿';
+COMMENT ON COLUMN test_leave.leave_days IS '璇峰亣澶╂暟';
+COMMENT ON COLUMN test_leave.remark IS '璇峰亣鍘熷洜';
+COMMENT ON COLUMN test_leave.status IS '鐘舵��';
+COMMENT ON COLUMN test_leave.create_dept IS '鍒涘缓閮ㄩ棬';
+COMMENT ON COLUMN test_leave.create_by IS '鍒涘缓鑰�';
+COMMENT ON COLUMN test_leave.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN test_leave.update_by IS '鏇存柊鑰�';
+COMMENT ON COLUMN test_leave.update_time IS '鏇存柊鏃堕棿';
+
+INSERT INTO sys_menu VALUES ('11616', '宸ヤ綔娴�', '0', '6', 'workflow', '', '', '1', '0', 'M', '0', '0', '', 'workflow', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11618', '鎴戠殑浠诲姟', '0', '7', 'task', '', '', '1', '0', 'M', '0', '0', '', 'my-task', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11619', '鎴戠殑寰呭姙', '11618', '2', 'taskWaiting', 'workflow/task/taskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11632', '鎴戠殑宸插姙', '11618', '3', 'taskFinish', 'workflow/task/taskFinish', '', '1', '1', 'C', '0', '0', '', 'finish', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11633', '鎴戠殑鎶勯��', '11618', '4', 'taskCopyList', 'workflow/task/taskCopyList', '', '1', '1', 'C', '0', '0', '', 'my-copy', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11620', '娴佺▼瀹氫箟', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'process-definition', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11621', '娴佺▼瀹炰緥', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11622', '娴佺▼鍒嗙被', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'category', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11629', '鎴戝彂璧风殑', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'guide', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11630', '娴佺▼鐩戞帶', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11631', '寰呭姙浠诲姟', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11700', '娴佺▼璁捐', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, SYSDATE, NULL, NULL, '/workflow/processDefinition');
+INSERT INTO sys_menu VALUES ('11701', '璇峰亣鐢宠', '11616', '6', 'leaveEdit/index', 'workflow/leave/leaveEdit', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
+
+INSERT INTO sys_menu VALUES ('11623', '娴佺▼鍒嗙被鏌ヨ', '11622', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11624', '娴佺▼鍒嗙被鏂板', '11622', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11625', '娴佺▼鍒嗙被淇敼', '11622', '3', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11626', '娴佺▼鍒嗙被鍒犻櫎', '11622', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11627', '娴佺▼鍒嗙被瀵煎嚭', '11622', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
+
+INSERT INTO sys_menu VALUES ('11801', '娴佺▼琛ㄨ揪寮�', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, SYSDATE, 1, SYSDATE, '娴佺▼杈惧紡瀹氫箟鑿滃崟');
+INSERT INTO sys_menu VALUES ('11802', '娴佺▼spel琛ㄨ揪寮忓畾涔夋煡璇�', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11803', '娴佺▼spel琛ㄨ揪寮忓畾涔夋柊澧�', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11804', '娴佺▼spel琛ㄨ揪寮忓畾涔変慨鏀�', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11805', '娴佺▼spel琛ㄨ揪寮忓畾涔夊垹闄�', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11806', '娴佺▼spel琛ㄨ揪寮忓畾涔夊鍑�', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
+
+INSERT INTO sys_menu VALUES ('11638', '璇峰亣鐢宠', '5', '1', 'leave', 'workflow/leave/index', '', '1', '0', 'C', '0', '0', 'workflow:leave:list', '#', 103, 1, SYSDATE, NULL, NULL, '璇峰亣鐢宠鑿滃崟');
+INSERT INTO sys_menu VALUES ('11639', '璇峰亣鐢宠鏌ヨ', '11638', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11640', '璇峰亣鐢宠鏂板', '11638', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11641', '璇峰亣鐢宠淇敼', '11638', '3', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11642', '璇峰亣鐢宠鍒犻櫎', '11638', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11643', '璇峰亣鐢宠瀵煎嚭', '11638', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
+
+INSERT INTO sys_dict_type VALUES (13, '000000', '涓氬姟鐘舵��', 'wf_business_status', 103, 1, SYSDATE, NULL, NULL, '涓氬姟鐘舵�佸垪琛�');
+INSERT INTO sys_dict_type VALUES (14, '000000', '琛ㄥ崟绫诲瀷', 'wf_form_type', 103, 1, SYSDATE, NULL, NULL, '琛ㄥ崟绫诲瀷鍒楄〃');
+INSERT INTO sys_dict_type VALUES (15, '000000', '浠诲姟鐘舵��', 'wf_task_status', 103, 1, SYSDATE, NULL, NULL, '浠诲姟鐘舵��');
+INSERT INTO sys_dict_data VALUES (39, '000000', 1, '宸叉挙閿�', 'cancel', 'wf_business_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '宸叉挙閿�');
+INSERT INTO sys_dict_data VALUES (40, '000000', 2, '鑽夌', 'draft', 'wf_business_status', '', 'info', 'N', 103, 1, SYSDATE, NULL, NULL, '鑽夌');
+INSERT INTO sys_dict_data VALUES (41, '000000', 3, '寰呭鏍�', 'waiting', 'wf_business_status', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '寰呭鏍�');
+INSERT INTO sys_dict_data VALUES (42, '000000', 4, '宸插畬鎴�', 'finish', 'wf_business_status', '', 'success', 'N', 103, 1, SYSDATE, NULL, NULL, '宸插畬鎴�');
+INSERT INTO sys_dict_data VALUES (43, '000000', 5, '宸蹭綔搴�', 'invalid', 'wf_business_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '宸蹭綔搴�');
+INSERT INTO sys_dict_data VALUES (44, '000000', 6, '宸查��鍥�', 'back', 'wf_business_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '宸查��鍥�');
+INSERT INTO sys_dict_data VALUES (45, '000000', 7, '宸茬粓姝�', 'termination', 'wf_business_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '宸茬粓姝�');
+INSERT INTO sys_dict_data VALUES (46, '000000', 1, '鑷畾涔夎〃鍗�', 'static', 'wf_form_type', '', 'success', 'N', 103, 1, SYSDATE, NULL, NULL, '鑷畾涔夎〃鍗�');
+INSERT INTO sys_dict_data VALUES (47, '000000', 2, '鍔ㄦ�佽〃鍗�', 'dynamic', 'wf_form_type', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '鍔ㄦ�佽〃鍗�');
+INSERT INTO sys_dict_data VALUES (48, '000000', 1, '鎾ら攢', 'cancel', 'wf_task_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '鎾ら攢');
+INSERT INTO sys_dict_data VALUES (49, '000000', 2, '閫氳繃', 'pass', 'wf_task_status', '', 'success', 'N', 103, 1, SYSDATE, NULL, NULL, '閫氳繃');
+INSERT INTO sys_dict_data VALUES (50, '000000', 3, '寰呭鏍�', 'waiting', 'wf_task_status', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '寰呭鏍�');
+INSERT INTO sys_dict_data VALUES (51, '000000', 4, '浣滃簾', 'invalid', 'wf_task_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '浣滃簾');
+INSERT INTO sys_dict_data VALUES (52, '000000', 5, '閫�鍥�', 'back', 'wf_task_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '閫�鍥�');
+INSERT INTO sys_dict_data VALUES (53, '000000', 6, '缁堟', 'termination', 'wf_task_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '缁堟');
+INSERT INTO sys_dict_data VALUES (54, '000000', 7, '杞姙', 'transfer', 'wf_task_status', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '杞姙');
+INSERT INTO sys_dict_data VALUES (55, '000000', 8, '濮旀墭', 'depute', 'wf_task_status', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '濮旀墭');
+INSERT INTO sys_dict_data VALUES (56, '000000', 9, '鎶勯��', 'copy', 'wf_task_status', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '鎶勯��');
+INSERT INTO sys_dict_data VALUES (57, '000000', 10, '鍔犵', 'sign', 'wf_task_status', '', 'primary', 'N', 103, 1, SYSDATE, NULL, NULL, '鍔犵');
+INSERT INTO sys_dict_data VALUES (58, '000000', 11, '鍑忕', 'sign_off', 'wf_task_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '鍑忕');
+INSERT INTO sys_dict_data VALUES (59, '000000', 11, '瓒呮椂', 'timeout', 'wf_task_status', '', 'danger', 'N', 103, 1, SYSDATE, NULL, NULL, '瓒呮椂');
diff --git a/RuoYi-Vue-Plus/script/sql/postgres/postgres_ry_job.sql b/RuoYi-Vue-Plus/script/sql/postgres/postgres_ry_job.sql
new file mode 100755
index 0000000..9980c3b
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/postgres/postgres_ry_job.sql
@@ -0,0 +1,871 @@
+/*
+ SnailJob Database Transfer Tool
+ Source Server Type : MySQL
+ Target Server Type : PostgreSQL
+ Date: 2025-06-21 23:23:10
+*/
+
+
+-- sj_namespace
+CREATE TABLE sj_namespace
+(
+ id bigserial PRIMARY KEY,
+ name varchar(64) NOT NULL,
+ unique_id varchar(64) NOT NULL,
+ description varchar(256) NOT NULL DEFAULT '',
+ deleted smallint NOT NULL DEFAULT 0,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_namespace_01 ON sj_namespace (name);
+
+COMMENT ON COLUMN sj_namespace.id IS '涓婚敭';
+COMMENT ON COLUMN sj_namespace.name IS '鍚嶇О';
+COMMENT ON COLUMN sj_namespace.unique_id IS '鍞竴id';
+COMMENT ON COLUMN sj_namespace.description IS '鎻忚堪';
+COMMENT ON COLUMN sj_namespace.deleted IS '閫昏緫鍒犻櫎 1銆佸垹闄�';
+COMMENT ON COLUMN sj_namespace.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_namespace.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_namespace IS '鍛藉悕绌洪棿';
+
+INSERT INTO sj_namespace VALUES (1, 'Development', 'dev', '', 0, now(), now());
+INSERT INTO sj_namespace VALUES (2, 'Production', 'prod', '', 0, now(), now());
+
+-- sj_group_config
+CREATE TABLE sj_group_config
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL DEFAULT '',
+ description varchar(256) NOT NULL DEFAULT '',
+ token varchar(64) NOT NULL DEFAULT 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT',
+ group_status smallint NOT NULL DEFAULT 0,
+ version int NOT NULL,
+ group_partition int NOT NULL,
+ id_generator_mode smallint NOT NULL DEFAULT 1,
+ init_scene smallint NOT NULL DEFAULT 0,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE UNIQUE INDEX uk_sj_group_config_01 ON sj_group_config (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_group_config.id IS '涓婚敭';
+COMMENT ON COLUMN sj_group_config.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_group_config.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_group_config.description IS '缁勬弿杩�';
+COMMENT ON COLUMN sj_group_config.token IS 'token';
+COMMENT ON COLUMN sj_group_config.group_status IS '缁勭姸鎬� 0銆佹湭鍚敤 1銆佸惎鐢�';
+COMMENT ON COLUMN sj_group_config.version IS '鐗堟湰鍙�';
+COMMENT ON COLUMN sj_group_config.group_partition IS '鍒嗗尯';
+COMMENT ON COLUMN sj_group_config.id_generator_mode IS '鍞竴id鐢熸垚妯″紡 榛樿鍙锋妯″紡';
+COMMENT ON COLUMN sj_group_config.init_scene IS '鏄惁鍒濆鍖栧満鏅� 0:鍚� 1:鏄�';
+COMMENT ON COLUMN sj_group_config.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_group_config.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_group_config IS '缁勯厤缃�';
+
+INSERT INTO sj_group_config VALUES (1, 'dev', 'ruoyi_group', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, now(), now());
+INSERT INTO sj_group_config VALUES (2, 'prod', 'ruoyi_group', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, now(), now());
+
+-- sj_notify_config
+CREATE TABLE sj_notify_config
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL,
+ notify_name varchar(64) NOT NULL DEFAULT '',
+ system_task_type smallint NOT NULL DEFAULT 3,
+ notify_status smallint NOT NULL DEFAULT 0,
+ recipient_ids varchar(128) NOT NULL,
+ notify_threshold int NOT NULL DEFAULT 0,
+ notify_scene smallint NOT NULL DEFAULT 0,
+ rate_limiter_status smallint NOT NULL DEFAULT 0,
+ rate_limiter_threshold int NOT NULL DEFAULT 0,
+ description varchar(256) NOT NULL DEFAULT '',
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_notify_config_01 ON sj_notify_config (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_notify_config.id IS '涓婚敭';
+COMMENT ON COLUMN sj_notify_config.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_notify_config.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_notify_config.notify_name IS '閫氱煡鍚嶇О';
+COMMENT ON COLUMN sj_notify_config.system_task_type IS '浠诲姟绫诲瀷 1. 閲嶈瘯浠诲姟 2. 閲嶈瘯鍥炶皟 3銆丣OB浠诲姟 4銆乄ORKFLOW浠诲姟';
+COMMENT ON COLUMN sj_notify_config.notify_status IS '閫氱煡鐘舵�� 0銆佹湭鍚敤 1銆佸惎鐢�';
+COMMENT ON COLUMN sj_notify_config.recipient_ids IS '鎺ユ敹浜篿d鍒楄〃';
+COMMENT ON COLUMN sj_notify_config.notify_threshold IS '閫氱煡闃堝��';
+COMMENT ON COLUMN sj_notify_config.notify_scene IS '閫氱煡鍦烘櫙';
+COMMENT ON COLUMN sj_notify_config.rate_limiter_status IS '闄愭祦鐘舵�� 0銆佹湭鍚敤 1銆佸惎鐢�';
+COMMENT ON COLUMN sj_notify_config.rate_limiter_threshold IS '姣忕闄愭祦闃堝��';
+COMMENT ON COLUMN sj_notify_config.description IS '鎻忚堪';
+COMMENT ON COLUMN sj_notify_config.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_notify_config.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_notify_config IS '閫氱煡閰嶇疆';
+
+-- sj_notify_recipient
+CREATE TABLE sj_notify_recipient
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ recipient_name varchar(64) NOT NULL,
+ notify_type smallint NOT NULL DEFAULT 0,
+ notify_attribute varchar(512) NOT NULL,
+ description varchar(256) NOT NULL DEFAULT '',
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_notify_recipient_01 ON sj_notify_recipient (namespace_id);
+
+COMMENT ON COLUMN sj_notify_recipient.id IS '涓婚敭';
+COMMENT ON COLUMN sj_notify_recipient.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_notify_recipient.recipient_name IS '鎺ユ敹浜哄悕绉�';
+COMMENT ON COLUMN sj_notify_recipient.notify_type IS '閫氱煡绫诲瀷 1銆侀拤閽� 2銆侀偖浠� 3銆佷紒涓氬井淇� 4 椋炰功 5 webhook';
+COMMENT ON COLUMN sj_notify_recipient.notify_attribute IS '閰嶇疆灞炴��';
+COMMENT ON COLUMN sj_notify_recipient.description IS '鎻忚堪';
+COMMENT ON COLUMN sj_notify_recipient.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_notify_recipient.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_notify_recipient IS '鍛婅閫氱煡鎺ユ敹浜�';
+
+-- sj_retry_dead_letter
+CREATE TABLE sj_retry_dead_letter
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL,
+ group_id bigint NOT NULL,
+ scene_name varchar(64) NOT NULL,
+ scene_id bigint NOT NULL,
+ idempotent_id varchar(64) NOT NULL,
+ biz_no varchar(64) NOT NULL DEFAULT '',
+ executor_name varchar(512) NOT NULL DEFAULT '',
+ serializer_name varchar(32) NOT NULL DEFAULT 'jackson',
+ args_str text NOT NULL,
+ ext_attrs text NOT NULL,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_retry_dead_letter_01 ON sj_retry_dead_letter (namespace_id, group_name, scene_name);
+CREATE INDEX idx_sj_retry_dead_letter_02 ON sj_retry_dead_letter (idempotent_id);
+CREATE INDEX idx_sj_retry_dead_letter_03 ON sj_retry_dead_letter (biz_no);
+CREATE INDEX idx_sj_retry_dead_letter_04 ON sj_retry_dead_letter (create_dt);
+
+COMMENT ON COLUMN sj_retry_dead_letter.id IS '涓婚敭';
+COMMENT ON COLUMN sj_retry_dead_letter.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_retry_dead_letter.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_retry_dead_letter.group_id IS '缁処d';
+COMMENT ON COLUMN sj_retry_dead_letter.scene_name IS '鍦烘櫙鍚嶇О';
+COMMENT ON COLUMN sj_retry_dead_letter.scene_id IS '鍦烘櫙ID';
+COMMENT ON COLUMN sj_retry_dead_letter.idempotent_id IS '骞傜瓑id';
+COMMENT ON COLUMN sj_retry_dead_letter.biz_no IS '涓氬姟缂栧彿';
+COMMENT ON COLUMN sj_retry_dead_letter.executor_name IS '鎵ц鍣ㄥ悕绉�';
+COMMENT ON COLUMN sj_retry_dead_letter.serializer_name IS '鎵ц鏂规硶鍙傛暟搴忓垪鍖栧櫒鍚嶇О';
+COMMENT ON COLUMN sj_retry_dead_letter.args_str IS '鎵ц鏂规硶鍙傛暟';
+COMMENT ON COLUMN sj_retry_dead_letter.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_retry_dead_letter.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON TABLE sj_retry_dead_letter IS '姝讳俊闃熷垪琛�';
+
+-- sj_retry
+CREATE TABLE sj_retry
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL,
+ group_id bigint NOT NULL,
+ scene_name varchar(64) NOT NULL,
+ scene_id bigint NOT NULL,
+ idempotent_id varchar(64) NOT NULL,
+ biz_no varchar(64) NOT NULL DEFAULT '',
+ executor_name varchar(512) NOT NULL DEFAULT '',
+ args_str text NOT NULL,
+ ext_attrs text NOT NULL,
+ serializer_name varchar(32) NOT NULL DEFAULT 'jackson',
+ next_trigger_at bigint NOT NULL,
+ retry_count int NOT NULL DEFAULT 0,
+ retry_status smallint NOT NULL DEFAULT 0,
+ task_type smallint NOT NULL DEFAULT 1,
+ bucket_index int NOT NULL DEFAULT 0,
+ parent_id bigint NOT NULL DEFAULT 0,
+ deleted bigint NOT NULL DEFAULT 0,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE UNIQUE INDEX uk_sj_retry_01 ON sj_retry (scene_id, task_type, idempotent_id, deleted);
+
+CREATE INDEX idx_sj_retry_01 ON sj_retry (biz_no);
+CREATE INDEX idx_sj_retry_02 ON sj_retry (idempotent_id);
+CREATE INDEX idx_sj_retry_03 ON sj_retry (retry_status, bucket_index);
+CREATE INDEX idx_sj_retry_04 ON sj_retry (parent_id);
+CREATE INDEX idx_sj_retry_05 ON sj_retry (create_dt);
+
+COMMENT ON COLUMN sj_retry.id IS '涓婚敭';
+COMMENT ON COLUMN sj_retry.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_retry.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_retry.group_id IS '缁処d';
+COMMENT ON COLUMN sj_retry.scene_name IS '鍦烘櫙鍚嶇О';
+COMMENT ON COLUMN sj_retry.scene_id IS '鍦烘櫙ID';
+COMMENT ON COLUMN sj_retry.idempotent_id IS '骞傜瓑id';
+COMMENT ON COLUMN sj_retry.biz_no IS '涓氬姟缂栧彿';
+COMMENT ON COLUMN sj_retry.executor_name IS '鎵ц鍣ㄥ悕绉�';
+COMMENT ON COLUMN sj_retry.args_str IS '鎵ц鏂规硶鍙傛暟';
+COMMENT ON COLUMN sj_retry.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_retry.serializer_name IS '鎵ц鏂规硶鍙傛暟搴忓垪鍖栧櫒鍚嶇О';
+COMMENT ON COLUMN sj_retry.next_trigger_at IS '涓嬫瑙﹀彂鏃堕棿';
+COMMENT ON COLUMN sj_retry.retry_count IS '閲嶈瘯娆℃暟';
+COMMENT ON COLUMN sj_retry.retry_status IS '閲嶈瘯鐘舵�� 0銆侀噸璇曚腑 1銆佹垚鍔� 2銆佹渶澶ч噸璇曟鏁�';
+COMMENT ON COLUMN sj_retry.task_type IS '浠诲姟绫诲瀷 1銆侀噸璇曟暟鎹� 2銆佸洖璋冩暟鎹�';
+COMMENT ON COLUMN sj_retry.bucket_index IS 'bucket';
+COMMENT ON COLUMN sj_retry.parent_id IS '鐖惰妭鐐筰d';
+COMMENT ON COLUMN sj_retry.deleted IS '閫昏緫鍒犻櫎';
+COMMENT ON COLUMN sj_retry.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_retry.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_retry IS '閲嶈瘯淇℃伅琛�';
+
+-- sj_retry_task
+CREATE TABLE sj_retry_task
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL,
+ scene_name varchar(64) NOT NULL,
+ retry_id bigint NOT NULL,
+ ext_attrs text NOT NULL,
+ task_status smallint NOT NULL DEFAULT 1,
+ task_type smallint NOT NULL DEFAULT 1,
+ operation_reason smallint NOT NULL DEFAULT 0,
+ client_info varchar(128) NULL DEFAULT NULL,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_retry_task_01 ON sj_retry_task (namespace_id, group_name, scene_name);
+CREATE INDEX idx_sj_retry_task_02 ON sj_retry_task (task_status);
+CREATE INDEX idx_sj_retry_task_03 ON sj_retry_task (create_dt);
+CREATE INDEX idx_sj_retry_task_04 ON sj_retry_task (retry_id);
+
+COMMENT ON COLUMN sj_retry_task.id IS '涓婚敭';
+COMMENT ON COLUMN sj_retry_task.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_retry_task.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_retry_task.scene_name IS '鍦烘櫙鍚嶇О';
+COMMENT ON COLUMN sj_retry_task.retry_id IS '閲嶈瘯淇℃伅Id';
+COMMENT ON COLUMN sj_retry_task.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_retry_task.task_status IS '閲嶈瘯鐘舵��';
+COMMENT ON COLUMN sj_retry_task.task_type IS '浠诲姟绫诲瀷 1銆侀噸璇曟暟鎹� 2銆佸洖璋冩暟鎹�';
+COMMENT ON COLUMN sj_retry_task.operation_reason IS '鎿嶄綔鍘熷洜';
+COMMENT ON COLUMN sj_retry_task.client_info IS '瀹㈡埛绔湴鍧� clientId#ip:port';
+COMMENT ON COLUMN sj_retry_task.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_retry_task.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_retry_task IS '閲嶈瘯浠诲姟琛�';
+
+-- sj_retry_task_log_message
+CREATE TABLE sj_retry_task_log_message
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL,
+ retry_id bigint NOT NULL,
+ retry_task_id bigint NOT NULL,
+ message text NOT NULL,
+ log_num int NOT NULL DEFAULT 1,
+ real_time bigint NOT NULL DEFAULT 0,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_retry_task_log_message_01 ON sj_retry_task_log_message (namespace_id, group_name, retry_task_id);
+CREATE INDEX idx_sj_retry_task_log_message_02 ON sj_retry_task_log_message (create_dt);
+
+COMMENT ON COLUMN sj_retry_task_log_message.id IS '涓婚敭';
+COMMENT ON COLUMN sj_retry_task_log_message.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_retry_task_log_message.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_retry_task_log_message.retry_id IS '閲嶈瘯淇℃伅Id';
+COMMENT ON COLUMN sj_retry_task_log_message.retry_task_id IS '閲嶈瘯浠诲姟Id';
+COMMENT ON COLUMN sj_retry_task_log_message.message IS '寮傚父淇℃伅';
+COMMENT ON COLUMN sj_retry_task_log_message.log_num IS '鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_retry_task_log_message.real_time IS '涓婃姤鏃堕棿';
+COMMENT ON COLUMN sj_retry_task_log_message.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON TABLE sj_retry_task_log_message IS '浠诲姟璋冨害鏃ュ織淇℃伅璁板綍琛�';
+
+-- sj_retry_scene_config
+CREATE TABLE sj_retry_scene_config
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ scene_name varchar(64) NOT NULL,
+ group_name varchar(64) NOT NULL,
+ scene_status smallint NOT NULL DEFAULT 0,
+ max_retry_count int NOT NULL DEFAULT 5,
+ back_off smallint NOT NULL DEFAULT 1,
+ trigger_interval varchar(16) NOT NULL DEFAULT '',
+ notify_ids varchar(128) NOT NULL DEFAULT '',
+ deadline_request bigint NOT NULL DEFAULT 60000,
+ executor_timeout int NOT NULL DEFAULT 5,
+ route_key smallint NOT NULL DEFAULT 4,
+ block_strategy smallint NOT NULL DEFAULT 1,
+ cb_status smallint NOT NULL DEFAULT 0,
+ cb_trigger_type smallint NOT NULL DEFAULT 1,
+ cb_max_count int NOT NULL DEFAULT 16,
+ cb_trigger_interval varchar(16) NOT NULL DEFAULT '',
+ owner_id bigint NULL DEFAULT NULL,
+ labels varchar(512) NULL DEFAULT '',
+ description varchar(256) NOT NULL DEFAULT '',
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE UNIQUE INDEX uk_sj_retry_scene_config_01 ON sj_retry_scene_config (namespace_id, group_name, scene_name);
+
+COMMENT ON COLUMN sj_retry_scene_config.id IS '涓婚敭';
+COMMENT ON COLUMN sj_retry_scene_config.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_retry_scene_config.scene_name IS '鍦烘櫙鍚嶇О';
+COMMENT ON COLUMN sj_retry_scene_config.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_retry_scene_config.scene_status IS '缁勭姸鎬� 0銆佹湭鍚敤 1銆佸惎鐢�';
+COMMENT ON COLUMN sj_retry_scene_config.max_retry_count IS '鏈�澶ч噸璇曟鏁�';
+COMMENT ON COLUMN sj_retry_scene_config.back_off IS '1銆侀粯璁ょ瓑绾� 2銆佸浐瀹氶棿闅旀椂闂� 3銆丆RON 琛ㄨ揪寮�';
+COMMENT ON COLUMN sj_retry_scene_config.trigger_interval IS '闂撮殧鏃堕暱';
+COMMENT ON COLUMN sj_retry_scene_config.notify_ids IS '閫氱煡鍛婅鍦烘櫙閰嶇疆id鍒楄〃';
+COMMENT ON COLUMN sj_retry_scene_config.deadline_request IS 'Deadline Request 璋冪敤閾捐秴鏃� 鍗曚綅姣';
+COMMENT ON COLUMN sj_retry_scene_config.executor_timeout IS '浠诲姟鎵ц瓒呮椂鏃堕棿锛屽崟浣嶇';
+COMMENT ON COLUMN sj_retry_scene_config.route_key IS '璺敱绛栫暐';
+COMMENT ON COLUMN sj_retry_scene_config.block_strategy IS '闃诲绛栫暐 1銆佷涪寮� 2銆佽鐩� 3銆佸苟琛�';
+COMMENT ON COLUMN sj_retry_scene_config.cb_status IS '鍥炶皟鐘舵�� 0銆佷笉寮�鍚� 1銆佸紑鍚�';
+COMMENT ON COLUMN sj_retry_scene_config.cb_trigger_type IS '1銆侀粯璁ょ瓑绾� 2銆佸浐瀹氶棿闅旀椂闂� 3銆丆RON 琛ㄨ揪寮�';
+COMMENT ON COLUMN sj_retry_scene_config.cb_max_count IS '鍥炶皟鐨勬渶澶ф墽琛屾鏁�';
+COMMENT ON COLUMN sj_retry_scene_config.cb_trigger_interval IS '鍥炶皟鐨勬渶澶ф墽琛屾鏁�';
+COMMENT ON COLUMN sj_retry_scene_config.owner_id IS '璐熻矗浜篿d';
+COMMENT ON COLUMN sj_retry_scene_config.labels IS '鏍囩';
+COMMENT ON COLUMN sj_retry_scene_config.description IS '鎻忚堪';
+COMMENT ON COLUMN sj_retry_scene_config.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_retry_scene_config.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_retry_scene_config IS '鍦烘櫙閰嶇疆';
+
+-- sj_server_node
+CREATE TABLE sj_server_node
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL,
+ host_id varchar(64) NOT NULL,
+ host_ip varchar(64) NOT NULL,
+ host_port int NOT NULL,
+ expire_at timestamp NOT NULL,
+ node_type smallint NOT NULL,
+ ext_attrs varchar(256) NULL DEFAULT '',
+ labels varchar(512) NULL DEFAULT '',
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE UNIQUE INDEX uk_sj_server_node_01 ON sj_server_node (host_id, host_ip);
+
+CREATE INDEX idx_sj_server_node_01 ON sj_server_node (namespace_id, group_name);
+CREATE INDEX idx_sj_server_node_02 ON sj_server_node (expire_at, node_type);
+
+COMMENT ON COLUMN sj_server_node.id IS '涓婚敭';
+COMMENT ON COLUMN sj_server_node.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_server_node.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_server_node.host_id IS '涓绘満id';
+COMMENT ON COLUMN sj_server_node.host_ip IS '鏈哄櫒ip';
+COMMENT ON COLUMN sj_server_node.host_port IS '鏈哄櫒绔彛';
+COMMENT ON COLUMN sj_server_node.expire_at IS '杩囨湡鏃堕棿';
+COMMENT ON COLUMN sj_server_node.node_type IS '鑺傜偣绫诲瀷 1銆佸鎴风 2銆佹槸鏈嶅姟绔�';
+COMMENT ON COLUMN sj_server_node.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_server_node.labels IS '鏍囩';
+COMMENT ON COLUMN sj_server_node.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_server_node.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_server_node IS '鏈嶅姟鍣ㄨ妭鐐�';
+
+-- sj_distributed_lock
+CREATE TABLE sj_distributed_lock
+(
+ name varchar(64) NOT NULL PRIMARY KEY,
+ lock_until timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+ locked_at timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+ locked_by varchar(255) NOT NULL,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+COMMENT ON COLUMN sj_distributed_lock.name IS '閿佸悕绉�';
+COMMENT ON COLUMN sj_distributed_lock.lock_until IS '閿佸畾鏃堕暱';
+COMMENT ON COLUMN sj_distributed_lock.locked_at IS '閿佸畾鏃堕棿';
+COMMENT ON COLUMN sj_distributed_lock.locked_by IS '閿佸畾鑰�';
+COMMENT ON COLUMN sj_distributed_lock.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_distributed_lock.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_distributed_lock IS '閿佸畾琛�';
+
+-- sj_system_user
+CREATE TABLE sj_system_user
+(
+ id bigserial PRIMARY KEY,
+ username varchar(64) NOT NULL,
+ password varchar(128) NOT NULL,
+ role smallint NOT NULL DEFAULT 0,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+COMMENT ON COLUMN sj_system_user.id IS '涓婚敭';
+COMMENT ON COLUMN sj_system_user.username IS '璐﹀彿';
+COMMENT ON COLUMN sj_system_user.password IS '瀵嗙爜';
+COMMENT ON COLUMN sj_system_user.role IS '瑙掕壊锛�1-鏅�氱敤鎴枫��2-绠$悊鍛�';
+COMMENT ON COLUMN sj_system_user.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_system_user.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_system_user IS '绯荤粺鐢ㄦ埛琛�';
+
+INSERT INTO sj_system_user (username, password, role)
+VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2);
+
+-- sj_system_user_permission
+CREATE TABLE sj_system_user_permission
+(
+ id bigserial PRIMARY KEY,
+ group_name varchar(64) NOT NULL,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ system_user_id bigint NOT NULL,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE UNIQUE INDEX uk_sj_system_user_permission_01 ON sj_system_user_permission (namespace_id, group_name, system_user_id);
+
+COMMENT ON COLUMN sj_system_user_permission.id IS '涓婚敭';
+COMMENT ON COLUMN sj_system_user_permission.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_system_user_permission.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_system_user_permission.system_user_id IS '绯荤粺鐢ㄦ埛id';
+COMMENT ON COLUMN sj_system_user_permission.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_system_user_permission.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_system_user_permission IS '绯荤粺鐢ㄦ埛鏉冮檺琛�';
+
+-- sj_job
+CREATE TABLE sj_job
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL,
+ job_name varchar(64) NOT NULL,
+ args_str text NULL DEFAULT NULL,
+ args_type smallint NOT NULL DEFAULT 1,
+ next_trigger_at bigint NOT NULL,
+ job_status smallint NOT NULL DEFAULT 1,
+ task_type smallint NOT NULL DEFAULT 1,
+ route_key smallint NOT NULL DEFAULT 4,
+ executor_type smallint NOT NULL DEFAULT 1,
+ executor_info varchar(255) NULL DEFAULT NULL,
+ trigger_type smallint NOT NULL,
+ trigger_interval varchar(255) NOT NULL,
+ block_strategy smallint NOT NULL DEFAULT 1,
+ executor_timeout int NOT NULL DEFAULT 0,
+ max_retry_times int NOT NULL DEFAULT 0,
+ parallel_num int NOT NULL DEFAULT 1,
+ retry_interval int NOT NULL DEFAULT 0,
+ bucket_index int NOT NULL DEFAULT 0,
+ resident smallint NOT NULL DEFAULT 0,
+ notify_ids varchar(128) NOT NULL DEFAULT '',
+ owner_id bigint NULL DEFAULT NULL,
+ labels varchar(512) NULL DEFAULT '',
+ description varchar(256) NOT NULL DEFAULT '',
+ ext_attrs varchar(256) NULL DEFAULT '',
+ deleted smallint NOT NULL DEFAULT 0,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_job_01 ON sj_job (namespace_id, group_name);
+CREATE INDEX idx_sj_job_02 ON sj_job (job_status, bucket_index);
+CREATE INDEX idx_sj_job_03 ON sj_job (create_dt);
+
+COMMENT ON COLUMN sj_job.id IS '涓婚敭';
+COMMENT ON COLUMN sj_job.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_job.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_job.job_name IS '鍚嶇О';
+COMMENT ON COLUMN sj_job.args_str IS '鎵ц鏂规硶鍙傛暟';
+COMMENT ON COLUMN sj_job.args_type IS '鍙傛暟绫诲瀷 ';
+COMMENT ON COLUMN sj_job.next_trigger_at IS '涓嬫瑙﹀彂鏃堕棿';
+COMMENT ON COLUMN sj_job.job_status IS '浠诲姟鐘舵�� 0銆佸叧闂��1銆佸紑鍚�';
+COMMENT ON COLUMN sj_job.task_type IS '浠诲姟绫诲瀷 1銆侀泦缇� 2銆佸箍鎾� 3銆佸垏鐗�';
+COMMENT ON COLUMN sj_job.route_key IS '璺敱绛栫暐';
+COMMENT ON COLUMN sj_job.executor_type IS '鎵ц鍣ㄧ被鍨�';
+COMMENT ON COLUMN sj_job.executor_info IS '鎵ц鍣ㄥ悕绉�';
+COMMENT ON COLUMN sj_job.trigger_type IS '瑙﹀彂绫诲瀷 1.CRON 琛ㄨ揪寮� 2. 鍥哄畾鏃堕棿';
+COMMENT ON COLUMN sj_job.trigger_interval IS '闂撮殧鏃堕暱';
+COMMENT ON COLUMN sj_job.block_strategy IS '闃诲绛栫暐 1銆佷涪寮� 2銆佽鐩� 3銆佸苟琛� 4銆佹仮澶�';
+COMMENT ON COLUMN sj_job.executor_timeout IS '浠诲姟鎵ц瓒呮椂鏃堕棿锛屽崟浣嶇';
+COMMENT ON COLUMN sj_job.max_retry_times IS '鏈�澶ч噸璇曟鏁�';
+COMMENT ON COLUMN sj_job.parallel_num IS '骞惰鏁�';
+COMMENT ON COLUMN sj_job.retry_interval IS '閲嶈瘯闂撮殧 ( s)';
+COMMENT ON COLUMN sj_job.bucket_index IS 'bucket';
+COMMENT ON COLUMN sj_job.resident IS '鏄惁鏄父椹讳换鍔�';
+COMMENT ON COLUMN sj_job.notify_ids IS '閫氱煡鍛婅鍦烘櫙閰嶇疆id鍒楄〃';
+COMMENT ON COLUMN sj_job.owner_id IS '璐熻矗浜篿d';
+COMMENT ON COLUMN sj_job.labels IS '鏍囩';
+COMMENT ON COLUMN sj_job.description IS '鎻忚堪';
+COMMENT ON COLUMN sj_job.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_job.deleted IS '閫昏緫鍒犻櫎 1銆佸垹闄�';
+COMMENT ON COLUMN sj_job.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_job.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_job IS '浠诲姟淇℃伅';
+
+INSERT INTO sj_job VALUES (1, 'dev', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '', '', '', 0, now(), now());
+
+-- sj_job_log_message
+CREATE TABLE sj_job_log_message
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL,
+ job_id bigint NOT NULL,
+ task_batch_id bigint NOT NULL,
+ task_id bigint NOT NULL,
+ message text NOT NULL,
+ log_num int NOT NULL DEFAULT 1,
+ real_time bigint NOT NULL DEFAULT 0,
+ ext_attrs varchar(256) NULL DEFAULT '',
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_job_log_message_01 ON sj_job_log_message (task_batch_id, task_id);
+CREATE INDEX idx_sj_job_log_message_02 ON sj_job_log_message (create_dt);
+CREATE INDEX idx_sj_job_log_message_03 ON sj_job_log_message (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_job_log_message.id IS '涓婚敭';
+COMMENT ON COLUMN sj_job_log_message.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_job_log_message.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_job_log_message.job_id IS '浠诲姟淇℃伅id';
+COMMENT ON COLUMN sj_job_log_message.task_batch_id IS '浠诲姟鎵规id';
+COMMENT ON COLUMN sj_job_log_message.task_id IS '璋冨害浠诲姟id';
+COMMENT ON COLUMN sj_job_log_message.message IS '璋冨害淇℃伅';
+COMMENT ON COLUMN sj_job_log_message.log_num IS '鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_job_log_message.real_time IS '涓婃姤鏃堕棿';
+COMMENT ON COLUMN sj_job_log_message.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_job_log_message.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON TABLE sj_job_log_message IS '璋冨害鏃ュ織';
+
+-- sj_job_task
+CREATE TABLE sj_job_task
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL,
+ job_id bigint NOT NULL,
+ task_batch_id bigint NOT NULL,
+ parent_id bigint NOT NULL DEFAULT 0,
+ task_status smallint NOT NULL DEFAULT 0,
+ retry_count int NOT NULL DEFAULT 0,
+ mr_stage smallint NULL DEFAULT NULL,
+ leaf smallint NOT NULL DEFAULT '1',
+ task_name varchar(255) NOT NULL DEFAULT '',
+ client_info varchar(128) NULL DEFAULT NULL,
+ wf_context text NULL DEFAULT NULL,
+ result_message text NOT NULL,
+ args_str text NULL DEFAULT NULL,
+ args_type smallint NOT NULL DEFAULT 1,
+ ext_attrs varchar(256) NULL DEFAULT '',
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_job_task_01 ON sj_job_task (task_batch_id, task_status);
+CREATE INDEX idx_sj_job_task_02 ON sj_job_task (create_dt);
+CREATE INDEX idx_sj_job_task_03 ON sj_job_task (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_job_task.id IS '涓婚敭';
+COMMENT ON COLUMN sj_job_task.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_job_task.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_job_task.job_id IS '浠诲姟淇℃伅id';
+COMMENT ON COLUMN sj_job_task.task_batch_id IS '璋冨害浠诲姟id';
+COMMENT ON COLUMN sj_job_task.parent_id IS '鐖舵墽琛屽櫒id';
+COMMENT ON COLUMN sj_job_task.task_status IS '鎵ц鐨勭姸鎬� 0銆佸け璐� 1銆佹垚鍔�';
+COMMENT ON COLUMN sj_job_task.retry_count IS '閲嶈瘯娆℃暟';
+COMMENT ON COLUMN sj_job_task.mr_stage IS '鍔ㄦ�佸垎鐗囨墍澶勯樁娈� 1:map 2:reduce 3:mergeReduce';
+COMMENT ON COLUMN sj_job_task.leaf IS '鍙跺瓙鑺傜偣';
+COMMENT ON COLUMN sj_job_task.task_name IS '浠诲姟鍚嶇О';
+COMMENT ON COLUMN sj_job_task.client_info IS '瀹㈡埛绔湴鍧� clientId#ip:port';
+COMMENT ON COLUMN sj_job_task.wf_context IS '宸ヤ綔娴佸叏灞�涓婁笅鏂�';
+COMMENT ON COLUMN sj_job_task.result_message IS '鎵ц缁撴灉';
+COMMENT ON COLUMN sj_job_task.args_str IS '鎵ц鏂规硶鍙傛暟';
+COMMENT ON COLUMN sj_job_task.args_type IS '鍙傛暟绫诲瀷 ';
+COMMENT ON COLUMN sj_job_task.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_job_task.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_job_task.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_job_task IS '浠诲姟瀹炰緥';
+
+-- sj_job_task_batch
+CREATE TABLE sj_job_task_batch
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL,
+ job_id bigint NOT NULL,
+ workflow_node_id bigint NOT NULL DEFAULT 0,
+ parent_workflow_node_id bigint NOT NULL DEFAULT 0,
+ workflow_task_batch_id bigint NOT NULL DEFAULT 0,
+ task_batch_status smallint NOT NULL DEFAULT 0,
+ operation_reason smallint NOT NULL DEFAULT 0,
+ execution_at bigint NOT NULL DEFAULT 0,
+ system_task_type smallint NOT NULL DEFAULT 3,
+ parent_id varchar(64) NOT NULL DEFAULT '',
+ ext_attrs varchar(256) NULL DEFAULT '',
+ deleted smallint NOT NULL DEFAULT 0,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_job_task_batch_01 ON sj_job_task_batch (job_id, task_batch_status);
+CREATE INDEX idx_sj_job_task_batch_02 ON sj_job_task_batch (create_dt);
+CREATE INDEX idx_sj_job_task_batch_03 ON sj_job_task_batch (namespace_id, group_name);
+CREATE INDEX idx_sj_job_task_batch_04 ON sj_job_task_batch (workflow_task_batch_id, workflow_node_id);
+
+COMMENT ON COLUMN sj_job_task_batch.id IS '涓婚敭';
+COMMENT ON COLUMN sj_job_task_batch.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_job_task_batch.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_job_task_batch.job_id IS '浠诲姟id';
+COMMENT ON COLUMN sj_job_task_batch.workflow_node_id IS '宸ヤ綔娴佽妭鐐筰d';
+COMMENT ON COLUMN sj_job_task_batch.parent_workflow_node_id IS '宸ヤ綔娴佷换鍔$埗鎵规id';
+COMMENT ON COLUMN sj_job_task_batch.workflow_task_batch_id IS '宸ヤ綔娴佷换鍔℃壒娆d';
+COMMENT ON COLUMN sj_job_task_batch.task_batch_status IS '浠诲姟鎵规鐘舵�� 0銆佸け璐� 1銆佹垚鍔�';
+COMMENT ON COLUMN sj_job_task_batch.operation_reason IS '鎿嶄綔鍘熷洜';
+COMMENT ON COLUMN sj_job_task_batch.execution_at IS '浠诲姟鎵ц鏃堕棿';
+COMMENT ON COLUMN sj_job_task_batch.system_task_type IS '浠诲姟绫诲瀷 3銆丣OB浠诲姟 4銆乄ORKFLOW浠诲姟';
+COMMENT ON COLUMN sj_job_task_batch.parent_id IS '鐖惰妭鐐�';
+COMMENT ON COLUMN sj_job_task_batch.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_job_task_batch.deleted IS '閫昏緫鍒犻櫎 1銆佸垹闄�';
+COMMENT ON COLUMN sj_job_task_batch.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_job_task_batch.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_job_task_batch IS '浠诲姟鎵规';
+
+-- sj_job_summary
+CREATE TABLE sj_job_summary
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL DEFAULT '',
+ business_id bigint NOT NULL,
+ system_task_type smallint NOT NULL DEFAULT 3,
+ trigger_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ success_num int NOT NULL DEFAULT 0,
+ fail_num int NOT NULL DEFAULT 0,
+ fail_reason varchar(512) NOT NULL DEFAULT '',
+ stop_num int NOT NULL DEFAULT 0,
+ stop_reason varchar(512) NOT NULL DEFAULT '',
+ cancel_num int NOT NULL DEFAULT 0,
+ cancel_reason varchar(512) NOT NULL DEFAULT '',
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE UNIQUE INDEX uk_sj_job_summary_01 ON sj_job_summary (trigger_at, system_task_type, business_id);
+
+CREATE INDEX idx_sj_job_summary_01 ON sj_job_summary (namespace_id, group_name, business_id);
+
+COMMENT ON COLUMN sj_job_summary.id IS '涓婚敭';
+COMMENT ON COLUMN sj_job_summary.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_job_summary.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_job_summary.business_id IS '涓氬姟id ( job_id鎴杦orkflow_id)';
+COMMENT ON COLUMN sj_job_summary.system_task_type IS '浠诲姟绫诲瀷 3銆丣OB浠诲姟 4銆乄ORKFLOW浠诲姟';
+COMMENT ON COLUMN sj_job_summary.trigger_at IS '缁熻鏃堕棿';
+COMMENT ON COLUMN sj_job_summary.success_num IS '鎵ц鎴愬姛-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_job_summary.fail_num IS '鎵ц澶辫触-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_job_summary.fail_reason IS '澶辫触鍘熷洜';
+COMMENT ON COLUMN sj_job_summary.stop_num IS '鎵ц澶辫触-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_job_summary.stop_reason IS '澶辫触鍘熷洜';
+COMMENT ON COLUMN sj_job_summary.cancel_num IS '鎵ц澶辫触-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_job_summary.cancel_reason IS '澶辫触鍘熷洜';
+COMMENT ON COLUMN sj_job_summary.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_job_summary.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_job_summary IS 'DashBoard_Job';
+
+-- sj_retry_summary
+CREATE TABLE sj_retry_summary
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL DEFAULT '',
+ scene_name varchar(64) NOT NULL DEFAULT '',
+ trigger_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ running_num int NOT NULL DEFAULT 0,
+ finish_num int NOT NULL DEFAULT 0,
+ max_count_num int NOT NULL DEFAULT 0,
+ suspend_num int NOT NULL DEFAULT 0,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE UNIQUE INDEX uk_sj_retry_summary_01 ON sj_retry_summary (namespace_id, group_name, scene_name, trigger_at);
+
+CREATE INDEX idx_sj_retry_summary_01 ON sj_retry_summary (trigger_at);
+
+COMMENT ON COLUMN sj_retry_summary.id IS '涓婚敭';
+COMMENT ON COLUMN sj_retry_summary.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_retry_summary.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_retry_summary.scene_name IS '鍦烘櫙鍚嶇О';
+COMMENT ON COLUMN sj_retry_summary.trigger_at IS '缁熻鏃堕棿';
+COMMENT ON COLUMN sj_retry_summary.running_num IS '閲嶈瘯涓�-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_retry_summary.finish_num IS '閲嶈瘯瀹屾垚-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_retry_summary.max_count_num IS '閲嶈瘯鍒拌揪鏈�澶ф鏁�-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_retry_summary.suspend_num IS '鏆傚仠閲嶈瘯-鏃ュ織鏁伴噺';
+COMMENT ON COLUMN sj_retry_summary.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_retry_summary.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_retry_summary IS 'DashBoard_Retry';
+
+-- sj_workflow
+CREATE TABLE sj_workflow
+(
+ id bigserial PRIMARY KEY,
+ workflow_name varchar(64) NOT NULL,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL,
+ workflow_status smallint NOT NULL DEFAULT 1,
+ trigger_type smallint NOT NULL,
+ trigger_interval varchar(255) NOT NULL,
+ next_trigger_at bigint NOT NULL,
+ block_strategy smallint NOT NULL DEFAULT 1,
+ executor_timeout int NOT NULL DEFAULT 0,
+ description varchar(256) NOT NULL DEFAULT '',
+ flow_info text NULL DEFAULT NULL,
+ wf_context text NULL DEFAULT NULL,
+ notify_ids varchar(128) NOT NULL DEFAULT '',
+ bucket_index int NOT NULL DEFAULT 0,
+ version int NOT NULL,
+ owner_id bigint NULL DEFAULT NULL,
+ ext_attrs varchar(256) NULL DEFAULT '',
+ deleted smallint NOT NULL DEFAULT 0,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_workflow_01 ON sj_workflow (create_dt);
+CREATE INDEX idx_sj_workflow_02 ON sj_workflow (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_workflow.id IS '涓婚敭';
+COMMENT ON COLUMN sj_workflow.workflow_name IS '宸ヤ綔娴佸悕绉�';
+COMMENT ON COLUMN sj_workflow.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_workflow.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_workflow.workflow_status IS '宸ヤ綔娴佺姸鎬� 0銆佸叧闂��1銆佸紑鍚�';
+COMMENT ON COLUMN sj_workflow.trigger_type IS '瑙﹀彂绫诲瀷 1.CRON 琛ㄨ揪寮� 2. 鍥哄畾鏃堕棿';
+COMMENT ON COLUMN sj_workflow.trigger_interval IS '闂撮殧鏃堕暱';
+COMMENT ON COLUMN sj_workflow.next_trigger_at IS '涓嬫瑙﹀彂鏃堕棿';
+COMMENT ON COLUMN sj_workflow.block_strategy IS '闃诲绛栫暐 1銆佷涪寮� 2銆佽鐩� 3銆佸苟琛�';
+COMMENT ON COLUMN sj_workflow.executor_timeout IS '浠诲姟鎵ц瓒呮椂鏃堕棿锛屽崟浣嶇';
+COMMENT ON COLUMN sj_workflow.description IS '鎻忚堪';
+COMMENT ON COLUMN sj_workflow.flow_info IS '娴佺▼淇℃伅';
+COMMENT ON COLUMN sj_workflow.wf_context IS '涓婁笅鏂�';
+COMMENT ON COLUMN sj_workflow.notify_ids IS '閫氱煡鍛婅鍦烘櫙閰嶇疆id鍒楄〃';
+COMMENT ON COLUMN sj_workflow.bucket_index IS 'bucket';
+COMMENT ON COLUMN sj_workflow.version IS '鐗堟湰鍙�';
+COMMENT ON COLUMN sj_workflow.owner_id IS '璐熻矗浜篿d';
+COMMENT ON COLUMN sj_workflow.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_workflow.deleted IS '閫昏緫鍒犻櫎 1銆佸垹闄�';
+COMMENT ON COLUMN sj_workflow.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_workflow.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_workflow IS '宸ヤ綔娴�';
+
+-- sj_workflow_node
+CREATE TABLE sj_workflow_node
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ node_name varchar(64) NOT NULL,
+ group_name varchar(64) NOT NULL,
+ job_id bigint NOT NULL,
+ workflow_id bigint NOT NULL,
+ node_type smallint NOT NULL DEFAULT 1,
+ expression_type smallint NOT NULL DEFAULT 0,
+ fail_strategy smallint NOT NULL DEFAULT 1,
+ workflow_node_status smallint NOT NULL DEFAULT 1,
+ priority_level int NOT NULL DEFAULT 1,
+ node_info text NULL DEFAULT NULL,
+ version int NOT NULL,
+ ext_attrs varchar(256) NULL DEFAULT '',
+ deleted smallint NOT NULL DEFAULT 0,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_workflow_node_01 ON sj_workflow_node (create_dt);
+CREATE INDEX idx_sj_workflow_node_02 ON sj_workflow_node (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_workflow_node.id IS '涓婚敭';
+COMMENT ON COLUMN sj_workflow_node.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_workflow_node.node_name IS '鑺傜偣鍚嶇О';
+COMMENT ON COLUMN sj_workflow_node.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_workflow_node.job_id IS '浠诲姟淇℃伅id';
+COMMENT ON COLUMN sj_workflow_node.workflow_id IS '宸ヤ綔娴両D';
+COMMENT ON COLUMN sj_workflow_node.node_type IS '1銆佷换鍔¤妭鐐� 2銆佹潯浠惰妭鐐�';
+COMMENT ON COLUMN sj_workflow_node.expression_type IS '1銆丼pEl銆�2銆丄viator 3銆丵L';
+COMMENT ON COLUMN sj_workflow_node.fail_strategy IS '澶辫触绛栫暐 1銆佽烦杩� 2銆侀樆濉�';
+COMMENT ON COLUMN sj_workflow_node.workflow_node_status IS '宸ヤ綔娴佽妭鐐圭姸鎬� 0銆佸叧闂��1銆佸紑鍚�';
+COMMENT ON COLUMN sj_workflow_node.priority_level IS '浼樺厛绾�';
+COMMENT ON COLUMN sj_workflow_node.node_info IS '鑺傜偣淇℃伅 ';
+COMMENT ON COLUMN sj_workflow_node.version IS '鐗堟湰鍙�';
+COMMENT ON COLUMN sj_workflow_node.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_workflow_node.deleted IS '閫昏緫鍒犻櫎 1銆佸垹闄�';
+COMMENT ON COLUMN sj_workflow_node.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_workflow_node.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_workflow_node IS '宸ヤ綔娴佽妭鐐�';
+
+-- sj_workflow_task_batch
+CREATE TABLE sj_workflow_task_batch
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL,
+ workflow_id bigint NOT NULL,
+ task_batch_status smallint NOT NULL DEFAULT 0,
+ operation_reason smallint NOT NULL DEFAULT 0,
+ flow_info text NULL DEFAULT NULL,
+ wf_context text NULL DEFAULT NULL,
+ execution_at bigint NOT NULL DEFAULT 0,
+ ext_attrs varchar(256) NULL DEFAULT '',
+ version int NOT NULL DEFAULT 1,
+ deleted smallint NOT NULL DEFAULT 0,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_workflow_task_batch_01 ON sj_workflow_task_batch (workflow_id, task_batch_status);
+CREATE INDEX idx_sj_workflow_task_batch_02 ON sj_workflow_task_batch (create_dt);
+CREATE INDEX idx_sj_workflow_task_batch_03 ON sj_workflow_task_batch (namespace_id, group_name);
+
+COMMENT ON COLUMN sj_workflow_task_batch.id IS '涓婚敭';
+COMMENT ON COLUMN sj_workflow_task_batch.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_workflow_task_batch.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_workflow_task_batch.workflow_id IS '宸ヤ綔娴佷换鍔d';
+COMMENT ON COLUMN sj_workflow_task_batch.task_batch_status IS '浠诲姟鎵规鐘舵�� 0銆佸け璐� 1銆佹垚鍔�';
+COMMENT ON COLUMN sj_workflow_task_batch.operation_reason IS '鎿嶄綔鍘熷洜';
+COMMENT ON COLUMN sj_workflow_task_batch.flow_info IS '娴佺▼淇℃伅';
+COMMENT ON COLUMN sj_workflow_task_batch.wf_context IS '鍏ㄥ眬涓婁笅鏂�';
+COMMENT ON COLUMN sj_workflow_task_batch.execution_at IS '浠诲姟鎵ц鏃堕棿';
+COMMENT ON COLUMN sj_workflow_task_batch.ext_attrs IS '鎵╁睍瀛楁';
+COMMENT ON COLUMN sj_workflow_task_batch.version IS '鐗堟湰鍙�';
+COMMENT ON COLUMN sj_workflow_task_batch.deleted IS '閫昏緫鍒犻櫎 1銆佸垹闄�';
+COMMENT ON COLUMN sj_workflow_task_batch.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_workflow_task_batch.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_workflow_task_batch IS '宸ヤ綔娴佹壒娆�';
+
+-- sj_job_executor
+CREATE TABLE sj_job_executor
+(
+ id bigserial PRIMARY KEY,
+ namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name varchar(64) NOT NULL,
+ executor_info varchar(256) NOT NULL,
+ executor_type varchar(3) NOT NULL,
+ create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX idx_sj_job_executor_01 ON sj_job_executor (namespace_id, group_name);
+CREATE INDEX idx_sj_job_executor_02 ON sj_job_executor (create_dt);
+
+COMMENT ON COLUMN sj_job_executor.id IS '涓婚敭';
+COMMENT ON COLUMN sj_job_executor.namespace_id IS '鍛藉悕绌洪棿id';
+COMMENT ON COLUMN sj_job_executor.group_name IS '缁勫悕绉�';
+COMMENT ON COLUMN sj_job_executor.executor_info IS '浠诲姟鎵ц鍣ㄥ悕绉�';
+COMMENT ON COLUMN sj_job_executor.executor_type IS '1:java 2:python 3:go';
+COMMENT ON COLUMN sj_job_executor.create_dt IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN sj_job_executor.update_dt IS '淇敼鏃堕棿';
+COMMENT ON TABLE sj_job_executor IS '浠诲姟鎵ц鍣ㄤ俊鎭�';
diff --git a/RuoYi-Vue-Plus/script/sql/postgres/postgres_ry_vue_5.X.sql b/RuoYi-Vue-Plus/script/sql/postgres/postgres_ry_vue_5.X.sql
new file mode 100755
index 0000000..bb127f9
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/postgres/postgres_ry_vue_5.X.sql
@@ -0,0 +1,1398 @@
+-- ----------------------------
+-- 绗笁鏂瑰钩鍙版巿鏉冭〃
+-- ----------------------------
+create table sys_social
+(
+ id int8 not null,
+ user_id int8 not null,
+ tenant_id varchar(20) default '000000'::varchar,
+ auth_id varchar(255) not null,
+ source varchar(255) not null,
+ open_id varchar(255) default null::varchar,
+ user_name varchar(30) not null,
+ nick_name varchar(30) default ''::varchar,
+ email varchar(255) default ''::varchar,
+ avatar varchar(500) default ''::varchar,
+ access_token varchar(2000) not null,
+ expire_in int8 default null,
+ refresh_token varchar(2000) default null::varchar,
+ access_code varchar(255) default null::varchar,
+ union_id varchar(255) default null::varchar,
+ scope varchar(255) default null::varchar,
+ token_type varchar(255) default null::varchar,
+ id_token varchar(2000) default null::varchar,
+ mac_algorithm varchar(255) default null::varchar,
+ mac_key varchar(255) default null::varchar,
+ code varchar(255) default null::varchar,
+ oauth_token varchar(255) default null::varchar,
+ oauth_token_secret varchar(255) default null::varchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ del_flag char default '0'::bpchar,
+ constraint "pk_sys_social" primary key (id)
+);
+
+comment on table sys_social is '绀句細鍖栧叧绯昏〃';
+comment on column sys_social.id is '涓婚敭';
+comment on column sys_social.user_id is '鐢ㄦ埛ID';
+comment on column sys_social.tenant_id is '绉熸埛id';
+comment on column sys_social.auth_id is '骞冲彴+骞冲彴鍞竴id';
+comment on column sys_social.source is '鐢ㄦ埛鏉ユ簮';
+comment on column sys_social.open_id is '骞冲彴缂栧彿鍞竴id';
+comment on column sys_social.user_name is '鐧诲綍璐﹀彿';
+comment on column sys_social.nick_name is '鐢ㄦ埛鏄电О';
+comment on column sys_social.email is '鐢ㄦ埛閭';
+comment on column sys_social.avatar is '澶村儚鍦板潃';
+comment on column sys_social.access_token is '鐢ㄦ埛鐨勬巿鏉冧护鐗�';
+comment on column sys_social.expire_in is '鐢ㄦ埛鐨勬巿鏉冧护鐗岀殑鏈夋晥鏈燂紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.refresh_token is '鍒锋柊浠ょ墝锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�';
+comment on column sys_social.access_code is '骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.union_id is '鐢ㄦ埛鐨� unionid';
+comment on column sys_social.scope is '鎺堜簣鐨勬潈闄愶紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.token_type is '涓埆骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.id_token is 'id token锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�';
+comment on column sys_social.mac_algorithm is '灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.mac_key is '灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.code is '鐢ㄦ埛鐨勬巿鏉僣ode锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�';
+comment on column sys_social.oauth_token is 'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.oauth_token_secret is 'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_social.create_by is '鍒涘缓鑰�';
+comment on column sys_social.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_social.update_by is '鏇存柊鑰�';
+comment on column sys_social.update_time is '鏇存柊鏃堕棿';
+comment on column sys_social.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+
+-- ----------------------------
+-- 绉熸埛琛�
+-- ----------------------------
+create table if not exists sys_tenant
+(
+ id int8,
+ tenant_id varchar(20) not null,
+ contact_user_name varchar(20) default null::varchar,
+ contact_phone varchar(20) default null::varchar,
+ company_name varchar(30) default null::varchar,
+ license_number varchar(30) default null::varchar,
+ address varchar(200) default null::varchar,
+ intro varchar(200) default null::varchar,
+ domain varchar(200) default null::varchar,
+ remark varchar(200) default null::varchar,
+ package_id int8,
+ expire_time timestamp,
+ account_count int4 default -1,
+ status char default '0'::bpchar,
+ del_flag char default '0'::bpchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ constraint "pk_sys_tenant" primary key (id)
+);
+
+
+comment on table sys_tenant is '绉熸埛琛�';
+comment on column sys_tenant.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_tenant.contact_phone is '鑱旂郴鐢佃瘽';
+comment on column sys_tenant.company_name is '浼佷笟鍚嶇О';
+comment on column sys_tenant.company_name is '鑱旂郴浜�';
+comment on column sys_tenant.license_number is '缁熶竴绀句細淇$敤浠g爜';
+comment on column sys_tenant.address is '鍦板潃';
+comment on column sys_tenant.intro is '浼佷笟绠�浠�';
+comment on column sys_tenant.domain is '鍩熷悕';
+comment on column sys_tenant.remark is '澶囨敞';
+comment on column sys_tenant.package_id is '绉熸埛濂楅缂栧彿';
+comment on column sys_tenant.expire_time is '杩囨湡鏃堕棿';
+comment on column sys_tenant.account_count is '鐢ㄦ埛鏁伴噺锛�-1涓嶉檺鍒讹級';
+comment on column sys_tenant.status is '绉熸埛鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_tenant.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+comment on column sys_tenant.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_tenant.create_by is '鍒涘缓鑰�';
+comment on column sys_tenant.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_tenant.update_by is '鏇存柊鑰�';
+comment on column sys_tenant.update_time is '鏇存柊鏃堕棿';
+
+
+-- ----------------------------
+-- 鍒濆鍖�-绉熸埛琛ㄦ暟鎹�
+-- ----------------------------
+
+insert into sys_tenant values(1, '000000', '绠$悊缁�', '15888888888', 'XXX鏈夐檺鍏徃', null, null, '澶氱鎴烽�氱敤鍚庡彴绠$悊绠$悊绯荤粺', null, null, null, null, -1, '0', '0', 103, 1, now(), null, null);
+
+
+-- ----------------------------
+-- 绉熸埛濂楅琛�
+-- ----------------------------
+create table if not exists sys_tenant_package
+(
+ package_id int8,
+ package_name varchar(20) default ''::varchar,
+ menu_ids varchar(3000) default ''::varchar,
+ remark varchar(200) default ''::varchar,
+ menu_check_strictly bool default true,
+ status char default '0'::bpchar,
+ del_flag char default '0'::bpchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ constraint "pk_sys_tenant_package" primary key (package_id)
+);
+
+
+comment on table sys_tenant_package is '绉熸埛濂楅琛�';
+comment on column sys_tenant_package.package_id is '绉熸埛濂楅id';
+comment on column sys_tenant_package.package_name is '濂楅鍚嶇О';
+comment on column sys_tenant_package.menu_ids is '鍏宠仈鑿滃崟id';
+comment on column sys_tenant_package.remark is '澶囨敞';
+comment on column sys_tenant_package.status is '鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_tenant_package.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+comment on column sys_tenant_package.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_tenant_package.create_by is '鍒涘缓鑰�';
+comment on column sys_tenant_package.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_tenant_package.update_by is '鏇存柊鑰�';
+comment on column sys_tenant_package.update_time is '鏇存柊鏃堕棿';
+
+
+-- ----------------------------
+-- 1銆侀儴闂ㄨ〃
+-- ----------------------------
+create table if not exists sys_dept
+(
+ dept_id int8,
+ tenant_id varchar(20) default '000000'::varchar,
+ parent_id int8 default 0,
+ ancestors varchar(500)default ''::varchar,
+ dept_name varchar(30) default ''::varchar,
+ dept_category varchar(100) default null::varchar,
+ order_num int4 default 0,
+ leader int8 default null,
+ phone varchar(11) default null::varchar,
+ email varchar(50) default null::varchar,
+ status char default '0'::bpchar,
+ del_flag char default '0'::bpchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ constraint "sys_dept_pk" primary key (dept_id)
+);
+
+comment on table sys_dept is '閮ㄩ棬琛�';
+comment on column sys_dept.dept_id is '閮ㄩ棬ID';
+comment on column sys_dept.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_dept.parent_id is '鐖堕儴闂↖D';
+comment on column sys_dept.ancestors is '绁栫骇鍒楄〃';
+comment on column sys_dept.dept_name is '閮ㄩ棬鍚嶇О';
+comment on column sys_dept.dept_category is '閮ㄩ棬绫诲埆缂栫爜';
+comment on column sys_dept.order_num is '鏄剧ず椤哄簭';
+comment on column sys_dept.leader is '璐熻矗浜�';
+comment on column sys_dept.phone is '鑱旂郴鐢佃瘽';
+comment on column sys_dept.email is '閭';
+comment on column sys_dept.status is '閮ㄩ棬鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_dept.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+comment on column sys_dept.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_dept.create_by is '鍒涘缓鑰�';
+comment on column sys_dept.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_dept.update_by is '鏇存柊鑰�';
+comment on column sys_dept.update_time is '鏇存柊鏃堕棿';
+
+-- ----------------------------
+-- 鍒濆鍖�-閮ㄩ棬琛ㄦ暟鎹�
+-- ----------------------------
+insert into sys_dept values(100, '000000', 0, '0', 'XXX绉戞妧', null,0, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, now(), null, null);
+insert into sys_dept values(101, '000000', 100, '0,100', '娣卞湷鎬诲叕鍙�', null,1, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, now(), null, null);
+insert into sys_dept values(102, '000000', 100, '0,100', '闀挎矙鍒嗗叕鍙�', null,2, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, now(), null, null);
+insert into sys_dept values(103, '000000', 101, '0,100,101', '鐮斿彂閮ㄩ棬', null,1, 1, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, now(), null, null);
+insert into sys_dept values(104, '000000', 101, '0,100,101', '甯傚満閮ㄩ棬', null,2, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, now(), null, null);
+insert into sys_dept values(105, '000000', 101, '0,100,101', '娴嬭瘯閮ㄩ棬', null,3, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, now(), null, null);
+insert into sys_dept values(106, '000000', 101, '0,100,101', '璐㈠姟閮ㄩ棬', null,4, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, now(), null, null);
+insert into sys_dept values(107, '000000', 101, '0,100,101', '杩愮淮閮ㄩ棬', null,5, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, now(), null, null);
+insert into sys_dept values(108, '000000', 102, '0,100,102', '甯傚満閮ㄩ棬', null,1, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, now(), null, null);
+insert into sys_dept values(109, '000000', 102, '0,100,102', '璐㈠姟閮ㄩ棬', null,2, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, now(), null, null);
+
+-- ----------------------------
+-- 2銆佺敤鎴蜂俊鎭〃
+-- ----------------------------
+create table if not exists sys_user
+(
+ user_id int8,
+ tenant_id varchar(20) default '000000'::varchar,
+ dept_id int8,
+ user_name varchar(30) not null,
+ nick_name varchar(30) not null,
+ user_type varchar(10) default 'sys_user'::varchar,
+ email varchar(50) default ''::varchar,
+ phonenumber varchar(11) default ''::varchar,
+ sex char default '0'::bpchar,
+ avatar int8,
+ password varchar(100) default ''::varchar,
+ status char default '0'::bpchar,
+ del_flag char default '0'::bpchar,
+ login_ip varchar(128) default ''::varchar,
+ login_date timestamp,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ remark varchar(500) default null::varchar,
+ constraint "sys_user_pk" primary key (user_id)
+);
+
+comment on table sys_user is '鐢ㄦ埛淇℃伅琛�';
+comment on column sys_user.user_id is '鐢ㄦ埛ID';
+comment on column sys_user.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_user.dept_id is '閮ㄩ棬ID';
+comment on column sys_user.user_name is '鐢ㄦ埛璐﹀彿';
+comment on column sys_user.nick_name is '鐢ㄦ埛鏄电О';
+comment on column sys_user.user_type is '鐢ㄦ埛绫诲瀷锛坰ys_user绯荤粺鐢ㄦ埛锛�';
+comment on column sys_user.email is '鐢ㄦ埛閭';
+comment on column sys_user.phonenumber is '鎵嬫満鍙风爜';
+comment on column sys_user.sex is '鐢ㄦ埛鎬у埆锛�0鐢� 1濂� 2鏈煡锛�';
+comment on column sys_user.avatar is '澶村儚鍦板潃';
+comment on column sys_user.password is '瀵嗙爜';
+comment on column sys_user.status is '璐﹀彿鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_user.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+comment on column sys_user.login_ip is '鏈�鍚庣櫥闄咺P';
+comment on column sys_user.login_date is '鏈�鍚庣櫥闄嗘椂闂�';
+comment on column sys_user.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_user.create_by is '鍒涘缓鑰�';
+comment on column sys_user.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_user.update_by is '鏇存柊鑰�';
+comment on column sys_user.update_time is '鏇存柊鏃堕棿';
+comment on column sys_user.remark is '澶囨敞';
+
+-- ----------------------------
+
+-- 鍒濆鍖�-鐢ㄦ埛淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+insert into sys_user values(1, '000000', 103, 'admin', '鐤媯鐨勭嫯瀛怢i', 'sys_user', 'crazyLionLi@163.com', '15888888888', '1', null, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', now(), 103, 1, now(), null, null, '绠$悊鍛�');
+insert into sys_user VALUES(3, '000000', 108, 'test', '鏈儴闂ㄥ強浠ヤ笅 瀵嗙爜666666', 'sys_user', '', '', '0', null, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', now(), 103, 1, now(), 3, now(), NULL);
+insert into sys_user VALUES(4, '000000', 102, 'test1', '浠呮湰浜� 瀵嗙爜666666', 'sys_user', '', '', '0', null, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', now(), 103, 1, now(), 4, now(), NULL);
+
+-- ----------------------------
+-- 3銆佸矖浣嶄俊鎭〃
+-- ----------------------------
+create table if not exists sys_post
+(
+ post_id int8,
+ tenant_id varchar(20) default '000000'::varchar,
+ dept_id int8,
+ post_code varchar(64) not null,
+ post_category varchar(100) default null,
+ post_name varchar(50) not null,
+ post_sort int4 not null,
+ status char not null,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ remark varchar(500) default null::varchar,
+ constraint "sys_post_pk" primary key (post_id)
+);
+
+comment on table sys_post is '宀椾綅淇℃伅琛�';
+comment on column sys_post.post_id is '宀椾綅ID';
+comment on column sys_post.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_post.dept_id is '閮ㄩ棬id';
+comment on column sys_post.post_code is '宀椾綅缂栫爜';
+comment on column sys_post.post_category is '宀椾綅绫诲埆缂栫爜';
+comment on column sys_post.post_name is '宀椾綅鍚嶇О';
+comment on column sys_post.post_sort is '鏄剧ず椤哄簭';
+comment on column sys_post.status is '鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_post.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_post.create_by is '鍒涘缓鑰�';
+comment on column sys_post.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_post.update_by is '鏇存柊鑰�';
+comment on column sys_post.update_time is '鏇存柊鏃堕棿';
+comment on column sys_post.remark is '澶囨敞';
+
+-- ----------------------------
+-- 鍒濆鍖�-宀椾綅淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+insert into sys_post values(1, '000000', 103, 'ceo', null, '钁d簨闀�', 1, '0', 103, 1, now(), null, null, '');
+insert into sys_post values(2, '000000', 100, 'se', null, '椤圭洰缁忕悊', 2, '0', 103, 1, now(), null, null, '');
+insert into sys_post values(3, '000000', 100, 'hr', null, '浜哄姏璧勬簮', 3, '0', 103, 1, now(), null, null, '');
+insert into sys_post values(4, '000000', 100, 'user', null, '鏅�氬憳宸�', 4, '0', 103, 1, now(), null, null, '');
+
+-- ----------------------------
+-- 4銆佽鑹蹭俊鎭〃
+-- ----------------------------
+create table if not exists sys_role
+(
+ role_id int8,
+ tenant_id varchar(20) default '000000'::varchar,
+ role_name varchar(30) not null,
+ role_key varchar(100) not null,
+ role_sort int4 not null,
+ data_scope char default '1'::bpchar,
+ menu_check_strictly bool default true,
+ dept_check_strictly bool default true,
+ status char not null,
+ del_flag char default '0'::bpchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ remark varchar(500) default null::varchar,
+ constraint "sys_role_pk" primary key (role_id)
+);
+
+comment on table sys_role is '瑙掕壊淇℃伅琛�';
+comment on column sys_role.role_id is '瑙掕壊ID';
+comment on column sys_role.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_role.role_name is '瑙掕壊鍚嶇О';
+comment on column sys_role.role_key is '瑙掕壊鏉冮檺瀛楃涓�';
+comment on column sys_role.role_sort is '鏄剧ず椤哄簭';
+comment on column sys_role.data_scope is '鏁版嵁鑼冨洿锛�1锛氬叏閮ㄦ暟鎹潈闄� 2锛氳嚜瀹氭暟鎹潈闄� 3锛氭湰閮ㄩ棬鏁版嵁鏉冮檺 4锛氭湰閮ㄩ棬鍙婁互涓嬫暟鎹潈闄� 5锛氫粎鏈汉鏁版嵁鏉冮檺 6锛氶儴闂ㄥ強浠ヤ笅鎴栨湰浜烘暟鎹潈闄愶級';
+comment on column sys_role.menu_check_strictly is '鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�';
+comment on column sys_role.dept_check_strictly is '閮ㄩ棬鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�';
+comment on column sys_role.status is '瑙掕壊鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_role.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+comment on column sys_role.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_role.create_by is '鍒涘缓鑰�';
+comment on column sys_role.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_role.update_by is '鏇存柊鑰�';
+comment on column sys_role.update_time is '鏇存柊鏃堕棿';
+comment on column sys_role.remark is '澶囨敞';
+
+-- ----------------------------
+-- 鍒濆鍖�-瑙掕壊淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+insert into sys_role values('1', '000000', '瓒呯骇绠$悊鍛�', 'superadmin', 1, '1', 't', 't', '0', '0', 103, 1, now(), null, null, '瓒呯骇绠$悊鍛�');
+insert into sys_role values('3', '000000', '鏈儴闂ㄥ強浠ヤ笅', 'test1', 3, '4', 't', 't', '0', '0', 103, 1, now(), NULL, NULL, '');
+insert into sys_role values('4', '000000', '浠呮湰浜�', 'test2', 4, '5', 't', 't', '0', '0', 103, 1, now(), NULL, NULL, '');
+
+-- ----------------------------
+-- 5銆佽彍鍗曟潈闄愯〃
+-- ----------------------------
+create table if not exists sys_menu
+(
+ menu_id int8,
+ menu_name varchar(50) not null,
+ parent_id int8 default 0,
+ order_num int4 default 0,
+ path varchar(200) default ''::varchar,
+ component varchar(255) default null::varchar,
+ query_param varchar(255) default null::varchar,
+ is_frame char default '1'::bpchar,
+ is_cache char default '0'::bpchar,
+ menu_type char default ''::bpchar,
+ visible char default '0'::bpchar,
+ status char default '0'::bpchar,
+ perms varchar(100) default null::varchar,
+ icon varchar(100) default '#'::varchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ remark varchar(500) default ''::varchar,
+ constraint "sys_menu_pk" primary key (menu_id)
+);
+
+comment on table sys_menu is '鑿滃崟鏉冮檺琛�';
+comment on column sys_menu.menu_id is '鑿滃崟ID';
+comment on column sys_menu.menu_name is '鑿滃崟鍚嶇О';
+comment on column sys_menu.parent_id is '鐖惰彍鍗旾D';
+comment on column sys_menu.order_num is '鏄剧ず椤哄簭';
+comment on column sys_menu.path is '璺敱鍦板潃';
+comment on column sys_menu.component is '缁勪欢璺緞';
+comment on column sys_menu.query_param is '璺敱鍙傛暟';
+comment on column sys_menu.is_frame is '鏄惁涓哄閾撅紙0鏄� 1鍚︼級';
+comment on column sys_menu.is_cache is '鏄惁缂撳瓨锛�0缂撳瓨 1涓嶇紦瀛橈級';
+comment on column sys_menu.menu_type is '鑿滃崟绫诲瀷锛圡鐩綍 C鑿滃崟 F鎸夐挳锛�';
+comment on column sys_menu.visible is '鏄剧ず鐘舵�侊紙0鏄剧ず 1闅愯棌锛�';
+comment on column sys_menu.status is '鑿滃崟鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_menu.perms is '鏉冮檺鏍囪瘑';
+comment on column sys_menu.icon is '鑿滃崟鍥炬爣';
+comment on column sys_menu.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_menu.create_by is '鍒涘缓鑰�';
+comment on column sys_menu.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_menu.update_by is '鏇存柊鑰�';
+comment on column sys_menu.update_time is '鏇存柊鏃堕棿';
+comment on column sys_menu.remark is '澶囨敞';
+
+-- ----------------------------
+-- 鍒濆鍖�-鑿滃崟淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+-- 涓�绾ц彍鍗�
+insert into sys_menu values('1', '绯荤粺绠$悊', '0', '1', 'system', null, '', '1', '0', 'M', '0', '0', '', 'system', 103, 1, now(), null, null, '绯荤粺绠$悊鐩綍');
+insert into sys_menu values('6', '绉熸埛绠$悊', '0', '2', 'tenant', null, '', '1', '0', 'M', '0', '0', '', 'chart', 103, 1, now(), null, null, '绉熸埛绠$悊鐩綍');
+insert into sys_menu values('2', '绯荤粺鐩戞帶', '0', '3', 'monitor', null, '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, now(), null, null, '绯荤粺鐩戞帶鐩綍');
+insert into sys_menu values('3', '绯荤粺宸ュ叿', '0', '4', 'tool', null, '', '1', '0', 'M', '0', '0', '', 'tool', 103, 1, now(), null, null, '绯荤粺宸ュ叿鐩綍');
+insert into sys_menu values('4', 'PLUS瀹樼綉', '0', '5', 'https://gitee.com/dromara/RuoYi-Vue-Plus', null, '', '0', '0', 'M', '0', '0', '', 'guide', 103, 1, now(), null, null, 'RuoYi-Vue-Plus瀹樼綉鍦板潃');
+insert into sys_menu VALUES('5', '娴嬭瘯鑿滃崟', '0', '5', 'demo', null, '', '1', '0', 'M', '0', '0', null, 'star', 103, 1, now(), null, null, '娴嬭瘯鑿滃崟');
+-- 浜岀骇鑿滃崟
+insert into sys_menu values('100', '鐢ㄦ埛绠$悊', '1', '1', 'user', 'system/user/index', '', '1', '0', 'C', '0', '0', 'system:user:list', 'user', 103, 1, now(), null, null, '鐢ㄦ埛绠$悊鑿滃崟');
+insert into sys_menu values('101', '瑙掕壊绠$悊', '1', '2', 'role', 'system/role/index', '', '1', '0', 'C', '0', '0', 'system:role:list', 'peoples', 103, 1, now(), null, null, '瑙掕壊绠$悊鑿滃崟');
+insert into sys_menu values('102', '鑿滃崟绠$悊', '1', '3', 'menu', 'system/menu/index', '', '1', '0', 'C', '0', '0', 'system:menu:list', 'tree-table', 103, 1, now(), null, null, '鑿滃崟绠$悊鑿滃崟');
+insert into sys_menu values('103', '閮ㄩ棬绠$悊', '1', '4', 'dept', 'system/dept/index', '', '1', '0', 'C', '0', '0', 'system:dept:list', 'tree', 103, 1, now(), null, null, '閮ㄩ棬绠$悊鑿滃崟');
+insert into sys_menu values('104', '宀椾綅绠$悊', '1', '5', 'post', 'system/post/index', '', '1', '0', 'C', '0', '0', 'system:post:list', 'post', 103, 1, now(), null, null, '宀椾綅绠$悊鑿滃崟');
+insert into sys_menu values('105', '瀛楀吀绠$悊', '1', '6', 'dict', 'system/dict/index', '', '1', '0', 'C', '0', '0', 'system:dict:list', 'dict', 103, 1, now(), null, null, '瀛楀吀绠$悊鑿滃崟');
+insert into sys_menu values('106', '鍙傛暟璁剧疆', '1', '7', 'config', 'system/config/index', '', '1', '0', 'C', '0', '0', 'system:config:list', 'edit', 103, 1, now(), null, null, '鍙傛暟璁剧疆鑿滃崟');
+insert into sys_menu values('107', '閫氱煡鍏憡', '1', '8', 'notice', 'system/notice/index', '', '1', '0', 'C', '0', '0', 'system:notice:list', 'message', 103, 1, now(), null, null, '閫氱煡鍏憡鑿滃崟');
+insert into sys_menu values('108', '鏃ュ織绠$悊', '1', '9', 'log', '', '', '1', '0', 'M', '0', '0', '', 'log', 103, 1, now(), null, null, '鏃ュ織绠$悊鑿滃崟');
+insert into sys_menu values('109', '鍦ㄧ嚎鐢ㄦ埛', '2', '1', 'online', 'monitor/online/index', '', '1', '0', 'C', '0', '0', 'monitor:online:list', 'online', 103, 1, now(), null, null, '鍦ㄧ嚎鐢ㄦ埛鑿滃崟');
+insert into sys_menu values('113', '缂撳瓨鐩戞帶', '2', '5', 'cache', 'monitor/cache/index', '', '1', '0', 'C', '0', '0', 'monitor:cache:list', 'redis', 103, 1, now(), null, null, '缂撳瓨鐩戞帶鑿滃崟');
+insert into sys_menu values('115', '浠g爜鐢熸垚', '3', '2', 'gen', 'tool/gen/index', '', '1', '0', 'C', '0', '0', 'tool:gen:list', 'code', 103, 1, now(), null, null, '浠g爜鐢熸垚鑿滃崟');
+insert into sys_menu values('121', '绉熸埛绠$悊', '6', '1', 'tenant', 'system/tenant/index', '', '1', '0', 'C', '0', '0', 'system:tenant:list', 'list', 103, 1, now(), null, null, '绉熸埛绠$悊鑿滃崟');
+insert into sys_menu values('122', '绉熸埛濂楅绠$悊', '6', '2', 'tenantPackage', 'system/tenantPackage/index', '', '1', '0', 'C', '0', '0', 'system:tenantPackage:list', 'form', 103, 1, now(), null, null, '绉熸埛濂楅绠$悊鑿滃崟');
+insert into sys_menu values('123', '瀹㈡埛绔鐞�', '1', '11', 'client', 'system/client/index', '', '1', '0', 'C', '0', '0', 'system:client:list', 'international', 103, 1, now(), null, null, '瀹㈡埛绔鐞嗚彍鍗�');
+insert into sys_menu values('116', '淇敼鐢熸垚閰嶇疆', '3', '2', 'gen-edit/index/:tableId', 'tool/gen/editTable', '', '1', '1', 'C', '1', '0', 'tool:gen:edit', '#', 103, 1, now(), null, null, '/tool/gen');
+insert into sys_menu values('130', '鍒嗛厤鐢ㄦ埛', '1', '2', 'role-auth/user/:roleId', 'system/role/authUser', '', '1', '1', 'C', '1', '0', 'system:role:edit', '#', 103, 1, now(), null, null, '/system/role');
+insert into sys_menu values('131', '鍒嗛厤瑙掕壊', '1', '1', 'user-auth/role/:userId', 'system/user/authRole', '', '1', '1', 'C', '1', '0', 'system:user:edit', '#', 103, 1, now(), null, null, '/system/user');
+insert into sys_menu values('132', '瀛楀吀鏁版嵁', '1', '6', 'dict-data/index/:dictId', 'system/dict/data', '', '1', '1', 'C', '1', '0', 'system:dict:list', '#', 103, 1, now(), null, null, '/system/dict');
+insert into sys_menu values('133', '鏂囦欢閰嶇疆绠$悊', '1', '10', 'oss-config/index', 'system/oss/config', '', '1', '1', 'C', '1', '0', 'system:ossConfig:list', '#', 103, 1, now(), null, null, '/system/oss');
+
+-- springboot-admin鐩戞帶
+insert into sys_menu values('117', 'Admin鐩戞帶', '2', '5', 'Admin', 'monitor/admin/index', '', '1', '0', 'C', '0', '0', 'monitor:admin:list', 'dashboard', 103, 1, now(), null, null, 'Admin鐩戞帶鑿滃崟');
+-- oss鑿滃崟
+insert into sys_menu values('118', '鏂囦欢绠$悊', '1', '10', 'oss', 'system/oss/index', '', '1', '0', 'C', '0', '0', 'system:oss:list', 'upload', 103, 1, now(), null, null, '鏂囦欢绠$悊鑿滃崟');
+-- snail-job server鎺у埗鍙�
+insert into sys_menu values('120', '浠诲姟璋冨害涓績', '2', '6', 'snailjob', 'monitor/snailjob/index', '', '1', '0', 'C', '0', '0', 'monitor:snailjob:list', 'job', 103, 1, now(), null, null, 'SnailJob鎺у埗鍙拌彍鍗�');
+
+-- 涓夌骇鑿滃崟
+insert into sys_menu values('500', '鎿嶄綔鏃ュ織', '108', '1', 'operlog', 'monitor/operlog/index', '', '1', '0', 'C', '0', '0', 'monitor:operlog:list', 'form', 103, 1, now(), null, null, '鎿嶄綔鏃ュ織鑿滃崟');
+insert into sys_menu values('501', '鐧诲綍鏃ュ織', '108', '2', 'logininfor', 'monitor/logininfor/index', '', '1', '0', 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 103, 1, now(), null, null, '鐧诲綍鏃ュ織鑿滃崟');
+-- 鐢ㄦ埛绠$悊鎸夐挳
+insert into sys_menu values('1001', '鐢ㄦ埛鏌ヨ', '100', '1', '', '', '', '1', '0', 'F', '0', '0', 'system:user:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1002', '鐢ㄦ埛鏂板', '100', '2', '', '', '', '1', '0', 'F', '0', '0', 'system:user:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1003', '鐢ㄦ埛淇敼', '100', '3', '', '', '', '1', '0', 'F', '0', '0', 'system:user:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1004', '鐢ㄦ埛鍒犻櫎', '100', '4', '', '', '', '1', '0', 'F', '0', '0', 'system:user:remove', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1005', '鐢ㄦ埛瀵煎嚭', '100', '5', '', '', '', '1', '0', 'F', '0', '0', 'system:user:export', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1006', '鐢ㄦ埛瀵煎叆', '100', '6', '', '', '', '1', '0', 'F', '0', '0', 'system:user:import', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1007', '閲嶇疆瀵嗙爜', '100', '7', '', '', '', '1', '0', 'F', '0', '0', 'system:user:resetPwd', '#', 103, 1, now(), null, null, '');
+-- 瑙掕壊绠$悊鎸夐挳
+insert into sys_menu values('1008', '瑙掕壊鏌ヨ', '101', '1', '', '', '', '1', '0', 'F', '0', '0', 'system:role:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1009', '瑙掕壊鏂板', '101', '2', '', '', '', '1', '0', 'F', '0', '0', 'system:role:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1010', '瑙掕壊淇敼', '101', '3', '', '', '', '1', '0', 'F', '0', '0', 'system:role:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1011', '瑙掕壊鍒犻櫎', '101', '4', '', '', '', '1', '0', 'F', '0', '0', 'system:role:remove', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1012', '瑙掕壊瀵煎嚭', '101', '5', '', '', '', '1', '0', 'F', '0', '0', 'system:role:export', '#', 103, 1, now(), null, null, '');
+-- 鑿滃崟绠$悊鎸夐挳
+insert into sys_menu values('1013', '鑿滃崟鏌ヨ', '102', '1', '', '', '', '1', '0', 'F', '0', '0', 'system:menu:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1014', '鑿滃崟鏂板', '102', '2', '', '', '', '1', '0', 'F', '0', '0', 'system:menu:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1015', '鑿滃崟淇敼', '102', '3', '', '', '', '1', '0', 'F', '0', '0', 'system:menu:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1016', '鑿滃崟鍒犻櫎', '102', '4', '', '', '', '1', '0', 'F', '0', '0', 'system:menu:remove', '#', 103, 1, now(), null, null, '');
+-- 閮ㄩ棬绠$悊鎸夐挳
+insert into sys_menu values('1017', '閮ㄩ棬鏌ヨ', '103', '1', '', '', '', '1', '0', 'F', '0', '0', 'system:dept:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1018', '閮ㄩ棬鏂板', '103', '2', '', '', '', '1', '0', 'F', '0', '0', 'system:dept:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1019', '閮ㄩ棬淇敼', '103', '3', '', '', '', '1', '0', 'F', '0', '0', 'system:dept:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1020', '閮ㄩ棬鍒犻櫎', '103', '4', '', '', '', '1', '0', 'F', '0', '0', 'system:dept:remove', '#', 103, 1, now(), null, null, '');
+-- 宀椾綅绠$悊鎸夐挳
+insert into sys_menu values('1021', '宀椾綅鏌ヨ', '104', '1', '', '', '', '1', '0', 'F', '0', '0', 'system:post:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1022', '宀椾綅鏂板', '104', '2', '', '', '', '1', '0', 'F', '0', '0', 'system:post:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1023', '宀椾綅淇敼', '104', '3', '', '', '', '1', '0', 'F', '0', '0', 'system:post:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1024', '宀椾綅鍒犻櫎', '104', '4', '', '', '', '1', '0', 'F', '0', '0', 'system:post:remove', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1025', '宀椾綅瀵煎嚭', '104', '5', '', '', '', '1', '0', 'F', '0', '0', 'system:post:export', '#', 103, 1, now(), null, null, '');
+-- 瀛楀吀绠$悊鎸夐挳
+insert into sys_menu values('1026', '瀛楀吀鏌ヨ', '105', '1', '#', '', '', '1', '0', 'F', '0', '0', 'system:dict:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1027', '瀛楀吀鏂板', '105', '2', '#', '', '', '1', '0', 'F', '0', '0', 'system:dict:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1028', '瀛楀吀淇敼', '105', '3', '#', '', '', '1', '0', 'F', '0', '0', 'system:dict:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1029', '瀛楀吀鍒犻櫎', '105', '4', '#', '', '', '1', '0', 'F', '0', '0', 'system:dict:remove', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1030', '瀛楀吀瀵煎嚭', '105', '5', '#', '', '', '1', '0', 'F', '0', '0', 'system:dict:export', '#', 103, 1, now(), null, null, '');
+-- 鍙傛暟璁剧疆鎸夐挳
+insert into sys_menu values('1031', '鍙傛暟鏌ヨ', '106', '1', '#', '', '', '1', '0', 'F', '0', '0', 'system:config:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1032', '鍙傛暟鏂板', '106', '2', '#', '', '', '1', '0', 'F', '0', '0', 'system:config:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1033', '鍙傛暟淇敼', '106', '3', '#', '', '', '1', '0', 'F', '0', '0', 'system:config:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1034', '鍙傛暟鍒犻櫎', '106', '4', '#', '', '', '1', '0', 'F', '0', '0', 'system:config:remove', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1035', '鍙傛暟瀵煎嚭', '106', '5', '#', '', '', '1', '0', 'F', '0', '0', 'system:config:export', '#', 103, 1, now(), null, null, '');
+-- 閫氱煡鍏憡鎸夐挳
+insert into sys_menu values('1036', '鍏憡鏌ヨ', '107', '1', '#', '', '', '1', '0', 'F', '0', '0', 'system:notice:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1037', '鍏憡鏂板', '107', '2', '#', '', '', '1', '0', 'F', '0', '0', 'system:notice:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1038', '鍏憡淇敼', '107', '3', '#', '', '', '1', '0', 'F', '0', '0', 'system:notice:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1039', '鍏憡鍒犻櫎', '107', '4', '#', '', '', '1', '0', 'F', '0', '0', 'system:notice:remove', '#', 103, 1, now(), null, null, '');
+-- 鎿嶄綔鏃ュ織鎸夐挳
+insert into sys_menu values('1040', '鎿嶄綔鏌ヨ', '500', '1', '#', '', '', '1', '0', 'F', '0', '0', 'monitor:operlog:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1041', '鎿嶄綔鍒犻櫎', '500', '2', '#', '', '', '1', '0', 'F', '0', '0', 'monitor:operlog:remove', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1042', '鏃ュ織瀵煎嚭', '500', '4', '#', '', '', '1', '0', 'F', '0', '0', 'monitor:operlog:export', '#', 103, 1, now(), null, null, '');
+-- 鐧诲綍鏃ュ織鎸夐挳
+insert into sys_menu values('1043', '鐧诲綍鏌ヨ', '501', '1', '#', '', '', '1', '0', 'F', '0', '0', 'monitor:logininfor:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1044', '鐧诲綍鍒犻櫎', '501', '2', '#', '', '', '1', '0', 'F', '0', '0', 'monitor:logininfor:remove', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1045', '鏃ュ織瀵煎嚭', '501', '3', '#', '', '', '1', '0', 'F', '0', '0', 'monitor:logininfor:export', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1050', '璐︽埛瑙i攣', '501', '4', '#', '', '', '1', '0', 'F', '0', '0', 'monitor:logininfor:unlock', '#', 103, 1, now(), null, null, '');
+-- 鍦ㄧ嚎鐢ㄦ埛鎸夐挳
+insert into sys_menu values('1046', '鍦ㄧ嚎鏌ヨ', '109', '1', '#', '', '', '1', '0', 'F', '0', '0', 'monitor:online:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1047', '鎵归噺寮洪��', '109', '2', '#', '', '', '1', '0', 'F', '0', '0', 'monitor:online:batchLogout', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1048', '鍗曟潯寮洪��', '109', '3', '#', '', '', '1', '0', 'F', '0', '0', 'monitor:online:forceLogout', '#', 103, 1, now(), null, null, '');
+-- 浠g爜鐢熸垚鎸夐挳
+insert into sys_menu values('1055', '鐢熸垚鏌ヨ', '115', '1', '#', '', '', '1', '0', 'F', '0', '0', 'tool:gen:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1056', '鐢熸垚淇敼', '115', '2', '#', '', '', '1', '0', 'F', '0', '0', 'tool:gen:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1057', '鐢熸垚鍒犻櫎', '115', '3', '#', '', '', '1', '0', 'F', '0', '0', 'tool:gen:remove', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1058', '瀵煎叆浠g爜', '115', '2', '#', '', '', '1', '0', 'F', '0', '0', 'tool:gen:import', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1059', '棰勮浠g爜', '115', '4', '#', '', '', '1', '0', 'F', '0', '0', 'tool:gen:preview', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1060', '鐢熸垚浠g爜', '115', '5', '#', '', '', '1', '0', 'F', '0', '0', 'tool:gen:code', '#', 103, 1, now(), null, null, '');
+-- oss鐩稿叧鎸夐挳
+insert into sys_menu values('1600', '鏂囦欢鏌ヨ', '118', '1', '#', '', '', '1', '0', 'F', '0', '0', 'system:oss:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1601', '鏂囦欢涓婁紶', '118', '2', '#', '', '', '1', '0', 'F', '0', '0', 'system:oss:upload', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1602', '鏂囦欢涓嬭浇', '118', '3', '#', '', '', '1', '0', 'F', '0', '0', 'system:oss:download', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1603', '鏂囦欢鍒犻櫎', '118', '4', '#', '', '', '1', '0', 'F', '0', '0', 'system:oss:remove', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1620', '閰嶇疆鍒楄〃', '118', '5', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:list', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1621', '閰嶇疆娣诲姞', '118', '6', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1622', '閰嶇疆缂栬緫', '118', '6', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1623', '閰嶇疆鍒犻櫎', '118', '6', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:remove', '#', 103, 1, now(), null, null, '');
+-- 绉熸埛绠$悊鐩稿叧鎸夐挳
+insert into sys_menu values('1606', '绉熸埛鏌ヨ', '121', '1', '#', '', '', '1', '0', 'F', '0', '0', 'system:tenant:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1607', '绉熸埛鏂板', '121', '2', '#', '', '', '1', '0', 'F', '0', '0', 'system:tenant:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1608', '绉熸埛淇敼', '121', '3', '#', '', '', '1', '0', 'F', '0', '0', 'system:tenant:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1609', '绉熸埛鍒犻櫎', '121', '4', '#', '', '', '1', '0', 'F', '0', '0', 'system:tenant:remove', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1610', '绉熸埛瀵煎嚭', '121', '5', '#', '', '', '1', '0', 'F', '0', '0', 'system:tenant:export', '#', 103, 1, now(), null, null, '');
+-- 绉熸埛濂楅绠$悊鐩稿叧鎸夐挳
+insert into sys_menu values('1611', '绉熸埛濂楅鏌ヨ', '122', '1', '#', '', '', '1', '0', 'F', '0', '0', 'system:tenantPackage:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1612', '绉熸埛濂楅鏂板', '122', '2', '#', '', '', '1', '0', 'F', '0', '0', 'system:tenantPackage:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1613', '绉熸埛濂楅淇敼', '122', '3', '#', '', '', '1', '0', 'F', '0', '0', 'system:tenantPackage:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1614', '绉熸埛濂楅鍒犻櫎', '122', '4', '#', '', '', '1', '0', 'F', '0', '0', 'system:tenantPackage:remove', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1615', '绉熸埛濂楅瀵煎嚭', '122', '5', '#', '', '', '1', '0', 'F', '0', '0', 'system:tenantPackage:export', '#', 103, 1, now(), null, null, '');
+-- 瀹㈡埛绔鐞嗘寜閽�
+insert into sys_menu values('1061', '瀹㈡埛绔鐞嗘煡璇�', '123', '1', '#', '', '', '1', '0', 'F', '0', '0', 'system:client:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1062', '瀹㈡埛绔鐞嗘柊澧�', '123', '2', '#', '', '', '1', '0', 'F', '0', '0', 'system:client:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1063', '瀹㈡埛绔鐞嗕慨鏀�', '123', '3', '#', '', '', '1', '0', 'F', '0', '0', 'system:client:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1064', '瀹㈡埛绔鐞嗗垹闄�', '123', '4', '#', '', '', '1', '0', 'F', '0', '0', 'system:client:remove', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1065', '瀹㈡埛绔鐞嗗鍑�', '123', '5', '#', '', '', '1', '0', 'F', '0', '0', 'system:client:export', '#', 103, 1, now(), null, null, '');
+-- 娴嬭瘯鑿滃崟
+INSERT INTO sys_menu VALUES('1500', '娴嬭瘯鍗曡〃', '5', '1', 'demo', 'demo/demo/index', '', '1', '0', 'C', '0', '0', 'demo:demo:list', '#', 103, 1, now(), NULL, NULL, '娴嬭瘯鍗曡〃鑿滃崟');
+INSERT INTO sys_menu VALUES('1501', '娴嬭瘯鍗曡〃鏌ヨ', '1500', '1', '#', '', '', '1', '0', 'F', '0', '0', 'demo:demo:query', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES('1502', '娴嬭瘯鍗曡〃鏂板', '1500', '2', '#', '', '', '1', '0', 'F', '0', '0', 'demo:demo:add', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES('1503', '娴嬭瘯鍗曡〃淇敼', '1500', '3', '#', '', '', '1', '0', 'F', '0', '0', 'demo:demo:edit', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES('1504', '娴嬭瘯鍗曡〃鍒犻櫎', '1500', '4', '#', '', '', '1', '0', 'F', '0', '0', 'demo:demo:remove', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES('1505', '娴嬭瘯鍗曡〃瀵煎嚭', '1500', '5', '#', '', '', '1', '0', 'F', '0', '0', 'demo:demo:export', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES('1506', '娴嬭瘯鏍戣〃', '5', '1', 'tree', 'demo/tree/index', '', '1', '0', 'C', '0', '0', 'demo:tree:list', '#', 103, 1, now(), NULL, NULL, '娴嬭瘯鏍戣〃鑿滃崟');
+INSERT INTO sys_menu VALUES('1507', '娴嬭瘯鏍戣〃鏌ヨ', '1506', '1', '#', '', '', '1', '0', 'F', '0', '0', 'demo:tree:query', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES('1508', '娴嬭瘯鏍戣〃鏂板', '1506', '2', '#', '', '', '1', '0', 'F', '0', '0', 'demo:tree:add', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES('1509', '娴嬭瘯鏍戣〃淇敼', '1506', '3', '#', '', '', '1', '0', 'F', '0', '0', 'demo:tree:edit', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES('1510', '娴嬭瘯鏍戣〃鍒犻櫎', '1506', '4', '#', '', '', '1', '0', 'F', '0', '0', 'demo:tree:remove', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES('1511', '娴嬭瘯鏍戣〃瀵煎嚭', '1506', '5', '#', '', '', '1', '0', 'F', '0', '0', 'demo:tree:export', '#', 103, 1, now(), NULL, NULL, '');
+
+
+-- ----------------------------
+-- 6銆佺敤鎴峰拰瑙掕壊鍏宠仈琛� 鐢ㄦ埛N-1瑙掕壊
+-- ----------------------------
+create table if not exists sys_user_role
+(
+ user_id int8 not null,
+ role_id int8 not null,
+ constraint sys_user_role_pk primary key (user_id, role_id)
+);
+
+comment on table sys_user_role is '鐢ㄦ埛鍜岃鑹插叧鑱旇〃';
+comment on column sys_user_role.user_id is '鐢ㄦ埛ID';
+comment on column sys_user_role.role_id is '瑙掕壊ID';
+
+-- ----------------------------
+-- 鍒濆鍖�-鐢ㄦ埛鍜岃鑹插叧鑱旇〃鏁版嵁
+-- ----------------------------
+insert into sys_user_role values ('1', '1');
+insert into sys_user_role values ('3', '3');
+insert into sys_user_role values ('4', '4');
+
+-- ----------------------------
+-- 7銆佽鑹插拰鑿滃崟鍏宠仈琛� 瑙掕壊1-N鑿滃崟
+-- ----------------------------
+create table if not exists sys_role_menu
+(
+ role_id int8 not null,
+ menu_id int8 not null,
+ constraint sys_role_menu_pk primary key (role_id, menu_id)
+);
+
+comment on table sys_role_menu is '瑙掕壊鍜岃彍鍗曞叧鑱旇〃';
+comment on column sys_role_menu.role_id is '瑙掕壊ID';
+comment on column sys_role_menu.menu_id is '鑿滃崟ID';
+
+-- ----------------------------
+-- 鍒濆鍖�-瑙掕壊鍜岃彍鍗曞叧鑱旇〃鏁版嵁
+-- ----------------------------
+insert into sys_role_menu values ('3', '1');
+insert into sys_role_menu values ('3', '5');
+insert into sys_role_menu values ('3', '100');
+insert into sys_role_menu values ('3', '101');
+insert into sys_role_menu values ('3', '102');
+insert into sys_role_menu values ('3', '103');
+insert into sys_role_menu values ('3', '104');
+insert into sys_role_menu values ('3', '105');
+insert into sys_role_menu values ('3', '106');
+insert into sys_role_menu values ('3', '107');
+insert into sys_role_menu values ('3', '108');
+insert into sys_role_menu values ('3', '118');
+insert into sys_role_menu values ('3', '123');
+insert into sys_role_menu values ('3', '130');
+insert into sys_role_menu values ('3', '131');
+insert into sys_role_menu values ('3', '132');
+insert into sys_role_menu values ('3', '133');
+insert into sys_role_menu values ('3', '500');
+insert into sys_role_menu values ('3', '501');
+insert into sys_role_menu values ('3', '1001');
+insert into sys_role_menu values ('3', '1002');
+insert into sys_role_menu values ('3', '1003');
+insert into sys_role_menu values ('3', '1004');
+insert into sys_role_menu values ('3', '1005');
+insert into sys_role_menu values ('3', '1006');
+insert into sys_role_menu values ('3', '1007');
+insert into sys_role_menu values ('3', '1008');
+insert into sys_role_menu values ('3', '1009');
+insert into sys_role_menu values ('3', '1010');
+insert into sys_role_menu values ('3', '1011');
+insert into sys_role_menu values ('3', '1012');
+insert into sys_role_menu values ('3', '1013');
+insert into sys_role_menu values ('3', '1014');
+insert into sys_role_menu values ('3', '1015');
+insert into sys_role_menu values ('3', '1016');
+insert into sys_role_menu values ('3', '1017');
+insert into sys_role_menu values ('3', '1018');
+insert into sys_role_menu values ('3', '1019');
+insert into sys_role_menu values ('3', '1020');
+insert into sys_role_menu values ('3', '1021');
+insert into sys_role_menu values ('3', '1022');
+insert into sys_role_menu values ('3', '1023');
+insert into sys_role_menu values ('3', '1024');
+insert into sys_role_menu values ('3', '1025');
+insert into sys_role_menu values ('3', '1026');
+insert into sys_role_menu values ('3', '1027');
+insert into sys_role_menu values ('3', '1028');
+insert into sys_role_menu values ('3', '1029');
+insert into sys_role_menu values ('3', '1030');
+insert into sys_role_menu values ('3', '1031');
+insert into sys_role_menu values ('3', '1032');
+insert into sys_role_menu values ('3', '1033');
+insert into sys_role_menu values ('3', '1034');
+insert into sys_role_menu values ('3', '1035');
+insert into sys_role_menu values ('3', '1036');
+insert into sys_role_menu values ('3', '1037');
+insert into sys_role_menu values ('3', '1038');
+insert into sys_role_menu values ('3', '1039');
+insert into sys_role_menu values ('3', '1040');
+insert into sys_role_menu values ('3', '1041');
+insert into sys_role_menu values ('3', '1042');
+insert into sys_role_menu values ('3', '1043');
+insert into sys_role_menu values ('3', '1044');
+insert into sys_role_menu values ('3', '1045');
+insert into sys_role_menu values ('3', '1050');
+insert into sys_role_menu values ('3', '1061');
+insert into sys_role_menu values ('3', '1062');
+insert into sys_role_menu values ('3', '1063');
+insert into sys_role_menu values ('3', '1064');
+insert into sys_role_menu values ('3', '1065');
+insert into sys_role_menu values ('3', '1500');
+insert into sys_role_menu values ('3', '1501');
+insert into sys_role_menu values ('3', '1502');
+insert into sys_role_menu values ('3', '1503');
+insert into sys_role_menu values ('3', '1504');
+insert into sys_role_menu values ('3', '1505');
+insert into sys_role_menu values ('3', '1506');
+insert into sys_role_menu values ('3', '1507');
+insert into sys_role_menu values ('3', '1508');
+insert into sys_role_menu values ('3', '1509');
+insert into sys_role_menu values ('3', '1510');
+insert into sys_role_menu values ('3', '1511');
+insert into sys_role_menu values ('3', '1600');
+insert into sys_role_menu values ('3', '1601');
+insert into sys_role_menu values ('3', '1602');
+insert into sys_role_menu values ('3', '1603');
+insert into sys_role_menu values ('3', '1620');
+insert into sys_role_menu values ('3', '1621');
+insert into sys_role_menu values ('3', '1622');
+insert into sys_role_menu values ('3', '1623');
+insert into sys_role_menu values ('3', '11616');
+insert into sys_role_menu values ('3', '11618');
+insert into sys_role_menu values ('3', '11619');
+insert into sys_role_menu values ('3', '11622');
+insert into sys_role_menu values ('3', '11623');
+insert into sys_role_menu values ('3', '11629');
+insert into sys_role_menu values ('3', '11632');
+insert into sys_role_menu values ('3', '11633');
+insert into sys_role_menu values ('3', '11638');
+insert into sys_role_menu values ('3', '11639');
+insert into sys_role_menu values ('3', '11640');
+insert into sys_role_menu values ('3', '11641');
+insert into sys_role_menu values ('3', '11642');
+insert into sys_role_menu values ('3', '11643');
+insert into sys_role_menu values ('3', '11701');
+insert into sys_role_menu values ('4', '5');
+insert into sys_role_menu values ('4', '1500');
+insert into sys_role_menu values ('4', '1501');
+insert into sys_role_menu values ('4', '1502');
+insert into sys_role_menu values ('4', '1503');
+insert into sys_role_menu values ('4', '1504');
+insert into sys_role_menu values ('4', '1505');
+insert into sys_role_menu values ('4', '1506');
+insert into sys_role_menu values ('4', '1507');
+insert into sys_role_menu values ('4', '1508');
+insert into sys_role_menu values ('4', '1509');
+insert into sys_role_menu values ('4', '1510');
+insert into sys_role_menu values ('4', '1511');
+
+-- ----------------------------
+-- 8銆佽鑹插拰閮ㄩ棬鍏宠仈琛� 瑙掕壊1-N閮ㄩ棬
+-- ----------------------------
+create table if not exists sys_role_dept
+(
+ role_id int8 not null,
+ dept_id int8 not null,
+ constraint sys_role_dept_pk primary key (role_id, dept_id)
+);
+
+comment on table sys_role_dept is '瑙掕壊鍜岄儴闂ㄥ叧鑱旇〃';
+comment on column sys_role_dept.role_id is '瑙掕壊ID';
+comment on column sys_role_dept.dept_id is '閮ㄩ棬ID';
+
+
+-- ----------------------------
+-- 9銆佺敤鎴蜂笌宀椾綅鍏宠仈琛� 鐢ㄦ埛1-N宀椾綅
+-- ----------------------------
+create table if not exists sys_user_post
+(
+ user_id int8 not null,
+ post_id int8 not null,
+ constraint sys_user_post_pk primary key (user_id, post_id)
+);
+
+comment on table sys_user_post is '鐢ㄦ埛涓庡矖浣嶅叧鑱旇〃';
+comment on column sys_user_post.user_id is '鐢ㄦ埛ID';
+comment on column sys_user_post.post_id is '宀椾綅ID';
+
+-- ----------------------------
+-- 鍒濆鍖�-鐢ㄦ埛涓庡矖浣嶅叧鑱旇〃鏁版嵁
+-- ----------------------------
+insert into sys_user_post values ('1', '1');
+
+-- ----------------------------
+-- 10銆佹搷浣滄棩蹇楄褰�
+-- ----------------------------
+create table if not exists sys_oper_log
+(
+ oper_id int8,
+ tenant_id varchar(20) default '000000'::varchar,
+ title varchar(50) default ''::varchar,
+ business_type int4 default 0,
+ method varchar(100) default ''::varchar,
+ request_method varchar(10) default ''::varchar,
+ operator_type int4 default 0,
+ oper_name varchar(50) default ''::varchar,
+ dept_name varchar(50) default ''::varchar,
+ oper_url varchar(255) default ''::varchar,
+ oper_ip varchar(128) default ''::varchar,
+ oper_location varchar(255) default ''::varchar,
+ oper_param varchar(4000) default ''::varchar,
+ json_result varchar(4000) default ''::varchar,
+ status int4 default 0,
+ error_msg varchar(4000) default ''::varchar,
+ oper_time timestamp,
+ cost_time int8 default 0,
+ constraint sys_oper_log_pk primary key (oper_id)
+);
+
+create index idx_sys_oper_log_bt ON sys_oper_log (business_type);
+create index idx_sys_oper_log_s ON sys_oper_log (status);
+create index idx_sys_oper_log_ot ON sys_oper_log (oper_time);
+
+comment on table sys_oper_log is '鎿嶄綔鏃ュ織璁板綍';
+comment on column sys_oper_log.oper_id is '鏃ュ織涓婚敭';
+comment on column sys_oper_log.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_oper_log.title is '妯″潡鏍囬';
+comment on column sys_oper_log.business_type is '涓氬姟绫诲瀷锛�0鍏跺畠 1鏂板 2淇敼 3鍒犻櫎锛�';
+comment on column sys_oper_log.method is '鏂规硶鍚嶇О';
+comment on column sys_oper_log.request_method is '璇锋眰鏂瑰紡';
+comment on column sys_oper_log.operator_type is '鎿嶄綔绫诲埆锛�0鍏跺畠 1鍚庡彴鐢ㄦ埛 2鎵嬫満绔敤鎴凤級';
+comment on column sys_oper_log.oper_name is '鎿嶄綔浜哄憳';
+comment on column sys_oper_log.dept_name is '閮ㄩ棬鍚嶇О';
+comment on column sys_oper_log.oper_url is '璇锋眰URL';
+comment on column sys_oper_log.oper_ip is '涓绘満鍦板潃';
+comment on column sys_oper_log.oper_location is '鎿嶄綔鍦扮偣';
+comment on column sys_oper_log.oper_param is '璇锋眰鍙傛暟';
+comment on column sys_oper_log.json_result is '杩斿洖鍙傛暟';
+comment on column sys_oper_log.status is '鎿嶄綔鐘舵�侊紙0姝e父 1寮傚父锛�';
+comment on column sys_oper_log.error_msg is '閿欒娑堟伅';
+comment on column sys_oper_log.oper_time is '鎿嶄綔鏃堕棿';
+comment on column sys_oper_log.cost_time is '娑堣�楁椂闂�';
+
+-- ----------------------------
+-- 11銆佸瓧鍏哥被鍨嬭〃
+-- ----------------------------
+create table if not exists sys_dict_type
+(
+ dict_id int8,
+ tenant_id varchar(20) default '000000'::varchar,
+ dict_name varchar(100) default ''::varchar,
+ dict_type varchar(100) default ''::varchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ remark varchar(500) default null::varchar,
+ constraint sys_dict_type_pk primary key (dict_id)
+);
+
+create unique index sys_dict_type_index1 ON sys_dict_type (tenant_id, dict_type);
+
+comment on table sys_dict_type is '瀛楀吀绫诲瀷琛�';
+comment on column sys_dict_type.dict_id is '瀛楀吀涓婚敭';
+comment on column sys_dict_type.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_dict_type.dict_name is '瀛楀吀鍚嶇О';
+comment on column sys_dict_type.dict_type is '瀛楀吀绫诲瀷';
+comment on column sys_dict_type.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_dict_type.create_by is '鍒涘缓鑰�';
+comment on column sys_dict_type.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_dict_type.update_by is '鏇存柊鑰�';
+comment on column sys_dict_type.update_time is '鏇存柊鏃堕棿';
+comment on column sys_dict_type.remark is '澶囨敞';
+
+insert into sys_dict_type values(1, '000000', '鐢ㄦ埛鎬у埆', 'sys_user_sex', 103, 1, now(), null, null, '鐢ㄦ埛鎬у埆鍒楄〃');
+insert into sys_dict_type values(2, '000000', '鑿滃崟鐘舵��', 'sys_show_hide', 103, 1, now(), null, null, '鑿滃崟鐘舵�佸垪琛�');
+insert into sys_dict_type values(3, '000000', '绯荤粺寮�鍏�', 'sys_normal_disable', 103, 1, now(), null, null, '绯荤粺寮�鍏冲垪琛�');
+insert into sys_dict_type values(6, '000000', '绯荤粺鏄惁', 'sys_yes_no', 103, 1, now(), null, null, '绯荤粺鏄惁鍒楄〃');
+insert into sys_dict_type values(7, '000000', '閫氱煡绫诲瀷', 'sys_notice_type', 103, 1, now(), null, null, '閫氱煡绫诲瀷鍒楄〃');
+insert into sys_dict_type values(8, '000000', '閫氱煡鐘舵��', 'sys_notice_status', 103, 1, now(), null, null, '閫氱煡鐘舵�佸垪琛�');
+insert into sys_dict_type values(9, '000000', '鎿嶄綔绫诲瀷', 'sys_oper_type', 103, 1, now(), null, null, '鎿嶄綔绫诲瀷鍒楄〃');
+insert into sys_dict_type values(10, '000000', '绯荤粺鐘舵��', 'sys_common_status', 103, 1, now(), null, null, '鐧诲綍鐘舵�佸垪琛�');
+insert into sys_dict_type values(11, '000000', '鎺堟潈绫诲瀷', 'sys_grant_type', 103, 1, now(), null, null, '璁よ瘉鎺堟潈绫诲瀷');
+insert into sys_dict_type values(12, '000000', '璁惧绫诲瀷', 'sys_device_type', 103, 1, now(), null, null, '瀹㈡埛绔澶囩被鍨�');
+
+-- ----------------------------
+-- 12銆佸瓧鍏告暟鎹〃
+-- ----------------------------
+create table if not exists sys_dict_data
+(
+ dict_code int8,
+ tenant_id varchar(20) default '000000'::varchar,
+ dict_sort int4 default 0,
+ dict_label varchar(100) default ''::varchar,
+ dict_value varchar(100) default ''::varchar,
+ dict_type varchar(100) default ''::varchar,
+ css_class varchar(100) default null::varchar,
+ list_class varchar(100) default null::varchar,
+ is_default char default 'N'::bpchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ remark varchar(500) default null::varchar,
+ constraint sys_dict_data_pk primary key (dict_code)
+);
+
+comment on table sys_dict_data is '瀛楀吀鏁版嵁琛�';
+comment on column sys_dict_data.dict_code is '瀛楀吀缂栫爜';
+comment on column sys_dict_type.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_dict_data.dict_sort is '瀛楀吀鎺掑簭';
+comment on column sys_dict_data.dict_label is '瀛楀吀鏍囩';
+comment on column sys_dict_data.dict_value is '瀛楀吀閿��';
+comment on column sys_dict_data.dict_type is '瀛楀吀绫诲瀷';
+comment on column sys_dict_data.css_class is '鏍峰紡灞炴�э紙鍏朵粬鏍峰紡鎵╁睍锛�';
+comment on column sys_dict_data.list_class is '琛ㄦ牸鍥炴樉鏍峰紡';
+comment on column sys_dict_data.is_default is '鏄惁榛樿锛圷鏄� N鍚︼級';
+comment on column sys_dict_data.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_dict_data.create_by is '鍒涘缓鑰�';
+comment on column sys_dict_data.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_dict_data.update_by is '鏇存柊鑰�';
+comment on column sys_dict_data.update_time is '鏇存柊鏃堕棿';
+comment on column sys_dict_data.remark is '澶囨敞';
+
+insert into sys_dict_data values(1, '000000', 1, '鐢�', '0', 'sys_user_sex', '', '', 'Y', 103, 1, now(), null, null, '鎬у埆鐢�');
+insert into sys_dict_data values(2, '000000', 2, '濂�', '1', 'sys_user_sex', '', '', 'N', 103, 1, now(), null, null, '鎬у埆濂�');
+insert into sys_dict_data values(3, '000000', 3, '鏈煡', '2', 'sys_user_sex', '', '', 'N', 103, 1, now(), null, null, '鎬у埆鏈煡');
+insert into sys_dict_data values(4, '000000', 1, '鏄剧ず', '0', 'sys_show_hide', '', 'primary', 'Y', 103, 1, now(), null, null, '鏄剧ず鑿滃崟');
+insert into sys_dict_data values(5, '000000', 2, '闅愯棌', '1', 'sys_show_hide', '', 'danger', 'N', 103, 1, now(), null, null, '闅愯棌鑿滃崟');
+insert into sys_dict_data values(6, '000000', 1, '姝e父', '0', 'sys_normal_disable', '', 'primary', 'Y', 103, 1, now(), null, null, '姝e父鐘舵��');
+insert into sys_dict_data values(7, '000000', 2, '鍋滅敤', '1', 'sys_normal_disable', '', 'danger', 'N', 103, 1, now(), null, null, '鍋滅敤鐘舵��');
+insert into sys_dict_data values(12, '000000', 1, '鏄�', 'Y', 'sys_yes_no', '', 'primary', 'Y', 103, 1, now(), null, null, '绯荤粺榛樿鏄�');
+insert into sys_dict_data values(13, '000000', 2, '鍚�', 'N', 'sys_yes_no', '', 'danger', 'N', 103, 1, now(), null, null, '绯荤粺榛樿鍚�');
+insert into sys_dict_data values(14, '000000', 1, '閫氱煡', '1', 'sys_notice_type', '', 'warning', 'Y', 103, 1, now(), null, null, '閫氱煡');
+insert into sys_dict_data values(15, '000000', 2, '鍏憡', '2', 'sys_notice_type', '', 'success', 'N', 103, 1, now(), null, null, '鍏憡');
+insert into sys_dict_data values(16, '000000', 1, '姝e父', '0', 'sys_notice_status', '', 'primary', 'Y', 103, 1, now(), null, null, '姝e父鐘舵��');
+insert into sys_dict_data values(17, '000000', 2, '鍏抽棴', '1', 'sys_notice_status', '', 'danger', 'N', 103, 1, now(), null, null, '鍏抽棴鐘舵��');
+insert into sys_dict_data values(29, '000000', 99, '鍏朵粬', '0', 'sys_oper_type', '', 'info', 'N', 103, 1, now(), null, null, '鍏朵粬鎿嶄綔');
+insert into sys_dict_data values(18, '000000', 1, '鏂板', '1', 'sys_oper_type', '', 'info', 'N', 103, 1, now(), null, null, '鏂板鎿嶄綔');
+insert into sys_dict_data values(19, '000000', 2, '淇敼', '2', 'sys_oper_type', '', 'info', 'N', 103, 1, now(), null, null, '淇敼鎿嶄綔');
+insert into sys_dict_data values(20, '000000', 3, '鍒犻櫎', '3', 'sys_oper_type', '', 'danger', 'N', 103, 1, now(), null, null, '鍒犻櫎鎿嶄綔');
+insert into sys_dict_data values(21, '000000', 4, '鎺堟潈', '4', 'sys_oper_type', '', 'primary', 'N', 103, 1, now(), null, null, '鎺堟潈鎿嶄綔');
+insert into sys_dict_data values(22, '000000', 5, '瀵煎嚭', '5', 'sys_oper_type', '', 'warning', 'N', 103, 1, now(), null, null, '瀵煎嚭鎿嶄綔');
+insert into sys_dict_data values(23, '000000', 6, '瀵煎叆', '6', 'sys_oper_type', '', 'warning', 'N', 103, 1, now(), null, null, '瀵煎叆鎿嶄綔');
+insert into sys_dict_data values(24, '000000', 7, '寮洪��', '7', 'sys_oper_type', '', 'danger', 'N', 103, 1, now(), null, null, '寮洪��鎿嶄綔');
+insert into sys_dict_data values(25, '000000', 8, '鐢熸垚浠g爜', '8', 'sys_oper_type', '', 'warning', 'N', 103, 1, now(), null, null, '鐢熸垚鎿嶄綔');
+insert into sys_dict_data values(26, '000000', 9, '娓呯┖鏁版嵁', '9', 'sys_oper_type', '', 'danger', 'N', 103, 1, now(), null, null, '娓呯┖鎿嶄綔');
+insert into sys_dict_data values(27, '000000', 1, '鎴愬姛', '0', 'sys_common_status', '', 'primary', 'N', 103, 1, now(), null, null, '姝e父鐘舵��');
+insert into sys_dict_data values(28, '000000', 2, '澶辫触', '1', 'sys_common_status', '', 'danger', 'N', 103, 1, now(), null, null, '鍋滅敤鐘舵��');
+insert into sys_dict_data values(30, '000000', 0, '瀵嗙爜璁よ瘉', 'password', 'sys_grant_type', '', 'default', 'N', 103, 1, now(), null, null, '瀵嗙爜璁よ瘉');
+insert into sys_dict_data values(31, '000000', 0, '鐭俊璁よ瘉', 'sms', 'sys_grant_type', '', 'default', 'N', 103, 1, now(), null, null, '鐭俊璁よ瘉');
+insert into sys_dict_data values(32, '000000', 0, '閭欢璁よ瘉', 'email', 'sys_grant_type', '', 'default', 'N', 103, 1, now(), null, null, '閭欢璁よ瘉');
+insert into sys_dict_data values(33, '000000', 0, '灏忕▼搴忚璇�', 'xcx', 'sys_grant_type', '', 'default', 'N', 103, 1, now(), null, null, '灏忕▼搴忚璇�');
+insert into sys_dict_data values(34, '000000', 0, '涓夋柟鐧诲綍璁よ瘉', 'social', 'sys_grant_type', '', 'default', 'N', 103, 1, now(), null, null, '涓夋柟鐧诲綍璁よ瘉');
+insert into sys_dict_data values(35, '000000', 0, 'PC', 'pc', 'sys_device_type', '', 'default', 'N', 103, 1, now(), null, null, 'PC');
+insert into sys_dict_data values(36, '000000', 0, '瀹夊崜', 'android', 'sys_device_type', '', 'default', 'N', 103, 1, now(), null, null, '瀹夊崜');
+insert into sys_dict_data values(37, '000000', 0, 'iOS', 'ios', 'sys_device_type', '', 'default', 'N', 103, 1, now(), null, null, 'iOS');
+insert into sys_dict_data values(38, '000000', 0, '灏忕▼搴�', 'xcx', 'sys_device_type', '', 'default', 'N', 103, 1, now(), null, null, '灏忕▼搴�');
+
+
+-- ----------------------------
+-- 13銆佸弬鏁伴厤缃〃
+-- ----------------------------
+create table if not exists sys_config
+(
+ config_id int8,
+ tenant_id varchar(20) default '000000'::varchar,
+ config_name varchar(100) default ''::varchar,
+ config_key varchar(100) default ''::varchar,
+ config_value varchar(500) default ''::varchar,
+ config_type char default 'N'::bpchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ remark varchar(500) default null::varchar,
+ constraint sys_config_pk primary key (config_id)
+);
+
+comment on table sys_config is '鍙傛暟閰嶇疆琛�';
+comment on column sys_config.config_id is '鍙傛暟涓婚敭';
+comment on column sys_config.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_config.config_name is '鍙傛暟鍚嶇О';
+comment on column sys_config.config_key is '鍙傛暟閿悕';
+comment on column sys_config.config_value is '鍙傛暟閿��';
+comment on column sys_config.config_type is '绯荤粺鍐呯疆锛圷鏄� N鍚︼級';
+comment on column sys_config.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_config.create_by is '鍒涘缓鑰�';
+comment on column sys_config.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_config.update_by is '鏇存柊鑰�';
+comment on column sys_config.update_time is '鏇存柊鏃堕棿';
+comment on column sys_config.remark is '澶囨敞';
+
+insert into sys_config values(1, '000000', '涓绘鏋堕〉-榛樿鐨偆鏍峰紡鍚嶇О', 'sys.index.skinName', 'skin-blue', 'Y', 103, 1, now(), null, null, '钃濊壊 skin-blue銆佺豢鑹� skin-green銆佺传鑹� skin-purple銆佺孩鑹� skin-red銆侀粍鑹� skin-yellow' );
+insert into sys_config values(2, '000000', '鐢ㄦ埛绠$悊-璐﹀彿鍒濆瀵嗙爜', 'sys.user.initPassword', '123456', 'Y', 103, 1, now(), null, null, '鍒濆鍖栧瘑鐮� 123456' );
+insert into sys_config values(3, '000000', '涓绘鏋堕〉-渚ц竟鏍忎富棰�', 'sys.index.sideTheme', 'theme-dark', 'Y', 103, 1, now(), null, null, '娣辫壊涓婚theme-dark锛屾祬鑹蹭富棰榯heme-light' );
+insert into sys_config values(5, '000000', '璐﹀彿鑷姪-鏄惁寮�鍚敤鎴锋敞鍐屽姛鑳�', 'sys.account.registerUser', 'false', 'Y', 103, 1, now(), null, null, '鏄惁寮�鍚敞鍐岀敤鎴峰姛鑳斤紙true寮�鍚紝false鍏抽棴锛�');
+insert into sys_config values(11, '000000', 'OSS棰勮鍒楄〃璧勬簮寮�鍏�', 'sys.oss.previewListResource', 'true', 'Y', 103, 1, now(), null, null, 'true:寮�鍚�, false:鍏抽棴');
+
+
+-- ----------------------------
+-- 14銆佺郴缁熻闂褰�
+-- ----------------------------
+create table if not exists sys_logininfor
+(
+ info_id int8,
+ tenant_id varchar(20) default '000000'::varchar,
+ user_name varchar(50) default ''::varchar,
+ client_key varchar(32) default ''::varchar,
+ device_type varchar(32) default ''::varchar,
+ ipaddr varchar(128) default ''::varchar,
+ login_location varchar(255) default ''::varchar,
+ browser varchar(50) default ''::varchar,
+ os varchar(50) default ''::varchar,
+ status char default '0'::bpchar,
+ msg varchar(255) default ''::varchar,
+ login_time timestamp,
+ constraint sys_logininfor_pk primary key (info_id)
+);
+
+create index idx_sys_logininfor_s ON sys_logininfor (status);
+create index idx_sys_logininfor_lt ON sys_logininfor (login_time);
+
+comment on table sys_logininfor is '绯荤粺璁块棶璁板綍';
+comment on column sys_logininfor.info_id is '璁块棶ID';
+comment on column sys_logininfor.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_logininfor.user_name is '鐢ㄦ埛璐﹀彿';
+comment on column sys_logininfor.client_key is '瀹㈡埛绔�';
+comment on column sys_logininfor.device_type is '璁惧绫诲瀷';
+comment on column sys_logininfor.ipaddr is '鐧诲綍IP鍦板潃';
+comment on column sys_logininfor.login_location is '鐧诲綍鍦扮偣';
+comment on column sys_logininfor.browser is '娴忚鍣ㄧ被鍨�';
+comment on column sys_logininfor.os is '鎿嶄綔绯荤粺';
+comment on column sys_logininfor.status is '鐧诲綍鐘舵�侊紙0鎴愬姛 1澶辫触锛�';
+comment on column sys_logininfor.msg is '鎻愮ず娑堟伅';
+comment on column sys_logininfor.login_time is '璁块棶鏃堕棿';
+
+-- ----------------------------
+-- 17銆侀�氱煡鍏憡琛�
+-- ----------------------------
+create table if not exists sys_notice
+(
+ notice_id int8,
+ tenant_id varchar(20) default '000000'::varchar,
+ notice_title varchar(50) not null,
+ notice_type char not null,
+ notice_content text,
+ status char default '0'::bpchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ remark varchar(255) default null::varchar,
+ constraint sys_notice_pk primary key (notice_id)
+);
+
+comment on table sys_notice is '閫氱煡鍏憡琛�';
+comment on column sys_notice.notice_id is '鍏憡ID';
+comment on column sys_notice.tenant_id is '绉熸埛缂栧彿';
+comment on column sys_notice.notice_title is '鍏憡鏍囬';
+comment on column sys_notice.notice_type is '鍏憡绫诲瀷锛�1閫氱煡 2鍏憡锛�';
+comment on column sys_notice.notice_content is '鍏憡鍐呭';
+comment on column sys_notice.status is '鍏憡鐘舵�侊紙0姝e父 1鍏抽棴锛�';
+comment on column sys_notice.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_notice.create_by is '鍒涘缓鑰�';
+comment on column sys_notice.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_notice.update_by is '鏇存柊鑰�';
+comment on column sys_notice.update_time is '鏇存柊鏃堕棿';
+comment on column sys_notice.remark is '澶囨敞';
+
+-- ----------------------------
+-- 鍒濆鍖�-鍏憡淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+insert into sys_notice values('1', '000000', '娓╅Θ鎻愰啋锛�2018-07-01 鏂扮増鏈彂甯冨暒', '2', '鏂扮増鏈唴瀹�', '0', 103, 1, now(), null, null, '绠$悊鍛�');
+insert into sys_notice values('2', '000000', '缁存姢閫氱煡锛�2018-07-01 绯荤粺鍑屾櫒缁存姢', '1', '缁存姢鍐呭', '0', 103, 1, now(), null, null, '绠$悊鍛�');
+
+
+-- ----------------------------
+-- 18銆佷唬鐮佺敓鎴愪笟鍔¤〃
+-- ----------------------------
+create table if not exists gen_table
+(
+ table_id int8,
+ data_name varchar(200) default ''::varchar,
+ table_name varchar(200) default ''::varchar,
+ table_comment varchar(500) default ''::varchar,
+ sub_table_name varchar(64) default ''::varchar,
+ sub_table_fk_name varchar(64) default ''::varchar,
+ class_name varchar(100) default ''::varchar,
+ tpl_category varchar(200) default 'crud'::varchar,
+ package_name varchar(100) default null::varchar,
+ module_name varchar(30) default null::varchar,
+ business_name varchar(30) default null::varchar,
+ function_name varchar(50) default null::varchar,
+ function_author varchar(50) default null::varchar,
+ gen_type char default '0'::bpchar not null,
+ gen_path varchar(200) default '/'::varchar,
+ options varchar(1000) default null::varchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ remark varchar(500) default null::varchar,
+ constraint gen_table_pk primary key (table_id)
+);
+
+comment on table gen_table is '浠g爜鐢熸垚涓氬姟琛�';
+comment on column gen_table.table_id is '缂栧彿';
+comment on column gen_table.data_name is '鏁版嵁婧愬悕绉�';
+comment on column gen_table.table_name is '琛ㄥ悕绉�';
+comment on column gen_table.table_comment is '琛ㄦ弿杩�';
+comment on column gen_table.sub_table_name is '鍏宠仈瀛愯〃鐨勮〃鍚�';
+comment on column gen_table.sub_table_fk_name is '瀛愯〃鍏宠仈鐨勫閿悕';
+comment on column gen_table.class_name is '瀹炰綋绫诲悕绉�';
+comment on column gen_table.tpl_category is '浣跨敤鐨勬ā鏉匡紙CRUD鍗曡〃鎿嶄綔 TREE鏍戣〃鎿嶄綔锛�';
+comment on column gen_table.package_name is '鐢熸垚鍖呰矾寰�';
+comment on column gen_table.module_name is '鐢熸垚妯″潡鍚�';
+comment on column gen_table.business_name is '鐢熸垚涓氬姟鍚�';
+comment on column gen_table.function_name is '鐢熸垚鍔熻兘鍚�';
+comment on column gen_table.function_author is '鐢熸垚鍔熻兘浣滆��';
+comment on column gen_table.gen_type is '鐢熸垚浠g爜鏂瑰紡锛�0zip鍘嬬缉鍖� 1鑷畾涔夎矾寰勶級';
+comment on column gen_table.gen_path is '鐢熸垚璺緞锛堜笉濉粯璁ら」鐩矾寰勶級';
+comment on column gen_table.options is '鍏跺畠鐢熸垚閫夐」';
+comment on column gen_table.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column gen_table.create_by is '鍒涘缓鑰�';
+comment on column gen_table.create_time is '鍒涘缓鏃堕棿';
+comment on column gen_table.update_by is '鏇存柊鑰�';
+comment on column gen_table.update_time is '鏇存柊鏃堕棿';
+comment on column gen_table.remark is '澶囨敞';
+
+-- ----------------------------
+-- 19銆佷唬鐮佺敓鎴愪笟鍔¤〃瀛楁
+-- ----------------------------
+create table if not exists gen_table_column
+(
+ column_id int8,
+ table_id int8,
+ column_name varchar(200) default null::varchar,
+ column_comment varchar(500) default null::varchar,
+ column_type varchar(100) default null::varchar,
+ java_type varchar(500) default null::varchar,
+ java_field varchar(200) default null::varchar,
+ is_pk char default null::bpchar,
+ is_increment char default null::bpchar,
+ is_required char default null::bpchar,
+ is_insert char default null::bpchar,
+ is_edit char default null::bpchar,
+ is_list char default null::bpchar,
+ is_query char default null::bpchar,
+ query_type varchar(200) default 'EQ'::varchar,
+ html_type varchar(200) default null::varchar,
+ dict_type varchar(200) default ''::varchar,
+ sort int4,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ constraint gen_table_column_pk primary key (column_id)
+);
+
+comment on table gen_table_column is '浠g爜鐢熸垚涓氬姟琛ㄥ瓧娈�';
+comment on column gen_table_column.column_id is '缂栧彿';
+comment on column gen_table_column.table_id is '褰掑睘琛ㄧ紪鍙�';
+comment on column gen_table_column.column_name is '鍒楀悕绉�';
+comment on column gen_table_column.column_comment is '鍒楁弿杩�';
+comment on column gen_table_column.column_type is '鍒楃被鍨�';
+comment on column gen_table_column.java_type is 'JAVA绫诲瀷';
+comment on column gen_table_column.java_field is 'JAVA瀛楁鍚�';
+comment on column gen_table_column.is_pk is '鏄惁涓婚敭锛�1鏄級';
+comment on column gen_table_column.is_increment is '鏄惁鑷锛�1鏄級';
+comment on column gen_table_column.is_required is '鏄惁蹇呭~锛�1鏄級';
+comment on column gen_table_column.is_insert is '鏄惁涓烘彃鍏ュ瓧娈碉紙1鏄級';
+comment on column gen_table_column.is_edit is '鏄惁缂栬緫瀛楁锛�1鏄級';
+comment on column gen_table_column.is_list is '鏄惁鍒楄〃瀛楁锛�1鏄級';
+comment on column gen_table_column.is_query is '鏄惁鏌ヨ瀛楁锛�1鏄級';
+comment on column gen_table_column.query_type is '鏌ヨ鏂瑰紡锛堢瓑浜庛�佷笉绛変簬銆佸ぇ浜庛�佸皬浜庛�佽寖鍥达級';
+comment on column gen_table_column.html_type is '鏄剧ず绫诲瀷锛堟枃鏈銆佹枃鏈煙銆佷笅鎷夋銆佸閫夋銆佸崟閫夋銆佹棩鏈熸帶浠讹級';
+comment on column gen_table_column.dict_type is '瀛楀吀绫诲瀷';
+comment on column gen_table_column.sort is '鎺掑簭';
+comment on column gen_table_column.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column gen_table_column.create_by is '鍒涘缓鑰�';
+comment on column gen_table_column.create_time is '鍒涘缓鏃堕棿';
+comment on column gen_table_column.update_by is '鏇存柊鑰�';
+comment on column gen_table_column.update_time is '鏇存柊鏃堕棿';
+
+-- ----------------------------
+-- OSS瀵硅薄瀛樺偍琛�
+-- ----------------------------
+create table if not exists sys_oss
+(
+ oss_id int8,
+ tenant_id varchar(20) default '000000'::varchar,
+ file_name varchar(255) default ''::varchar not null,
+ original_name varchar(255) default ''::varchar not null,
+ file_suffix varchar(10) default ''::varchar not null,
+ url varchar(500) default ''::varchar not null,
+ ext1 varchar(500) default ''::varchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ service varchar(20) default 'minio'::varchar,
+ constraint sys_oss_pk primary key (oss_id)
+);
+
+comment on table sys_oss is 'OSS瀵硅薄瀛樺偍琛�';
+comment on column sys_oss.oss_id is '瀵硅薄瀛樺偍涓婚敭';
+comment on column sys_oss.tenant_id is '绉熸埛缂栫爜';
+comment on column sys_oss.file_name is '鏂囦欢鍚�';
+comment on column sys_oss.original_name is '鍘熷悕';
+comment on column sys_oss.file_suffix is '鏂囦欢鍚庣紑鍚�';
+comment on column sys_oss.url is 'URL鍦板潃';
+comment on column sys_oss.ext1 is '鎵╁睍瀛楁';
+comment on column sys_oss.create_by is '涓婁紶浜�';
+comment on column sys_oss.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_oss.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_oss.update_by is '鏇存柊鑰�';
+comment on column sys_oss.update_time is '鏇存柊鏃堕棿';
+comment on column sys_oss.service is '鏈嶅姟鍟�';
+
+-- ----------------------------
+-- OSS瀵硅薄瀛樺偍鍔ㄦ�侀厤缃〃
+-- ----------------------------
+create table if not exists sys_oss_config
+(
+ oss_config_id int8,
+ tenant_id varchar(20) default '000000'::varchar,
+ config_key varchar(20) default ''::varchar not null,
+ access_key varchar(255) default ''::varchar,
+ secret_key varchar(255) default ''::varchar,
+ bucket_name varchar(255) default ''::varchar,
+ prefix varchar(255) default ''::varchar,
+ endpoint varchar(255) default ''::varchar,
+ domain varchar(255) default ''::varchar,
+ is_https char default 'N'::bpchar,
+ region varchar(255) default ''::varchar,
+ access_policy char(1) default '1'::bpchar not null,
+ status char default '1'::bpchar,
+ ext1 varchar(255) default ''::varchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ remark varchar(500) default ''::varchar,
+ constraint sys_oss_config_pk primary key (oss_config_id)
+);
+
+comment on table sys_oss_config is '瀵硅薄瀛樺偍閰嶇疆琛�';
+comment on column sys_oss_config.oss_config_id is '涓婚敭';
+comment on column sys_oss_config.tenant_id is '绉熸埛缂栫爜';
+comment on column sys_oss_config.config_key is '閰嶇疆key';
+comment on column sys_oss_config.access_key is 'accessKey';
+comment on column sys_oss_config.secret_key is '绉橀挜';
+comment on column sys_oss_config.bucket_name is '妗跺悕绉�';
+comment on column sys_oss_config.prefix is '鍓嶇紑';
+comment on column sys_oss_config.endpoint is '璁块棶绔欑偣';
+comment on column sys_oss_config.domain is '鑷畾涔夊煙鍚�';
+comment on column sys_oss_config.is_https is '鏄惁https锛圷=鏄�,N=鍚︼級';
+comment on column sys_oss_config.region is '鍩�';
+comment on column sys_oss_config.access_policy is '妗舵潈闄愮被鍨�(0=private 1=public 2=custom)';
+comment on column sys_oss_config.status is '鏄惁榛樿锛�0=鏄�,1=鍚︼級';
+comment on column sys_oss_config.ext1 is '鎵╁睍瀛楁';
+comment on column sys_oss_config.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_oss_config.create_by is '鍒涘缓鑰�';
+comment on column sys_oss_config.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_oss_config.update_by is '鏇存柊鑰�';
+comment on column sys_oss_config.update_time is '鏇存柊鏃堕棿';
+comment on column sys_oss_config.remark is '澶囨敞';
+
+insert into sys_oss_config values (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '','N', '', '1', '0', '', 103, 1, now(), 1, now(), null);
+insert into sys_oss_config values (2, '000000', 'qiniu', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 's3-cn-north-1.qiniucs.com', '','N', '', '1', '1', '', 103, 1, now(), 1, now(), null);
+insert into sys_oss_config values (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 'oss-cn-beijing.aliyuncs.com', '','N', '', '1', '1', '', 103, 1, now(), 1, now(), null);
+insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi-1240000000', '', 'cos.ap-beijing.myqcloud.com', '','N', 'ap-beijing', '1', '1', '', 103, 1, now(), 1, now(), null);
+insert into sys_oss_config values (5, '000000', 'image', 'ruoyi', 'ruoyi123', 'ruoyi', 'image', '127.0.0.1:9000', '','N', '', '1', '1', '', 103, 1, now(), 1, now(), NULL);
+
+-- ----------------------------
+-- 绯荤粺鎺堟潈琛�
+-- ----------------------------
+create table sys_client (
+ id int8,
+ client_id varchar(64) default ''::varchar,
+ client_key varchar(32) default ''::varchar,
+ client_secret varchar(255) default ''::varchar,
+ grant_type varchar(255) default ''::varchar,
+ device_type varchar(32) default ''::varchar,
+ active_timeout int4 default 1800,
+ timeout int4 default 604800,
+ status char(1) default '0'::bpchar,
+ del_flag char(1) default '0'::bpchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ constraint sys_client_pk primary key (id)
+);
+
+comment on table sys_client is '绯荤粺鎺堟潈琛�';
+comment on column sys_client.id is '涓婚敭';
+comment on column sys_client.client_id is '瀹㈡埛绔痠d';
+comment on column sys_client.client_key is '瀹㈡埛绔痥ey';
+comment on column sys_client.client_secret is '瀹㈡埛绔閽�';
+comment on column sys_client.grant_type is '鎺堟潈绫诲瀷';
+comment on column sys_client.device_type is '璁惧绫诲瀷';
+comment on column sys_client.active_timeout is 'token娲昏穬瓒呮椂鏃堕棿';
+comment on column sys_client.timeout is 'token鍥哄畾瓒呮椂';
+comment on column sys_client.status is '鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_client.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+comment on column sys_client.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_client.create_by is '鍒涘缓鑰�';
+comment on column sys_client.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_client.update_by is '鏇存柊鑰�';
+comment on column sys_client.update_time is '鏇存柊鏃堕棿';
+
+insert into sys_client values (1, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 103, 1, now(), 1, now());
+insert into sys_client values (2, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'android', 1800, 604800, 0, 0, 103, 1, now(), 1, now());
+
+create table if not exists test_demo
+(
+ id int8,
+ tenant_id varchar(20) default '000000',
+ dept_id int8,
+ user_id int8,
+ order_num int4 default 0,
+ test_key varchar(255),
+ value varchar(255),
+ version int4 default 0,
+ create_dept int8,
+ create_time timestamp,
+ create_by int8,
+ update_time timestamp,
+ update_by int8,
+ del_flag int4 default 0
+);
+
+comment on table test_demo is '娴嬭瘯鍗曡〃';
+comment on column test_demo.id is '涓婚敭';
+comment on column test_demo.tenant_id is '绉熸埛缂栧彿';
+comment on column test_demo.dept_id is '閮ㄩ棬id';
+comment on column test_demo.user_id is '鐢ㄦ埛id';
+comment on column test_demo.order_num is '鎺掑簭鍙�';
+comment on column test_demo.test_key is 'key閿�';
+comment on column test_demo.value is '鍊�';
+comment on column test_demo.version is '鐗堟湰';
+comment on column test_demo.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column test_demo.create_time is '鍒涘缓鏃堕棿';
+comment on column test_demo.create_by is '鍒涘缓浜�';
+comment on column test_demo.update_time is '鏇存柊鏃堕棿';
+comment on column test_demo.update_by is '鏇存柊浜�';
+comment on column test_demo.del_flag is '鍒犻櫎鏍囧織';
+
+create table if not exists test_tree
+(
+ id int8,
+ tenant_id varchar(20) default '000000',
+ parent_id int8 default 0,
+ dept_id int8,
+ user_id int8,
+ tree_name varchar(255),
+ version int4 default 0,
+ create_dept int8,
+ create_time timestamp,
+ create_by int8,
+ update_time timestamp,
+ update_by int8,
+ del_flag integer default 0
+);
+
+comment on table test_tree is '娴嬭瘯鏍戣〃';
+comment on column test_tree.id is '涓婚敭';
+comment on column test_tree.tenant_id is '绉熸埛缂栧彿';
+comment on column test_tree.parent_id is '鐖秈d';
+comment on column test_tree.dept_id is '閮ㄩ棬id';
+comment on column test_tree.user_id is '鐢ㄦ埛id';
+comment on column test_tree.tree_name is '鍊�';
+comment on column test_tree.version is '鐗堟湰';
+comment on column test_tree.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column test_tree.create_time is '鍒涘缓鏃堕棿';
+comment on column test_tree.create_by is '鍒涘缓浜�';
+comment on column test_tree.update_time is '鏇存柊鏃堕棿';
+comment on column test_tree.update_by is '鏇存柊浜�';
+comment on column test_tree.del_flag is '鍒犻櫎鏍囧織';
+
+INSERT INTO test_demo VALUES (1, '000000', 102, 4, 1, '娴嬭瘯鏁版嵁鏉冮檺', '娴嬭瘯', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (2, '000000', 102, 3, 2, '瀛愯妭鐐�1', '111', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (3, '000000', 102, 3, 3, '瀛愯妭鐐�2', '222', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (4, '000000', 108, 4, 4, '娴嬭瘯鏁版嵁', 'demo', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (5, '000000', 108, 3, 13, '瀛愯妭鐐�11', '1111', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (6, '000000', 108, 3, 12, '瀛愯妭鐐�22', '2222', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (7, '000000', 108, 3, 11, '瀛愯妭鐐�33', '3333', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (8, '000000', 108, 3, 10, '瀛愯妭鐐�44', '4444', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (9, '000000', 108, 3, 9, '瀛愯妭鐐�55', '5555', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (10, '000000', 108, 3, 8, '瀛愯妭鐐�66', '6666', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (11, '000000', 108, 3, 7, '瀛愯妭鐐�77', '7777', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (12, '000000', 108, 3, 6, '瀛愯妭鐐�88', '8888', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (13, '000000', 108, 3, 5, '瀛愯妭鐐�99', '9999', 0, 103, now(), 1, NULL, NULL, 0);
+
+INSERT INTO test_tree VALUES (1, '000000', 0, 102, 4, '娴嬭瘯鏁版嵁鏉冮檺', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (2, '000000', 1, 102, 3, '瀛愯妭鐐�1', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (3, '000000', 2, 102, 3, '瀛愯妭鐐�2', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (4, '000000', 0, 108, 4, '娴嬭瘯鏍�1', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (5, '000000', 4, 108, 3, '瀛愯妭鐐�11', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (6, '000000', 4, 108, 3, '瀛愯妭鐐�22', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (7, '000000', 4, 108, 3, '瀛愯妭鐐�33', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (8, '000000', 5, 108, 3, '瀛愯妭鐐�44', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (9, '000000', 6, 108, 3, '瀛愯妭鐐�55', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (10, '000000', 7, 108, 3, '瀛愯妭鐐�66', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (11, '000000', 7, 108, 3, '瀛愯妭鐐�77', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (12, '000000', 10, 108, 3, '瀛愯妭鐐�88', 0, 103, now(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (13, '000000', 10, 108, 3, '瀛愯妭鐐�99', 0, 103, now(), 1, NULL, NULL, 0);
+
+-- 瀛楃涓茶嚜鍔ㄨ浆鏃堕棿 閬垮厤妗嗘灦鏃堕棿鏌ヨ鎶ラ敊闂
+create or replace function cast_varchar_to_timestamp(varchar) returns timestamptz as $$
+select to_timestamp($1, 'yyyy-mm-dd hh24:mi:ss');
+$$ language sql strict ;
+
+create cast (varchar as timestamptz) with function cast_varchar_to_timestamp as IMPLICIT;
diff --git a/RuoYi-Vue-Plus/script/sql/postgres/postgres_ry_workflow.sql b/RuoYi-Vue-Plus/script/sql/postgres/postgres_ry_workflow.sql
new file mode 100755
index 0000000..d6f30df
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/postgres/postgres_ry_workflow.sql
@@ -0,0 +1,507 @@
+CREATE TABLE flow_definition
+(
+ id int8 NOT NULL,
+ flow_code varchar(40) NOT NULL,
+ flow_name varchar(100) NOT NULL,
+ model_value varchar(40) NOT NULL DEFAULT 'CLASSICS',
+ category varchar(100) NULL,
+ "version" varchar(20) NOT NULL,
+ is_publish int2 NOT NULL DEFAULT 0,
+ form_custom bpchar(1) NULL DEFAULT 'N':: character varying,
+ form_path varchar(100) NULL,
+ activity_status int2 NOT NULL DEFAULT 1,
+ listener_type varchar(100) NULL,
+ listener_path varchar(400) NULL,
+ ext varchar(500) NULL,
+ create_time timestamp NULL,
+ create_by varchar(64) NULL DEFAULT '':: character varying,
+ update_time timestamp NULL,
+ update_by varchar(64) NULL DEFAULT '':: character varying,
+ del_flag bpchar(1) NULL DEFAULT '0':: character varying,
+ tenant_id varchar(40) NULL,
+ CONSTRAINT flow_definition_pkey PRIMARY KEY (id)
+);
+COMMENT ON TABLE flow_definition IS '娴佺▼瀹氫箟琛�';
+
+COMMENT ON COLUMN flow_definition.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_definition.flow_code IS '娴佺▼缂栫爜';
+COMMENT ON COLUMN flow_definition.flow_name IS '娴佺▼鍚嶇О';
+COMMENT ON COLUMN flow_definition.model_value IS '璁捐鍣ㄦā鍨嬶紙CLASSICS缁忓吀妯″瀷 MIMIC浠块拤閽夋ā鍨嬶級';
+COMMENT ON COLUMN flow_definition.category IS '娴佺▼绫诲埆';
+COMMENT ON COLUMN flow_definition."version" IS '娴佺▼鐗堟湰';
+COMMENT ON COLUMN flow_definition.is_publish IS '鏄惁鍙戝竷锛�0鏈彂甯� 1宸插彂甯� 9澶辨晥锛�';
+COMMENT ON COLUMN flow_definition.form_custom IS '瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級';
+COMMENT ON COLUMN flow_definition.form_path IS '瀹℃壒琛ㄥ崟璺緞';
+COMMENT ON COLUMN flow_definition.activity_status IS '娴佺▼婵�娲荤姸鎬侊紙0鎸傝捣 1婵�娲伙級';
+COMMENT ON COLUMN flow_definition.listener_type IS '鐩戝惉鍣ㄧ被鍨�';
+COMMENT ON COLUMN flow_definition.listener_path IS '鐩戝惉鍣ㄨ矾寰�';
+COMMENT ON COLUMN flow_definition.ext IS '鎵╁睍瀛楁锛岄鐣欑粰涓氬姟绯荤粺浣跨敤';
+COMMENT ON COLUMN flow_definition.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_definition.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_definition.update_time IS '鏇存柊鏃堕棿';
+COMMENT ON COLUMN flow_definition.update_by IS '鏇存柊浜�';
+COMMENT ON COLUMN flow_definition.del_flag IS '鍒犻櫎鏍囧織';
+COMMENT ON COLUMN flow_definition.tenant_id IS '绉熸埛id';
+
+CREATE TABLE flow_node
+(
+ id int8 NOT NULL,
+ node_type int2 NOT NULL,
+ definition_id int8 NOT NULL,
+ node_code varchar(100) NOT NULL,
+ node_name varchar(100) NULL,
+ permission_flag varchar(200) NULL,
+ node_ratio varchar(200) NULL,
+ coordinate varchar(100) NULL,
+ any_node_skip varchar(100) NULL,
+ listener_type varchar(100) NULL,
+ listener_path varchar(400) NULL,
+ form_custom bpchar(1) NULL DEFAULT 'N':: character varying,
+ form_path varchar(100) NULL,
+ "version" varchar(20) NOT NULL,
+ create_time timestamp NULL,
+ create_by varchar(64) NULL DEFAULT '':: character varying,
+ update_time timestamp NULL,
+ update_by varchar(64) NULL DEFAULT '':: character varying,
+ ext text NULL,
+ del_flag bpchar(1) NULL DEFAULT '0':: character varying,
+ tenant_id varchar(40) NULL,
+ CONSTRAINT flow_node_pkey PRIMARY KEY (id)
+);
+COMMENT ON TABLE flow_node IS '娴佺▼鑺傜偣琛�';
+
+COMMENT ON COLUMN flow_node.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_node.node_type IS '鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�';
+COMMENT ON COLUMN flow_node.definition_id IS '娴佺▼瀹氫箟id';
+COMMENT ON COLUMN flow_node.node_code IS '娴佺▼鑺傜偣缂栫爜';
+COMMENT ON COLUMN flow_node.node_name IS '娴佺▼鑺傜偣鍚嶇О';
+COMMENT ON COLUMN flow_node.permission_flag IS '鏉冮檺鏍囪瘑锛堟潈闄愮被鍨�:鏉冮檺鏍囪瘑锛屽彲浠ュ涓紝鐢ˊ@闅斿紑)';
+COMMENT ON COLUMN flow_node.node_ratio IS '娴佺▼绛剧讲姣斾緥鍊�';
+COMMENT ON COLUMN flow_node.coordinate IS '鍧愭爣';
+COMMENT ON COLUMN flow_node.any_node_skip IS '浠绘剰缁撶偣璺宠浆';
+COMMENT ON COLUMN flow_node.listener_type IS '鐩戝惉鍣ㄧ被鍨�';
+COMMENT ON COLUMN flow_node.listener_path IS '鐩戝惉鍣ㄨ矾寰�';
+COMMENT ON COLUMN flow_node.form_custom IS '瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級';
+COMMENT ON COLUMN flow_node.form_path IS '瀹℃壒琛ㄥ崟璺緞';
+COMMENT ON COLUMN flow_node."version" IS '鐗堟湰';
+COMMENT ON COLUMN flow_node.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_node.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_node.update_time IS '鏇存柊鏃堕棿';
+COMMENT ON COLUMN flow_node.update_by IS '鏇存柊浜�';
+COMMENT ON COLUMN flow_node.ext IS '鑺傜偣鎵╁睍灞炴��';
+COMMENT ON COLUMN flow_node.del_flag IS '鍒犻櫎鏍囧織';
+COMMENT ON COLUMN flow_node.tenant_id IS '绉熸埛id';
+
+
+CREATE TABLE flow_skip
+(
+ id int8 NOT NULL,
+ definition_id int8 NOT NULL,
+ now_node_code varchar(100) NOT NULL,
+ now_node_type int2 NULL,
+ next_node_code varchar(100) NOT NULL,
+ next_node_type int2 NULL,
+ skip_name varchar(100) NULL,
+ skip_type varchar(40) NULL,
+ skip_condition varchar(200) NULL,
+ coordinate varchar(100) NULL,
+ create_time timestamp NULL,
+ create_by varchar(64) NULL DEFAULT '':: character varying,
+ update_time timestamp NULL,
+ update_by varchar(64) NULL DEFAULT '':: character varying,
+ del_flag bpchar(1) NULL DEFAULT '0':: character varying,
+ tenant_id varchar(40) NULL,
+ CONSTRAINT flow_skip_pkey PRIMARY KEY (id)
+);
+COMMENT ON TABLE flow_skip IS '鑺傜偣璺宠浆鍏宠仈琛�';
+
+COMMENT ON COLUMN flow_skip.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_skip.definition_id IS '娴佺▼瀹氫箟id';
+COMMENT ON COLUMN flow_skip.now_node_code IS '褰撳墠娴佺▼鑺傜偣鐨勭紪鐮�';
+COMMENT ON COLUMN flow_skip.now_node_type IS '褰撳墠鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�';
+COMMENT ON COLUMN flow_skip.next_node_code IS '涓嬩竴涓祦绋嬭妭鐐圭殑缂栫爜';
+COMMENT ON COLUMN flow_skip.next_node_type IS '涓嬩竴涓妭鐐圭被鍨嬶紙0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�';
+COMMENT ON COLUMN flow_skip.skip_name IS '璺宠浆鍚嶇О';
+COMMENT ON COLUMN flow_skip.skip_type IS '璺宠浆绫诲瀷锛圥ASS瀹℃壒閫氳繃 REJECT閫�鍥烇級';
+COMMENT ON COLUMN flow_skip.skip_condition IS '璺宠浆鏉′欢';
+COMMENT ON COLUMN flow_skip.coordinate IS '鍧愭爣';
+COMMENT ON COLUMN flow_skip.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_skip.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_skip.update_time IS '鏇存柊鏃堕棿';
+COMMENT ON COLUMN flow_skip.update_by IS '鏇存柊浜�';
+COMMENT ON COLUMN flow_skip.del_flag IS '鍒犻櫎鏍囧織';
+COMMENT ON COLUMN flow_skip.tenant_id IS '绉熸埛id';
+
+CREATE TABLE flow_instance
+(
+ id int8 NOT NULL,
+ definition_id int8 NOT NULL,
+ business_id varchar(40) NOT NULL,
+ node_type int2 NOT NULL,
+ node_code varchar(40) NOT NULL,
+ node_name varchar(100) NULL,
+ variable text NULL,
+ flow_status varchar(20) NOT NULL,
+ activity_status int2 NOT NULL DEFAULT 1,
+ def_json text NULL,
+ create_time timestamp NULL,
+ create_by varchar(64) NULL DEFAULT '':: character varying,
+ update_time timestamp NULL,
+ update_by varchar(64) NULL DEFAULT '':: character varying,
+ ext varchar(500) NULL,
+ del_flag bpchar(1) NULL DEFAULT '0':: character varying,
+ tenant_id varchar(40) NULL,
+ CONSTRAINT flow_instance_pkey PRIMARY KEY (id)
+);
+COMMENT ON TABLE flow_instance IS '娴佺▼瀹炰緥琛�';
+
+COMMENT ON COLUMN flow_instance.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_instance.definition_id IS '瀵瑰簲flow_definition琛ㄧ殑id';
+COMMENT ON COLUMN flow_instance.business_id IS '涓氬姟id';
+COMMENT ON COLUMN flow_instance.node_type IS '鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�';
+COMMENT ON COLUMN flow_instance.node_code IS '娴佺▼鑺傜偣缂栫爜';
+COMMENT ON COLUMN flow_instance.node_name IS '娴佺▼鑺傜偣鍚嶇О';
+COMMENT ON COLUMN flow_instance.variable IS '浠诲姟鍙橀噺';
+COMMENT ON COLUMN flow_instance.flow_status IS '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�';
+COMMENT ON COLUMN flow_instance.activity_status IS '娴佺▼婵�娲荤姸鎬侊紙0鎸傝捣 1婵�娲伙級';
+COMMENT ON COLUMN flow_instance.def_json IS '娴佺▼瀹氫箟json';
+COMMENT ON COLUMN flow_instance.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_instance.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_instance.update_time IS '鏇存柊鏃堕棿';
+COMMENT ON COLUMN flow_instance.update_by IS '鏇存柊浜�';
+COMMENT ON COLUMN flow_instance.ext IS '鎵╁睍瀛楁锛岄鐣欑粰涓氬姟绯荤粺浣跨敤';
+COMMENT ON COLUMN flow_instance.del_flag IS '鍒犻櫎鏍囧織';
+COMMENT ON COLUMN flow_instance.tenant_id IS '绉熸埛id';
+
+CREATE TABLE flow_task
+(
+ id int8 NOT NULL,
+ definition_id int8 NOT NULL,
+ instance_id int8 NOT NULL,
+ node_code varchar(100) NOT NULL,
+ node_name varchar(100) NULL,
+ node_type int2 NOT NULL,
+ flow_status varchar(20) NOT NULL,
+ form_custom bpchar(1) NULL DEFAULT 'N':: character varying,
+ form_path varchar(100) NULL,
+ create_time timestamp NULL,
+ create_by varchar(64) NULL DEFAULT '':: character varying,
+ update_time timestamp NULL,
+ update_by varchar(64) NULL DEFAULT '':: character varying,
+ del_flag bpchar(1) NULL DEFAULT '0':: character varying,
+ tenant_id varchar(40) NULL,
+ CONSTRAINT flow_task_pkey PRIMARY KEY (id)
+);
+COMMENT ON TABLE flow_task IS '寰呭姙浠诲姟琛�';
+
+COMMENT ON COLUMN flow_task.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_task.definition_id IS '瀵瑰簲flow_definition琛ㄧ殑id';
+COMMENT ON COLUMN flow_task.instance_id IS '瀵瑰簲flow_instance琛ㄧ殑id';
+COMMENT ON COLUMN flow_task.node_code IS '鑺傜偣缂栫爜';
+COMMENT ON COLUMN flow_task.node_name IS '鑺傜偣鍚嶇О';
+COMMENT ON COLUMN flow_task.node_type IS '鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�';
+COMMENT ON COLUMN flow_task.flow_status IS '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�';
+COMMENT ON COLUMN flow_task.form_custom IS '瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級';
+COMMENT ON COLUMN flow_task.form_path IS '瀹℃壒琛ㄥ崟璺緞';
+COMMENT ON COLUMN flow_task.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_task.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_task.update_time IS '鏇存柊鏃堕棿';
+COMMENT ON COLUMN flow_task.update_by IS '鏇存柊浜�';
+COMMENT ON COLUMN flow_task.del_flag IS '鍒犻櫎鏍囧織';
+COMMENT ON COLUMN flow_task.tenant_id IS '绉熸埛id';
+
+CREATE TABLE flow_his_task
+(
+ id int8 NOT NULL,
+ definition_id int8 NOT NULL,
+ instance_id int8 NOT NULL,
+ task_id int8 NOT NULL,
+ node_code varchar(100) NULL,
+ node_name varchar(100) NULL,
+ node_type int2 NULL,
+ target_node_code varchar(200) NULL,
+ target_node_name varchar(200) NULL,
+ approver varchar(40) NULL,
+ cooperate_type int2 NOT NULL DEFAULT 0,
+ collaborator varchar(500) NULL,
+ skip_type varchar(10) NULL,
+ flow_status varchar(20) NOT NULL,
+ form_custom bpchar(1) NULL DEFAULT 'N':: character varying,
+ form_path varchar(100) NULL,
+ ext text NULL,
+ message varchar(500) NULL,
+ variable text NULL,
+ create_time timestamp NULL,
+ update_time timestamp NULL,
+ del_flag bpchar(1) NULL DEFAULT '0':: character varying,
+ tenant_id varchar(40) NULL,
+ CONSTRAINT flow_his_task_pkey PRIMARY KEY (id)
+);
+COMMENT ON TABLE flow_his_task IS '鍘嗗彶浠诲姟璁板綍琛�';
+
+COMMENT ON COLUMN flow_his_task.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_his_task.definition_id IS '瀵瑰簲flow_definition琛ㄧ殑id';
+COMMENT ON COLUMN flow_his_task.instance_id IS '瀵瑰簲flow_instance琛ㄧ殑id';
+COMMENT ON COLUMN flow_his_task.task_id IS '瀵瑰簲flow_task琛ㄧ殑id';
+COMMENT ON COLUMN flow_his_task.node_code IS '寮�濮嬭妭鐐圭紪鐮�';
+COMMENT ON COLUMN flow_his_task.node_name IS '寮�濮嬭妭鐐瑰悕绉�';
+COMMENT ON COLUMN flow_his_task.node_type IS '寮�濮嬭妭鐐圭被鍨嬶紙0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�';
+COMMENT ON COLUMN flow_his_task.target_node_code IS '鐩爣鑺傜偣缂栫爜';
+COMMENT ON COLUMN flow_his_task.target_node_name IS '缁撴潫鑺傜偣鍚嶇О';
+COMMENT ON COLUMN flow_his_task.approver IS '瀹℃壒鑰�';
+COMMENT ON COLUMN flow_his_task.cooperate_type IS '鍗忎綔鏂瑰紡(1瀹℃壒 2杞姙 3濮旀淳 4浼氱 5绁ㄧ 6鍔犵 7鍑忕)';
+COMMENT ON COLUMN flow_his_task.collaborator IS '鍗忎綔浜�';
+COMMENT ON COLUMN flow_his_task.skip_type IS '娴佽浆绫诲瀷锛圥ASS閫氳繃 REJECT閫�鍥� NONE鏃犲姩浣滐級';
+COMMENT ON COLUMN flow_his_task.flow_status IS '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�';
+COMMENT ON COLUMN flow_his_task.form_custom IS '瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級';
+COMMENT ON COLUMN flow_his_task.form_path IS '瀹℃壒琛ㄥ崟璺緞';
+COMMENT ON COLUMN flow_his_task.message IS '瀹℃壒鎰忚';
+COMMENT ON COLUMN flow_his_task.variable IS '浠诲姟鍙橀噺';
+COMMENT ON COLUMN flow_his_task.ext IS '鎵╁睍瀛楁锛岄鐣欑粰涓氬姟绯荤粺浣跨敤';
+COMMENT ON COLUMN flow_his_task.create_time IS '浠诲姟寮�濮嬫椂闂�';
+COMMENT ON COLUMN flow_his_task.update_time IS '瀹℃壒瀹屾垚鏃堕棿';
+COMMENT ON COLUMN flow_his_task.del_flag IS '鍒犻櫎鏍囧織';
+COMMENT ON COLUMN flow_his_task.tenant_id IS '绉熸埛id';
+
+CREATE TABLE flow_user
+(
+ id int8 NOT NULL,
+ "type" bpchar(1) NOT NULL,
+ processed_by varchar(80) NULL,
+ associated int8 NOT NULL,
+ create_time timestamp NULL,
+ create_by varchar(64) NULL DEFAULT '':: character varying,
+ update_time timestamp NULL,
+ update_by varchar(64) NULL DEFAULT '':: character varying,
+ del_flag bpchar(1) NULL DEFAULT '0':: character varying,
+ tenant_id varchar(40) NULL,
+ CONSTRAINT flow_user_pk PRIMARY KEY (id)
+);
+CREATE INDEX user_processed_type ON flow_user USING btree (processed_by, type);
+CREATE INDEX user_associated_idx ON FLOW_USER USING btree (associated);
+COMMENT ON TABLE flow_user IS '娴佺▼鐢ㄦ埛琛�';
+
+COMMENT ON COLUMN flow_user.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_user."type" IS '浜哄憳绫诲瀷锛�1寰呭姙浠诲姟鐨勫鎵逛汉鏉冮檺 2寰呭姙浠诲姟鐨勮浆鍔炰汉鏉冮檺 3寰呭姙浠诲姟鐨勫鎵樹汉鏉冮檺锛�';
+COMMENT ON COLUMN flow_user.processed_by IS '鏉冮檺浜�';
+COMMENT ON COLUMN flow_user.associated IS '浠诲姟琛╥d';
+COMMENT ON COLUMN flow_user.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_user.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_user.update_time IS '鏇存柊鏃堕棿';
+COMMENT ON COLUMN flow_user.update_by IS '鏇存柊浜�';
+COMMENT ON COLUMN flow_user.del_flag IS '鍒犻櫎鏍囧織';
+COMMENT ON COLUMN flow_user.tenant_id IS '绉熸埛id';
+
+-- ----------------------------
+-- 娴佺▼鍒嗙被琛�
+-- ----------------------------
+CREATE TABLE flow_category
+(
+ category_id int8 NOT NULL,
+ tenant_id VARCHAR(20) DEFAULT '000000'::varchar,
+ parent_id int8 DEFAULT 0,
+ ancestors VARCHAR(500) DEFAULT ''::varchar,
+ category_name VARCHAR(30) NOT NULL,
+ order_num INT DEFAULT 0,
+ del_flag CHAR DEFAULT '0'::bpchar,
+ create_dept int8,
+ create_by int8,
+ create_time TIMESTAMP,
+ update_by int8,
+ update_time TIMESTAMP,
+ PRIMARY KEY (category_id)
+);
+
+COMMENT ON TABLE flow_category IS '娴佺▼鍒嗙被';
+COMMENT ON COLUMN flow_category.category_id IS '娴佺▼鍒嗙被ID';
+COMMENT ON COLUMN flow_category.tenant_id IS '绉熸埛缂栧彿';
+COMMENT ON COLUMN flow_category.parent_id IS '鐖舵祦绋嬪垎绫籭d';
+COMMENT ON COLUMN flow_category.ancestors IS '绁栫骇鍒楄〃';
+COMMENT ON COLUMN flow_category.category_name IS '娴佺▼鍒嗙被鍚嶇О';
+COMMENT ON COLUMN flow_category.order_num IS '鏄剧ず椤哄簭';
+COMMENT ON COLUMN flow_category.del_flag IS '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+COMMENT ON COLUMN flow_category.create_dept IS '鍒涘缓閮ㄩ棬';
+COMMENT ON COLUMN flow_category.create_by IS '鍒涘缓鑰�';
+COMMENT ON COLUMN flow_category.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_category.update_by IS '鏇存柊鑰�';
+COMMENT ON COLUMN flow_category.update_time IS '鏇存柊鏃堕棿';
+
+INSERT INTO flow_category VALUES (100, '000000', 0, '0', 'OA瀹℃壒', 0, '0', 103, 1, now(), NULL, NULL);
+INSERT INTO flow_category VALUES (101, '000000', 100, '0,100', '鍋囧嫟绠$悊', 0, '0', 103, 1, now(), NULL, NULL);
+INSERT INTO flow_category VALUES (102, '000000', 100, '0,100', '浜轰簨绠$悊', 1, '0', 103, 1, now(), NULL, NULL);
+INSERT INTO flow_category VALUES (103, '000000', 101, '0,100,101', '璇峰亣', 0, '0', 103, 1, now(), NULL, NULL);
+INSERT INTO flow_category VALUES (104, '000000', 101, '0,100,101', '鍑哄樊', 1, '0', 103, 1, now(), NULL, NULL);
+INSERT INTO flow_category VALUES (105, '000000', 101, '0,100,101', '鍔犵彮', 2, '0', 103, 1, now(), NULL, NULL);
+INSERT INTO flow_category VALUES (106, '000000', 101, '0,100,101', '鎹㈢彮', 3, '0', 103, 1, now(), NULL, NULL);
+INSERT INTO flow_category VALUES (107, '000000', 101, '0,100,101', '澶栧嚭', 4, '0', 103, 1, now(), NULL, NULL);
+INSERT INTO flow_category VALUES (108, '000000', 102, '0,100,102', '杞', 1, '0', 103, 1, now(), NULL, NULL);
+INSERT INTO flow_category VALUES (109, '000000', 102, '0,100,102', '绂昏亴', 2, '0', 103, 1, now(), NULL, NULL);
+
+-- ----------------------------
+-- 娴佺▼spel琛ㄨ揪寮忓畾涔夎〃
+-- ----------------------------
+CREATE TABLE flow_spel (
+ id int8 NOT NULL,
+ component_name VARCHAR(255),
+ method_name VARCHAR(255),
+ method_params VARCHAR(255),
+ view_spel VARCHAR(255),
+ remark VARCHAR(255),
+ status CHAR(1) DEFAULT '0',
+ del_flag CHAR(1) DEFAULT '0',
+ create_dept int8,
+ create_by int8,
+ create_time TIMESTAMP,
+ update_by int8,
+ update_time TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+COMMENT ON TABLE flow_spel IS '娴佺▼spel琛ㄨ揪寮忓畾涔夎〃';
+COMMENT ON COLUMN flow_spel.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_spel.component_name IS '缁勪欢鍚嶇О';
+COMMENT ON COLUMN flow_spel.method_name IS '鏂规硶鍚�';
+COMMENT ON COLUMN flow_spel.method_params IS '鍙傛暟';
+COMMENT ON COLUMN flow_spel.view_spel IS '棰勮spel琛ㄨ揪寮�';
+COMMENT ON COLUMN flow_spel.remark IS '澶囨敞';
+COMMENT ON COLUMN flow_spel.status IS '鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+COMMENT ON COLUMN flow_spel.del_flag IS '鍒犻櫎鏍囧織';
+COMMENT ON COLUMN flow_spel.create_dept IS '鍒涘缓閮ㄩ棬';
+COMMENT ON COLUMN flow_spel.create_by IS '鍒涘缓鑰�';
+COMMENT ON COLUMN flow_spel.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_spel.update_by IS '鏇存柊鑰�';
+COMMENT ON COLUMN flow_spel.update_time IS '鏇存柊鏃堕棿';
+
+INSERT INTO flow_spel VALUES (1, 'spelRuleComponent', 'selectDeptLeaderById', 'initiatorDeptId', '#{@spelRuleComponent.selectDeptLeaderById(#initiatorDeptId)}', '鏍规嵁閮ㄩ棬id鑾峰彇閮ㄩ棬璐熻矗浜�', '0', '0', 103, 1, now(), 1, now());
+INSERT INTO flow_spel VALUES (2, NULL, NULL, 'initiator', '${initiator}', '娴佺▼鍙戣捣浜�', '0', '0', 103, 1, now(), 1, now());
+
+-- ----------------------------
+-- 娴佺▼瀹炰緥涓氬姟鎵╁睍琛�
+-- ----------------------------
+CREATE TABLE flow_instance_biz_ext (
+ id int8,
+ tenant_id VARCHAR(20) DEFAULT '000000',
+ create_dept int8,
+ create_by int8,
+ create_time TIMESTAMP,
+ update_by int8,
+ update_time TIMESTAMP,
+ business_code VARCHAR(255),
+ business_title VARCHAR(1000),
+ del_flag CHAR(1) DEFAULT '0',
+ instance_id int8,
+ business_id VARCHAR(255),
+ PRIMARY KEY (id)
+);
+
+COMMENT ON TABLE flow_instance_biz_ext IS '娴佺▼瀹炰緥涓氬姟鎵╁睍琛�';
+COMMENT ON COLUMN flow_instance_biz_ext.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_instance_biz_ext.tenant_id IS '绉熸埛缂栧彿';
+COMMENT ON COLUMN flow_instance_biz_ext.create_dept IS '鍒涘缓閮ㄩ棬';
+COMMENT ON COLUMN flow_instance_biz_ext.create_by IS '鍒涘缓鑰�';
+COMMENT ON COLUMN flow_instance_biz_ext.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_instance_biz_ext.update_by IS '鏇存柊鑰�';
+COMMENT ON COLUMN flow_instance_biz_ext.update_time IS '鏇存柊鏃堕棿';
+COMMENT ON COLUMN flow_instance_biz_ext.business_code IS '涓氬姟缂栫爜';
+COMMENT ON COLUMN flow_instance_biz_ext.business_title IS '涓氬姟鏍囬';
+COMMENT ON COLUMN flow_instance_biz_ext.del_flag IS '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+COMMENT ON COLUMN flow_instance_biz_ext.instance_id IS '娴佺▼瀹炰緥Id';
+COMMENT ON COLUMN flow_instance_biz_ext.business_id IS '涓氬姟Id';
+
+-- ----------------------------
+-- 璇峰亣鍗曚俊鎭�
+-- ----------------------------
+CREATE TABLE test_leave
+(
+ id int8 NOT NULL,
+ tenant_id VARCHAR(20) DEFAULT '000000'::varchar,
+ apply_code VARCHAR(50) NOT NULL,
+ leave_type VARCHAR(255) NOT NULL,
+ start_date TIMESTAMP NOT NULL,
+ end_date TIMESTAMP NOT NULL,
+ leave_days int2 NOT NULL,
+ remark VARCHAR(255),
+ status VARCHAR(255),
+ create_dept int8,
+ create_by int8,
+ create_time TIMESTAMP,
+ update_by int8,
+ update_time TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+COMMENT ON TABLE test_leave IS '璇峰亣鐢宠琛�';
+COMMENT ON COLUMN test_leave.id IS 'id';
+COMMENT ON COLUMN test_leave.tenant_id IS '绉熸埛缂栧彿';
+COMMENT ON COLUMN test_leave.apply_code IS '鐢宠缂栧彿';
+COMMENT ON COLUMN test_leave.leave_type IS '璇峰亣绫诲瀷';
+COMMENT ON COLUMN test_leave.start_date IS '寮�濮嬫椂闂�';
+COMMENT ON COLUMN test_leave.end_date IS '缁撴潫鏃堕棿';
+COMMENT ON COLUMN test_leave.leave_days IS '璇峰亣澶╂暟';
+COMMENT ON COLUMN test_leave.remark IS '璇峰亣鍘熷洜';
+COMMENT ON COLUMN test_leave.status IS '鐘舵��';
+COMMENT ON COLUMN test_leave.create_dept IS '鍒涘缓閮ㄩ棬';
+COMMENT ON COLUMN test_leave.create_by IS '鍒涘缓鑰�';
+COMMENT ON COLUMN test_leave.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN test_leave.update_by IS '鏇存柊鑰�';
+COMMENT ON COLUMN test_leave.update_time IS '鏇存柊鏃堕棿';
+
+INSERT INTO sys_menu VALUES ('11616', '宸ヤ綔娴�', '0', '6', 'workflow', '', '', '1', '0', 'M', '0', '0', '', 'workflow', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11618', '鎴戠殑浠诲姟', '0', '7', 'task', '', '', '1', '0', 'M', '0', '0', '', 'my-task', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11619', '鎴戠殑寰呭姙', '11618', '2', 'taskWaiting', 'workflow/task/taskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11632', '鎴戠殑宸插姙', '11618', '3', 'taskFinish', 'workflow/task/taskFinish', '', '1', '1', 'C', '0', '0', '', 'finish', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11633', '鎴戠殑鎶勯��', '11618', '4', 'taskCopyList', 'workflow/task/taskCopyList', '', '1', '1', 'C', '0', '0', '', 'my-copy', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11620', '娴佺▼瀹氫箟', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'process-definition', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11621', '娴佺▼瀹炰緥', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11622', '娴佺▼鍒嗙被', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'category', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11629', '鎴戝彂璧风殑', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'guide', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11630', '娴佺▼鐩戞帶', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11631', '寰呭姙浠诲姟', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11700', '娴佺▼璁捐', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, now(), NULL, NULL, '/workflow/processDefinition');
+INSERT INTO sys_menu VALUES ('11701', '璇峰亣鐢宠', '11616', '6', 'leaveEdit/index', 'workflow/leave/leaveEdit', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, now(), NULL, NULL, '');
+
+INSERT INTO sys_menu VALUES ('11623', '娴佺▼鍒嗙被鏌ヨ', '11622', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:query', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11624', '娴佺▼鍒嗙被鏂板', '11622', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:add', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11625', '娴佺▼鍒嗙被淇敼', '11622', '3', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:edit', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11626', '娴佺▼鍒嗙被鍒犻櫎', '11622', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:remove', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11627', '娴佺▼鍒嗙被瀵煎嚭', '11622', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:export', '#', 103, 1, now(), NULL, NULL, '');
+
+INSERT INTO sys_menu VALUES ('11801', '娴佺▼琛ㄨ揪寮�', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, now(), 1, now(), '娴佺▼杈惧紡瀹氫箟鑿滃崟');
+INSERT INTO sys_menu VALUES ('11802', '娴佺▼spel琛ㄨ揪寮忓畾涔夋煡璇�', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11803', '娴佺▼spel琛ㄨ揪寮忓畾涔夋柊澧�', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11804', '娴佺▼spel琛ㄨ揪寮忓畾涔変慨鏀�', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11805', '娴佺▼spel琛ㄨ揪寮忓畾涔夊垹闄�', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11806', '娴佺▼spel琛ㄨ揪寮忓畾涔夊鍑�', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, now(), NULL, NULL, '');
+
+INSERT INTO sys_menu VALUES ('11638', '璇峰亣鐢宠', '5', '1', 'leave', 'workflow/leave/index', '', '1', '0', 'C', '0', '0', 'workflow:leave:list', '#', 103, 1, now(), NULL, NULL, '璇峰亣鐢宠鑿滃崟');
+INSERT INTO sys_menu VALUES ('11639', '璇峰亣鐢宠鏌ヨ', '11638', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:query', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11640', '璇峰亣鐢宠鏂板', '11638', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:add', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11641', '璇峰亣鐢宠淇敼', '11638', '3', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:edit', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11642', '璇峰亣鐢宠鍒犻櫎', '11638', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:remove', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11643', '璇峰亣鐢宠瀵煎嚭', '11638', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:export', '#', 103, 1, now(), NULL, NULL, '');
+
+INSERT INTO sys_dict_type VALUES (13, '000000', '涓氬姟鐘舵��', 'wf_business_status', 103, 1, now(), NULL, NULL, '涓氬姟鐘舵�佸垪琛�');
+INSERT INTO sys_dict_type VALUES (14, '000000', '琛ㄥ崟绫诲瀷', 'wf_form_type', 103, 1, now(), NULL, NULL, '琛ㄥ崟绫诲瀷鍒楄〃');
+INSERT INTO sys_dict_type VALUES (15, '000000', '浠诲姟鐘舵��', 'wf_task_status', 103, 1, now(), NULL, NULL, '浠诲姟鐘舵��');
+INSERT INTO sys_dict_data VALUES (39, '000000', 1, '宸叉挙閿�', 'cancel', 'wf_business_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '宸叉挙閿�');
+INSERT INTO sys_dict_data VALUES (40, '000000', 2, '鑽夌', 'draft', 'wf_business_status', '', 'info', 'N', 103, 1, now(), NULL, NULL, '鑽夌');
+INSERT INTO sys_dict_data VALUES (41, '000000', 3, '寰呭鏍�', 'waiting', 'wf_business_status', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '寰呭鏍�');
+INSERT INTO sys_dict_data VALUES (42, '000000', 4, '宸插畬鎴�', 'finish', 'wf_business_status', '', 'success', 'N', 103, 1, now(), NULL, NULL, '宸插畬鎴�');
+INSERT INTO sys_dict_data VALUES (43, '000000', 5, '宸蹭綔搴�', 'invalid', 'wf_business_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '宸蹭綔搴�');
+INSERT INTO sys_dict_data VALUES (44, '000000', 6, '宸查��鍥�', 'back', 'wf_business_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '宸查��鍥�');
+INSERT INTO sys_dict_data VALUES (45, '000000', 7, '宸茬粓姝�', 'termination', 'wf_business_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '宸茬粓姝�');
+INSERT INTO sys_dict_data VALUES (46, '000000', 1, '鑷畾涔夎〃鍗�', 'static', 'wf_form_type', '', 'success', 'N', 103, 1, now(), NULL, NULL, '鑷畾涔夎〃鍗�');
+INSERT INTO sys_dict_data VALUES (47, '000000', 2, '鍔ㄦ�佽〃鍗�', 'dynamic', 'wf_form_type', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '鍔ㄦ�佽〃鍗�');
+INSERT INTO sys_dict_data VALUES (48, '000000', 1, '鎾ら攢', 'cancel', 'wf_task_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '鎾ら攢');
+INSERT INTO sys_dict_data VALUES (49, '000000', 2, '閫氳繃', 'pass', 'wf_task_status', '', 'success', 'N', 103, 1, now(), NULL, NULL, '閫氳繃');
+INSERT INTO sys_dict_data VALUES (50, '000000', 3, '寰呭鏍�', 'waiting', 'wf_task_status', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '寰呭鏍�');
+INSERT INTO sys_dict_data VALUES (51, '000000', 4, '浣滃簾', 'invalid', 'wf_task_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '浣滃簾');
+INSERT INTO sys_dict_data VALUES (52, '000000', 5, '閫�鍥�', 'back', 'wf_task_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '閫�鍥�');
+INSERT INTO sys_dict_data VALUES (53, '000000', 6, '缁堟', 'termination', 'wf_task_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '缁堟');
+INSERT INTO sys_dict_data VALUES (54, '000000', 7, '杞姙', 'transfer', 'wf_task_status', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '杞姙');
+INSERT INTO sys_dict_data VALUES (55, '000000', 8, '濮旀墭', 'depute', 'wf_task_status', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '濮旀墭');
+INSERT INTO sys_dict_data VALUES (56, '000000', 9, '鎶勯��', 'copy', 'wf_task_status', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '鎶勯��');
+INSERT INTO sys_dict_data VALUES (57, '000000', 10, '鍔犵', 'sign', 'wf_task_status', '', 'primary', 'N', 103, 1, now(), NULL, NULL, '鍔犵');
+INSERT INTO sys_dict_data VALUES (58, '000000', 11, '鍑忕', 'sign_off', 'wf_task_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '鍑忕');
+INSERT INTO sys_dict_data VALUES (59, '000000', 11, '瓒呮椂', 'timeout', 'wf_task_status', '', 'danger', 'N', 103, 1, now(), NULL, NULL, '瓒呮椂');
+
diff --git a/RuoYi-Vue-Plus/script/sql/ry_job.sql b/RuoYi-Vue-Plus/script/sql/ry_job.sql
new file mode 100755
index 0000000..dd8124c
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/ry_job.sql
@@ -0,0 +1,535 @@
+SET NAMES utf8mb4;
+
+CREATE TABLE `sj_namespace`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `name` varchar(64) NOT NULL COMMENT '鍚嶇О',
+ `unique_id` varchar(64) NOT NULL COMMENT '鍞竴id',
+ `description` varchar(256) NOT NULL DEFAULT '' COMMENT '鎻忚堪',
+ `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '閫昏緫鍒犻櫎 1銆佸垹闄�',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_name` (`name`),
+ UNIQUE KEY `uk_unique_id` (`unique_id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4 COMMENT ='鍛藉悕绌洪棿';
+
+INSERT INTO `sj_namespace` VALUES (1, 'Development', 'dev', '', 0, now(), now());
+INSERT INTO `sj_namespace` VALUES (2, 'Production', 'prod', '', 0, now(), now());
+
+CREATE TABLE `sj_group_config`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL DEFAULT '' COMMENT '缁勫悕绉�',
+ `description` varchar(256) NOT NULL DEFAULT '' COMMENT '缁勬弿杩�',
+ `token` varchar(64) NOT NULL DEFAULT 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT' COMMENT 'token',
+ `group_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '缁勭姸鎬� 0銆佹湭鍚敤 1銆佸惎鐢�',
+ `version` int(11) NOT NULL COMMENT '鐗堟湰鍙�',
+ `group_partition` int(11) NOT NULL COMMENT '鍒嗗尯',
+ `id_generator_mode` tinyint(4) NOT NULL DEFAULT 1 COMMENT '鍞竴id鐢熸垚妯″紡 榛樿鍙锋妯″紡',
+ `init_scene` tinyint(4) NOT NULL DEFAULT 0 COMMENT '鏄惁鍒濆鍖栧満鏅� 0:鍚� 1:鏄�',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_namespace_id_group_name` (`namespace_id`, `group_name`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='缁勯厤缃�';
+
+INSERT INTO `sj_group_config` VALUES (1, 'dev', 'ruoyi_group', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, now(), now());
+INSERT INTO `sj_group_config` VALUES (2, 'prod', 'ruoyi_group', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, now(), now());
+
+CREATE TABLE `sj_notify_config`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `notify_name` varchar(64) NOT NULL DEFAULT '' COMMENT '閫氱煡鍚嶇О',
+ `system_task_type` tinyint(4) NOT NULL DEFAULT 3 COMMENT '浠诲姟绫诲瀷 1. 閲嶈瘯浠诲姟 2. 閲嶈瘯鍥炶皟 3銆丣OB浠诲姟 4銆乄ORKFLOW浠诲姟',
+ `notify_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '閫氱煡鐘舵�� 0銆佹湭鍚敤 1銆佸惎鐢�',
+ `recipient_ids` varchar(128) NOT NULL COMMENT '鎺ユ敹浜篿d鍒楄〃',
+ `notify_threshold` int(11) NOT NULL DEFAULT 0 COMMENT '閫氱煡闃堝��',
+ `notify_scene` tinyint(4) NOT NULL DEFAULT 0 COMMENT '閫氱煡鍦烘櫙',
+ `rate_limiter_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '闄愭祦鐘舵�� 0銆佹湭鍚敤 1銆佸惎鐢�',
+ `rate_limiter_threshold` int(11) NOT NULL DEFAULT 0 COMMENT '姣忕闄愭祦闃堝��',
+ `description` varchar(256) NOT NULL DEFAULT '' COMMENT '鎻忚堪',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='閫氱煡閰嶇疆';
+
+CREATE TABLE `sj_notify_recipient`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `recipient_name` varchar(64) NOT NULL COMMENT '鎺ユ敹浜哄悕绉�',
+ `notify_type` tinyint(4) NOT NULL DEFAULT 0 COMMENT '閫氱煡绫诲瀷 1銆侀拤閽� 2銆侀偖浠� 3銆佷紒涓氬井淇� 4 椋炰功 5 webhook',
+ `notify_attribute` varchar(512) NOT NULL COMMENT '閰嶇疆灞炴��',
+ `description` varchar(256) NOT NULL DEFAULT '' COMMENT '鎻忚堪',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_namespace_id` (`namespace_id`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='鍛婅閫氱煡鎺ユ敹浜�';
+
+CREATE TABLE `sj_retry_dead_letter`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `group_id` bigint(20) NOT NULL COMMENT '缁処d',
+ `scene_name` varchar(64) NOT NULL COMMENT '鍦烘櫙鍚嶇О',
+ `scene_id` bigint(20) NOT NULL COMMENT '鍦烘櫙ID',
+ `idempotent_id` varchar(64) NOT NULL COMMENT '骞傜瓑id',
+ `biz_no` varchar(64) NOT NULL DEFAULT '' COMMENT '涓氬姟缂栧彿',
+ `executor_name` varchar(512) NOT NULL DEFAULT '' COMMENT '鎵ц鍣ㄥ悕绉�',
+ -- jackson 鍏煎鍘嗗彶鏁版嵁 棰勮1.8.0榛樿鏀逛负fury
+ `serializer_name` varchar(32) NOT NULL DEFAULT 'jackson' COMMENT '鎵ц鏂规硶鍙傛暟搴忓垪鍖栧櫒鍚嶇О',
+ `args_str` text NOT NULL COMMENT '鎵ц鏂规硶鍙傛暟',
+ `ext_attrs` text NOT NULL COMMENT '鎵╁睍瀛楁',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`, `scene_name`),
+ KEY `idx_idempotent_id` (`idempotent_id`),
+ KEY `idx_biz_no` (`biz_no`),
+ KEY `idx_create_dt` (`create_dt`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='姝讳俊闃熷垪琛�';
+
+CREATE TABLE `sj_retry`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `group_id` bigint(20) NOT NULL COMMENT '缁処d',
+ `scene_name` varchar(64) NOT NULL COMMENT '鍦烘櫙鍚嶇О',
+ `scene_id` bigint(20) NOT NULL COMMENT '鍦烘櫙ID',
+ `idempotent_id` varchar(64) NOT NULL COMMENT '骞傜瓑id',
+ `biz_no` varchar(64) NOT NULL DEFAULT '' COMMENT '涓氬姟缂栧彿',
+ `executor_name` varchar(512) NOT NULL DEFAULT '' COMMENT '鎵ц鍣ㄥ悕绉�',
+ `args_str` text NOT NULL COMMENT '鎵ц鏂规硶鍙傛暟',
+ `ext_attrs` text NOT NULL COMMENT '鎵╁睍瀛楁',
+ -- jackson 鍏煎鍘嗗彶鏁版嵁 棰勮1.8.0榛樿鏀逛负fury
+ `serializer_name` varchar(32) NOT NULL DEFAULT 'jackson' COMMENT '鎵ц鏂规硶鍙傛暟搴忓垪鍖栧櫒鍚嶇О',
+ `next_trigger_at` bigint(13) NOT NULL COMMENT '涓嬫瑙﹀彂鏃堕棿',
+ `retry_count` int(11) NOT NULL DEFAULT 0 COMMENT '閲嶈瘯娆℃暟',
+ `retry_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '閲嶈瘯鐘舵�� 0銆侀噸璇曚腑 1銆佹垚鍔� 2銆佹渶澶ч噸璇曟鏁�',
+ `task_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '浠诲姟绫诲瀷 1銆侀噸璇曟暟鎹� 2銆佸洖璋冩暟鎹�',
+ `bucket_index` int(11) NOT NULL DEFAULT 0 COMMENT 'bucket',
+ `parent_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '鐖惰妭鐐筰d',
+ `deleted` bigint(20) NOT NULL DEFAULT 0 COMMENT '閫昏緫鍒犻櫎',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_biz_no` (`biz_no`),
+ KEY `idx_idempotent_id` (`idempotent_id`),
+ KEY `idx_retry_status_bucket_index` (`retry_status`, `bucket_index`),
+ KEY `idx_parent_id` (`parent_id`),
+ KEY `idx_create_dt` (`create_dt`),
+ UNIQUE KEY `uk_scene_tasktype_idempotentid_deleted` (`scene_id`, `task_type`, `idempotent_id`, `deleted`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='閲嶈瘯淇℃伅琛�';
+
+CREATE TABLE `sj_retry_task`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `scene_name` varchar(64) NOT NULL COMMENT '鍦烘櫙鍚嶇О',
+ `retry_id` bigint(20) NOT NULL COMMENT '閲嶈瘯淇℃伅Id',
+ `ext_attrs` text NOT NULL COMMENT '鎵╁睍瀛楁',
+ `task_status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '閲嶈瘯鐘舵��',
+ `task_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '浠诲姟绫诲瀷 1銆侀噸璇曟暟鎹� 2銆佸洖璋冩暟鎹�',
+ `operation_reason` tinyint(4) NOT NULL DEFAULT 0 COMMENT '鎿嶄綔鍘熷洜',
+ `client_info` varchar(128) DEFAULT NULL COMMENT '瀹㈡埛绔湴鍧� clientId#ip:port',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_group_name_scene_name` (`namespace_id`, `group_name`, `scene_name`),
+ KEY `task_status` (`task_status`),
+ KEY `idx_create_dt` (`create_dt`),
+ KEY `idx_retry_id` (`retry_id`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='閲嶈瘯浠诲姟琛�';
+
+CREATE TABLE `sj_retry_task_log_message`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `retry_id` bigint(20) NOT NULL COMMENT '閲嶈瘯淇℃伅Id',
+ `retry_task_id` bigint(20) NOT NULL COMMENT '閲嶈瘯浠诲姟Id',
+ `message` longtext NOT NULL COMMENT '寮傚父淇℃伅',
+ `log_num` int(11) NOT NULL DEFAULT 1 COMMENT '鏃ュ織鏁伴噺',
+ `real_time` bigint(13) NOT NULL DEFAULT 0 COMMENT '涓婃姤鏃堕棿',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_namespace_id_group_name_retry_task_id` (`namespace_id`, `group_name`, `retry_task_id`),
+ KEY `idx_create_dt` (`create_dt`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='浠诲姟璋冨害鏃ュ織淇℃伅璁板綍琛�';
+
+CREATE TABLE `sj_retry_scene_config`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `scene_name` varchar(64) NOT NULL COMMENT '鍦烘櫙鍚嶇О',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `scene_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '缁勭姸鎬� 0銆佹湭鍚敤 1銆佸惎鐢�',
+ `max_retry_count` int(11) NOT NULL DEFAULT 5 COMMENT '鏈�澶ч噸璇曟鏁�',
+ `back_off` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1銆侀粯璁ょ瓑绾� 2銆佸浐瀹氶棿闅旀椂闂� 3銆丆RON 琛ㄨ揪寮�',
+ `trigger_interval` varchar(16) NOT NULL DEFAULT '' COMMENT '闂撮殧鏃堕暱',
+ `notify_ids` varchar(128) NOT NULL DEFAULT '' COMMENT '閫氱煡鍛婅鍦烘櫙閰嶇疆id鍒楄〃',
+ `deadline_request` bigint(20) unsigned NOT NULL DEFAULT 60000 COMMENT 'Deadline Request 璋冪敤閾捐秴鏃� 鍗曚綅姣',
+ `executor_timeout` int(11) unsigned NOT NULL DEFAULT 5 COMMENT '浠诲姟鎵ц瓒呮椂鏃堕棿锛屽崟浣嶇',
+ `route_key` tinyint(4) NOT NULL DEFAULT 4 COMMENT '璺敱绛栫暐',
+ `block_strategy` tinyint(4) NOT NULL DEFAULT 1 COMMENT '闃诲绛栫暐 1銆佷涪寮� 2銆佽鐩� 3銆佸苟琛�',
+ `cb_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '鍥炶皟鐘舵�� 0銆佷笉寮�鍚� 1銆佸紑鍚�',
+ `cb_trigger_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1銆侀粯璁ょ瓑绾� 2銆佸浐瀹氶棿闅旀椂闂� 3銆丆RON 琛ㄨ揪寮�',
+ `cb_max_count` int(11) NOT NULL DEFAULT 16 COMMENT '鍥炶皟鐨勬渶澶ф墽琛屾鏁�',
+ `cb_trigger_interval` varchar(16) NOT NULL DEFAULT '' COMMENT '鍥炶皟鐨勬渶澶ф墽琛屾鏁�',
+ `owner_id` bigint(20) NULL DEFAULT NULL COMMENT '璐熻矗浜篿d',
+ `labels` varchar(512) NULL DEFAULT '' COMMENT '鏍囩',
+ `description` varchar(256) NOT NULL DEFAULT '' COMMENT '鎻忚堪',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`, `scene_name`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='鍦烘櫙閰嶇疆';
+
+CREATE TABLE `sj_server_node`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `host_id` varchar(64) NOT NULL COMMENT '涓绘満id',
+ `host_ip` varchar(64) NOT NULL COMMENT '鏈哄櫒ip',
+ `host_port` int(16) NOT NULL COMMENT '鏈哄櫒绔彛',
+ `expire_at` datetime NOT NULL COMMENT '杩囨湡鏃堕棿',
+ `node_type` tinyint(4) NOT NULL COMMENT '鑺傜偣绫诲瀷 1銆佸鎴风 2銆佹槸鏈嶅姟绔�',
+ `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '鎵╁睍瀛楁',
+ `labels` varchar(512) NULL DEFAULT '' COMMENT '鏍囩',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`),
+ KEY `idx_expire_at_node_type` (`expire_at`, `node_type`),
+ UNIQUE KEY `uk_host_id_host_ip` (`host_id`, `host_ip`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='鏈嶅姟鍣ㄨ妭鐐�';
+
+CREATE TABLE `sj_distributed_lock`
+(
+ `name` varchar(64) NOT NULL COMMENT '閿佸悕绉�',
+ `lock_until` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '閿佸畾鏃堕暱',
+ `locked_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '閿佸畾鏃堕棿',
+ `locked_by` varchar(255) NOT NULL COMMENT '閿佸畾鑰�',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`name`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='閿佸畾琛�';
+
+CREATE TABLE `sj_system_user`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `username` varchar(64) NOT NULL COMMENT '璐﹀彿',
+ `password` varchar(128) NOT NULL COMMENT '瀵嗙爜',
+ `role` tinyint(4) NOT NULL DEFAULT 0 COMMENT '瑙掕壊锛�1-鏅�氱敤鎴枫��2-绠$悊鍛�',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_username` (`username`) USING BTREE
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4 COMMENT ='绯荤粺鐢ㄦ埛琛�';
+
+-- pwd: admin
+INSERT INTO `sj_system_user` (username, password, role)
+VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2);
+
+CREATE TABLE `sj_system_user_permission`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `system_user_id` bigint(20) NOT NULL COMMENT '绯荤粺鐢ㄦ埛id',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_namespace_id_group_name_system_user_id` (`namespace_id`, `group_name`, `system_user_id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4 COMMENT ='绯荤粺鐢ㄦ埛鏉冮檺琛�';
+
+-- 鍒嗗竷寮忚皟搴DL
+CREATE TABLE `sj_job`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `job_name` varchar(64) NOT NULL COMMENT '鍚嶇О',
+ `args_str` text DEFAULT NULL COMMENT '鎵ц鏂规硶鍙傛暟',
+ `args_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '鍙傛暟绫诲瀷 ',
+ `next_trigger_at` bigint(13) NOT NULL COMMENT '涓嬫瑙﹀彂鏃堕棿',
+ `job_status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '浠诲姟鐘舵�� 0銆佸叧闂��1銆佸紑鍚�',
+ `task_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '浠诲姟绫诲瀷 1銆侀泦缇� 2銆佸箍鎾� 3銆佸垏鐗�',
+ `route_key` tinyint(4) NOT NULL DEFAULT 4 COMMENT '璺敱绛栫暐',
+ `executor_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '鎵ц鍣ㄧ被鍨�',
+ `executor_info` varchar(255) DEFAULT NULL COMMENT '鎵ц鍣ㄥ悕绉�',
+ `trigger_type` tinyint(4) NOT NULL COMMENT '瑙﹀彂绫诲瀷 1.CRON 琛ㄨ揪寮� 2. 鍥哄畾鏃堕棿',
+ `trigger_interval` varchar(255) NOT NULL COMMENT '闂撮殧鏃堕暱',
+ `block_strategy` tinyint(4) NOT NULL DEFAULT 1 COMMENT '闃诲绛栫暐 1銆佷涪寮� 2銆佽鐩� 3銆佸苟琛� 4銆佹仮澶�',
+ `executor_timeout` int(11) NOT NULL DEFAULT 0 COMMENT '浠诲姟鎵ц瓒呮椂鏃堕棿锛屽崟浣嶇',
+ `max_retry_times` int(11) NOT NULL DEFAULT 0 COMMENT '鏈�澶ч噸璇曟鏁�',
+ `parallel_num` int(11) NOT NULL DEFAULT 1 COMMENT '骞惰鏁�',
+ `retry_interval` int(11) NOT NULL DEFAULT 0 COMMENT '閲嶈瘯闂撮殧(s)',
+ `bucket_index` int(11) NOT NULL DEFAULT 0 COMMENT 'bucket',
+ `resident` tinyint(4) NOT NULL DEFAULT 0 COMMENT '鏄惁鏄父椹讳换鍔�',
+ `notify_ids` varchar(128) NOT NULL DEFAULT '' COMMENT '閫氱煡鍛婅鍦烘櫙閰嶇疆id鍒楄〃',
+ `owner_id` bigint(20) NULL DEFAULT NULL COMMENT '璐熻矗浜篿d',
+ `labels` varchar(512) NULL DEFAULT '' COMMENT '鏍囩',
+ `description` varchar(256) NOT NULL DEFAULT '' COMMENT '鎻忚堪',
+ `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '鎵╁睍瀛楁',
+ `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '閫昏緫鍒犻櫎 1銆佸垹闄�',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`),
+ KEY `idx_job_status_bucket_index` (`job_status`, `bucket_index`),
+ KEY `idx_create_dt` (`create_dt`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='浠诲姟淇℃伅';
+
+INSERT INTO `sj_job` VALUES (1, 'dev', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '','', '', 0 , now(), now());
+
+CREATE TABLE `sj_job_log_message`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `job_id` bigint(20) NOT NULL COMMENT '浠诲姟淇℃伅id',
+ `task_batch_id` bigint(20) NOT NULL COMMENT '浠诲姟鎵规id',
+ `task_id` bigint(20) NOT NULL COMMENT '璋冨害浠诲姟id',
+ `message` longtext NOT NULL COMMENT '璋冨害淇℃伅',
+ `log_num` int(11) NOT NULL DEFAULT 1 COMMENT '鏃ュ織鏁伴噺',
+ `real_time` bigint(13) NOT NULL DEFAULT 0 COMMENT '涓婃姤鏃堕棿',
+ `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '鎵╁睍瀛楁',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_task_batch_id_task_id` (`task_batch_id`, `task_id`),
+ KEY `idx_create_dt` (`create_dt`),
+ KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='璋冨害鏃ュ織';
+
+CREATE TABLE `sj_job_task`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `job_id` bigint(20) NOT NULL COMMENT '浠诲姟淇℃伅id',
+ `task_batch_id` bigint(20) NOT NULL COMMENT '璋冨害浠诲姟id',
+ `parent_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '鐖舵墽琛屽櫒id',
+ `task_status` tinyint NOT NULL DEFAULT 0 COMMENT '鎵ц鐨勭姸鎬� 0銆佸け璐� 1銆佹垚鍔�',
+ `retry_count` int(11) NOT NULL DEFAULT 0 COMMENT '閲嶈瘯娆℃暟',
+ `mr_stage` tinyint DEFAULT NULL COMMENT '鍔ㄦ�佸垎鐗囨墍澶勯樁娈� 1:map 2:reduce 3:mergeReduce',
+ `leaf` tinyint NOT NULL DEFAULT '1' COMMENT '鍙跺瓙鑺傜偣',
+ `task_name` varchar(255) NOT NULL DEFAULT '' COMMENT '浠诲姟鍚嶇О',
+ `client_info` varchar(128) DEFAULT NULL COMMENT '瀹㈡埛绔湴鍧� clientId#ip:port',
+ `wf_context` text DEFAULT NULL COMMENT '宸ヤ綔娴佸叏灞�涓婁笅鏂�',
+ `result_message` text NOT NULL COMMENT '鎵ц缁撴灉',
+ `args_str` text DEFAULT NULL COMMENT '鎵ц鏂规硶鍙傛暟',
+ `args_type` tinyint NOT NULL DEFAULT 1 COMMENT '鍙傛暟绫诲瀷 ',
+ `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '鎵╁睍瀛楁',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_task_batch_id_task_status` (`task_batch_id`, `task_status`),
+ KEY `idx_create_dt` (`create_dt`),
+ KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='浠诲姟瀹炰緥';
+
+CREATE TABLE `sj_job_task_batch`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `job_id` bigint(20) NOT NULL COMMENT '浠诲姟id',
+ `workflow_node_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '宸ヤ綔娴佽妭鐐筰d',
+ `parent_workflow_node_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '宸ヤ綔娴佷换鍔$埗鎵规id',
+ `workflow_task_batch_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '宸ヤ綔娴佷换鍔℃壒娆d',
+ `task_batch_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '浠诲姟鎵规鐘舵�� 0銆佸け璐� 1銆佹垚鍔�',
+ `operation_reason` tinyint(4) NOT NULL DEFAULT 0 COMMENT '鎿嶄綔鍘熷洜',
+ `execution_at` bigint(13) NOT NULL DEFAULT 0 COMMENT '浠诲姟鎵ц鏃堕棿',
+ `system_task_type` tinyint(4) NOT NULL DEFAULT 3 COMMENT '浠诲姟绫诲瀷 3銆丣OB浠诲姟 4銆乄ORKFLOW浠诲姟',
+ `parent_id` varchar(64) NOT NULL DEFAULT '' COMMENT '鐖惰妭鐐�',
+ `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '鎵╁睍瀛楁',
+ `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '閫昏緫鍒犻櫎 1銆佸垹闄�',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_job_id_task_batch_status` (`job_id`, `task_batch_status`),
+ KEY `idx_create_dt` (`create_dt`),
+ KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`),
+ KEY `idx_workflow_task_batch_id_workflow_node_id` (`workflow_task_batch_id`, `workflow_node_id`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='浠诲姟鎵规';
+
+CREATE TABLE `sj_job_summary`
+(
+ `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` VARCHAR(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '缁勫悕绉�',
+ `business_id` bigint NOT NULL COMMENT '涓氬姟id (job_id鎴杦orkflow_id)',
+ `system_task_type` tinyint(4) NOT NULL DEFAULT 3 COMMENT '浠诲姟绫诲瀷 3銆丣OB浠诲姟 4銆乄ORKFLOW浠诲姟',
+ `trigger_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '缁熻鏃堕棿',
+ `success_num` int NOT NULL DEFAULT 0 COMMENT '鎵ц鎴愬姛-鏃ュ織鏁伴噺',
+ `fail_num` int NOT NULL DEFAULT 0 COMMENT '鎵ц澶辫触-鏃ュ織鏁伴噺',
+ `fail_reason` varchar(512) NOT NULL DEFAULT '' COMMENT '澶辫触鍘熷洜',
+ `stop_num` int NOT NULL DEFAULT 0 COMMENT '鎵ц澶辫触-鏃ュ織鏁伴噺',
+ `stop_reason` varchar(512) NOT NULL DEFAULT '' COMMENT '澶辫触鍘熷洜',
+ `cancel_num` int NOT NULL DEFAULT 0 COMMENT '鎵ц澶辫触-鏃ュ織鏁伴噺',
+ `cancel_reason` varchar(512) NOT NULL DEFAULT '' COMMENT '澶辫触鍘熷洜',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_namespace_id_group_name_business_id` (`namespace_id`, `group_name`, business_id),
+ UNIQUE KEY `uk_trigger_at_system_task_type_business_id` (`trigger_at`, `system_task_type`, `business_id`) USING BTREE
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 1
+ DEFAULT CHARSET = utf8mb4 COMMENT ='DashBoard_Job';
+
+CREATE TABLE `sj_retry_summary`
+(
+ `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` VARCHAR(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '缁勫悕绉�',
+ `scene_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '鍦烘櫙鍚嶇О',
+ `trigger_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '缁熻鏃堕棿',
+ `running_num` int NOT NULL DEFAULT 0 COMMENT '閲嶈瘯涓�-鏃ュ織鏁伴噺',
+ `finish_num` int NOT NULL DEFAULT 0 COMMENT '閲嶈瘯瀹屾垚-鏃ュ織鏁伴噺',
+ `max_count_num` int NOT NULL DEFAULT 0 COMMENT '閲嶈瘯鍒拌揪鏈�澶ф鏁�-鏃ュ織鏁伴噺',
+ `suspend_num` int NOT NULL DEFAULT 0 COMMENT '鏆傚仠閲嶈瘯-鏃ュ織鏁伴噺',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_trigger_at` (`trigger_at`),
+ UNIQUE KEY `uk_scene_name_trigger_at` (`namespace_id`, `group_name`, `scene_name`, `trigger_at`) USING BTREE
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 1
+ DEFAULT CHARSET = utf8mb4 COMMENT ='DashBoard_Retry';
+
+CREATE TABLE `sj_workflow`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `workflow_name` varchar(64) NOT NULL COMMENT '宸ヤ綔娴佸悕绉�',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `workflow_status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '宸ヤ綔娴佺姸鎬� 0銆佸叧闂��1銆佸紑鍚�',
+ `trigger_type` tinyint(4) NOT NULL COMMENT '瑙﹀彂绫诲瀷 1.CRON 琛ㄨ揪寮� 2. 鍥哄畾鏃堕棿',
+ `trigger_interval` varchar(255) NOT NULL COMMENT '闂撮殧鏃堕暱',
+ `next_trigger_at` bigint NOT NULL COMMENT '涓嬫瑙﹀彂鏃堕棿',
+ `block_strategy` tinyint(4) NOT NULL DEFAULT 1 COMMENT '闃诲绛栫暐 1銆佷涪寮� 2銆佽鐩� 3銆佸苟琛�',
+ `executor_timeout` int(11) NOT NULL DEFAULT 0 COMMENT '浠诲姟鎵ц瓒呮椂鏃堕棿锛屽崟浣嶇',
+ `description` varchar(256) NOT NULL DEFAULT '' COMMENT '鎻忚堪',
+ `flow_info` text DEFAULT NULL COMMENT '娴佺▼淇℃伅',
+ `wf_context` text DEFAULT NULL COMMENT '涓婁笅鏂�',
+ `notify_ids` varchar(128) NOT NULL DEFAULT '' COMMENT '閫氱煡鍛婅鍦烘櫙閰嶇疆id鍒楄〃',
+ `bucket_index` int(11) NOT NULL DEFAULT 0 COMMENT 'bucket',
+ `version` int(11) NOT NULL COMMENT '鐗堟湰鍙�',
+ `owner_id` bigint(20) NULL DEFAULT NULL COMMENT '璐熻矗浜篿d',
+ `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '鎵╁睍瀛楁',
+ `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '閫昏緫鍒犻櫎 1銆佸垹闄�',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_create_dt` (`create_dt`),
+ KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='宸ヤ綔娴�';
+
+CREATE TABLE `sj_workflow_node`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `node_name` varchar(64) NOT NULL COMMENT '鑺傜偣鍚嶇О',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `job_id` bigint(20) NOT NULL COMMENT '浠诲姟淇℃伅id',
+ `workflow_id` bigint(20) NOT NULL COMMENT '宸ヤ綔娴両D',
+ `node_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1銆佷换鍔¤妭鐐� 2銆佹潯浠惰妭鐐�',
+ `expression_type` tinyint(4) NOT NULL DEFAULT 0 COMMENT '1銆丼pEl銆�2銆丄viator 3銆丵L',
+ `fail_strategy` tinyint(4) NOT NULL DEFAULT 1 COMMENT '澶辫触绛栫暐 1銆佽烦杩� 2銆侀樆濉�',
+ `workflow_node_status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '宸ヤ綔娴佽妭鐐圭姸鎬� 0銆佸叧闂��1銆佸紑鍚�',
+ `priority_level` int(11) NOT NULL DEFAULT 1 COMMENT '浼樺厛绾�',
+ `node_info` text DEFAULT NULL COMMENT '鑺傜偣淇℃伅 ',
+ `version` int(11) NOT NULL COMMENT '鐗堟湰鍙�',
+ `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '鎵╁睍瀛楁',
+ `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '閫昏緫鍒犻櫎 1銆佸垹闄�',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_create_dt` (`create_dt`),
+ KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='宸ヤ綔娴佽妭鐐�';
+
+CREATE TABLE `sj_workflow_task_batch`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `workflow_id` bigint(20) NOT NULL COMMENT '宸ヤ綔娴佷换鍔d',
+ `task_batch_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '浠诲姟鎵规鐘舵�� 0銆佸け璐� 1銆佹垚鍔�',
+ `operation_reason` tinyint(4) NOT NULL DEFAULT 0 COMMENT '鎿嶄綔鍘熷洜',
+ `flow_info` text DEFAULT NULL COMMENT '娴佺▼淇℃伅',
+ `wf_context` text DEFAULT NULL COMMENT '鍏ㄥ眬涓婁笅鏂�',
+ `execution_at` bigint(13) NOT NULL DEFAULT 0 COMMENT '浠诲姟鎵ц鏃堕棿',
+ `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '鎵╁睍瀛楁',
+ `version` int(11) NOT NULL DEFAULT 1 COMMENT '鐗堟湰鍙�',
+ `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '閫昏緫鍒犻櫎 1銆佸垹闄�',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_job_id_task_batch_status` (`workflow_id`, `task_batch_status`),
+ KEY `idx_create_dt` (`create_dt`),
+ KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='宸ヤ綔娴佹壒娆�';
+
+CREATE TABLE `sj_job_executor`
+(
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '鍛藉悕绌洪棿id',
+ `group_name` varchar(64) NOT NULL COMMENT '缁勫悕绉�',
+ `executor_info` varchar(256) NOT NULL COMMENT '浠诲姟鎵ц鍣ㄥ悕绉�',
+ `executor_type` varchar(3) NOT NULL COMMENT '1:java 2:python 3:go',
+ `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+ PRIMARY KEY (`id`),
+ KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`),
+ KEY `idx_create_dt` (`create_dt`)
+) ENGINE = InnoDB
+ AUTO_INCREMENT = 0
+ DEFAULT CHARSET = utf8mb4 COMMENT ='浠诲姟鎵ц鍣ㄤ俊鎭�';
diff --git a/RuoYi-Vue-Plus/script/sql/ry_vue_5.X.sql b/RuoYi-Vue-Plus/script/sql/ry_vue_5.X.sql
new file mode 100755
index 0000000..3cd642b
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/ry_vue_5.X.sql
@@ -0,0 +1,976 @@
+-- ----------------------------
+-- 绗笁鏂瑰钩鍙版巿鏉冭〃
+-- ----------------------------
+create table sys_social
+(
+ id bigint not null comment '涓婚敭',
+ user_id bigint not null comment '鐢ㄦ埛ID',
+ tenant_id varchar(20) default '000000' comment '绉熸埛id',
+ auth_id varchar(255) not null comment '骞冲彴+骞冲彴鍞竴id',
+ source varchar(255) not null comment '鐢ㄦ埛鏉ユ簮',
+ open_id varchar(255) default null comment '骞冲彴缂栧彿鍞竴id',
+ user_name varchar(30) not null comment '鐧诲綍璐﹀彿',
+ nick_name varchar(30) default '' comment '鐢ㄦ埛鏄电О',
+ email varchar(255) default '' comment '鐢ㄦ埛閭',
+ avatar varchar(500) default '' comment '澶村儚鍦板潃',
+ access_token varchar(2000) not null comment '鐢ㄦ埛鐨勬巿鏉冧护鐗�',
+ expire_in int default null comment '鐢ㄦ埛鐨勬巿鏉冧护鐗岀殑鏈夋晥鏈燂紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ refresh_token varchar(255) default null comment '鍒锋柊浠ょ墝锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�',
+ access_code varchar(2000) default null comment '骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ union_id varchar(255) default null comment '鐢ㄦ埛鐨� unionid',
+ scope varchar(255) default null comment '鎺堜簣鐨勬潈闄愶紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ token_type varchar(255) default null comment '涓埆骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ id_token varchar(2000) default null comment 'id token锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�',
+ mac_algorithm varchar(255) default null comment '灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ mac_key varchar(255) default null comment '灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ code varchar(255) default null comment '鐢ㄦ埛鐨勬巿鏉僣ode锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�',
+ oauth_token varchar(255) default null comment 'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ oauth_token_secret varchar(255) default null comment 'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ create_dept bigint(20) comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ del_flag char(1) default '0' comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�',
+ PRIMARY KEY (id)
+) engine=innodb comment = '绀句細鍖栧叧绯昏〃';
+
+
+-- ----------------------------
+-- 绉熸埛琛�
+-- ----------------------------
+create table sys_tenant
+(
+ id bigint(20) not null comment 'id',
+ tenant_id varchar(20) not null comment '绉熸埛缂栧彿',
+ contact_user_name varchar(20) comment '鑱旂郴浜�',
+ contact_phone varchar(20) comment '鑱旂郴鐢佃瘽',
+ company_name varchar(30) comment '浼佷笟鍚嶇О',
+ license_number varchar(30) comment '缁熶竴绀句細淇$敤浠g爜',
+ address varchar(200) comment '鍦板潃',
+ intro varchar(200) comment '浼佷笟绠�浠�',
+ domain varchar(200) comment '鍩熷悕',
+ remark varchar(200) comment '澶囨敞',
+ package_id bigint(20) comment '绉熸埛濂楅缂栧彿',
+ expire_time datetime comment '杩囨湡鏃堕棿',
+ account_count int default -1 comment '鐢ㄦ埛鏁伴噺锛�-1涓嶉檺鍒讹級',
+ status char(1) default '0' comment '绉熸埛鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ del_flag char(1) default '0' comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�',
+ create_dept bigint(20) comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ primary key (id)
+) engine=innodb comment = '绉熸埛琛�';
+
+
+-- ----------------------------
+-- 鍒濆鍖�-绉熸埛琛ㄦ暟鎹�
+-- ----------------------------
+
+insert into sys_tenant values(1, '000000', '绠$悊缁�', '15888888888', 'XXX鏈夐檺鍏徃', null, null, '澶氱鎴烽�氱敤鍚庡彴绠$悊绠$悊绯荤粺', null, null, null, null, -1, '0', '0', 103, 1, sysdate(), null, null);
+
+
+-- ----------------------------
+-- 绉熸埛濂楅琛�
+-- ----------------------------
+create table sys_tenant_package (
+ package_id bigint(20) not null comment '绉熸埛濂楅id',
+ package_name varchar(20) comment '濂楅鍚嶇О',
+ menu_ids varchar(3000) comment '鍏宠仈鑿滃崟id',
+ remark varchar(200) comment '澶囨敞',
+ menu_check_strictly tinyint(1) default 1 comment '鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�',
+ status char(1) default '0' comment '鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ del_flag char(1) default '0' comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�',
+ create_dept bigint(20) comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ primary key (package_id)
+) engine=innodb comment = '绉熸埛濂楅琛�';
+
+
+-- ----------------------------
+-- 1銆侀儴闂ㄨ〃
+-- ----------------------------
+create table sys_dept (
+ dept_id bigint(20) not null comment '閮ㄩ棬id',
+ tenant_id varchar(20) default '000000' comment '绉熸埛缂栧彿',
+ parent_id bigint(20) default 0 comment '鐖堕儴闂╥d',
+ ancestors varchar(500) default '' comment '绁栫骇鍒楄〃',
+ dept_name varchar(30) default '' comment '閮ㄩ棬鍚嶇О',
+ dept_category varchar(100) default null comment '閮ㄩ棬绫诲埆缂栫爜',
+ order_num int(4) default 0 comment '鏄剧ず椤哄簭',
+ leader bigint(20) default null comment '璐熻矗浜�',
+ phone varchar(11) default null comment '鑱旂郴鐢佃瘽',
+ email varchar(50) default null comment '閭',
+ status char(1) default '0' comment '閮ㄩ棬鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ del_flag char(1) default '0' comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ primary key (dept_id)
+) engine=innodb comment = '閮ㄩ棬琛�';
+
+-- ----------------------------
+-- 鍒濆鍖�-閮ㄩ棬琛ㄦ暟鎹�
+-- ----------------------------
+
+
+insert into sys_dept values(100, '000000', 0, '0', 'XXX绉戞妧', null,0, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate(), null, null);
+insert into sys_dept values(101, '000000', 100, '0,100', '娣卞湷鎬诲叕鍙�', null,1, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate(), null, null);
+insert into sys_dept values(102, '000000', 100, '0,100', '闀挎矙鍒嗗叕鍙�', null,2, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate(), null, null);
+insert into sys_dept values(103, '000000', 101, '0,100,101', '鐮斿彂閮ㄩ棬', null,1, 1, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate(), null, null);
+insert into sys_dept values(104, '000000', 101, '0,100,101', '甯傚満閮ㄩ棬', null,2, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate(), null, null);
+insert into sys_dept values(105, '000000', 101, '0,100,101', '娴嬭瘯閮ㄩ棬', null,3, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate(), null, null);
+insert into sys_dept values(106, '000000', 101, '0,100,101', '璐㈠姟閮ㄩ棬', null,4, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate(), null, null);
+insert into sys_dept values(107, '000000', 101, '0,100,101', '杩愮淮閮ㄩ棬', null,5, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate(), null, null);
+insert into sys_dept values(108, '000000', 102, '0,100,102', '甯傚満閮ㄩ棬', null,1, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate(), null, null);
+insert into sys_dept values(109, '000000', 102, '0,100,102', '璐㈠姟閮ㄩ棬', null,2, null, '15888888888', 'xxx@qq.com', '0', '0', 103, 1, sysdate(), null, null);
+
+
+-- ----------------------------
+-- 2銆佺敤鎴蜂俊鎭〃
+-- ----------------------------
+create table sys_user (
+ user_id bigint(20) not null comment '鐢ㄦ埛ID',
+ tenant_id varchar(20) default '000000' comment '绉熸埛缂栧彿',
+ dept_id bigint(20) default null comment '閮ㄩ棬ID',
+ user_name varchar(30) not null comment '鐢ㄦ埛璐﹀彿',
+ nick_name varchar(30) not null comment '鐢ㄦ埛鏄电О',
+ user_type varchar(10) default 'sys_user' comment '鐢ㄦ埛绫诲瀷锛坰ys_user绯荤粺鐢ㄦ埛锛�',
+ email varchar(50) default '' comment '鐢ㄦ埛閭',
+ phonenumber varchar(11) default '' comment '鎵嬫満鍙风爜',
+ sex char(1) default '0' comment '鐢ㄦ埛鎬у埆锛�0鐢� 1濂� 2鏈煡锛�',
+ avatar bigint(20) comment '澶村儚鍦板潃',
+ password varchar(100) default '' comment '瀵嗙爜',
+ status char(1) default '0' comment '璐﹀彿鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ del_flag char(1) default '0' comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�',
+ login_ip varchar(128) default '' comment '鏈�鍚庣櫥褰旾P',
+ login_date datetime comment '鏈�鍚庣櫥褰曟椂闂�',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ remark varchar(500) default null comment '澶囨敞',
+ primary key (user_id)
+) engine=innodb comment = '鐢ㄦ埛淇℃伅琛�';
+
+-- ----------------------------
+-- 鍒濆鍖�-鐢ㄦ埛淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+insert into sys_user values(1, '000000', 103, 'admin', '鐤媯鐨勭嫯瀛怢i', 'sys_user', 'crazyLionLi@163.com', '15888888888', '1', null, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 103, 1, sysdate(), null, null, '绠$悊鍛�');
+insert into sys_user values(3, '000000', 108, 'test', '鏈儴闂ㄥ強浠ヤ笅 瀵嗙爜666666', 'sys_user', '', '', '0', null, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', sysdate(), 103, 1, sysdate(), 3, sysdate(), null);
+insert into sys_user values(4, '000000', 102, 'test1', '浠呮湰浜� 瀵嗙爜666666', 'sys_user', '', '', '0', null, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', sysdate(), 103, 1, sysdate(), 4, sysdate(), null);
+
+-- ----------------------------
+-- 3銆佸矖浣嶄俊鎭〃
+-- ----------------------------
+create table sys_post
+(
+ post_id bigint(20) not null comment '宀椾綅ID',
+ tenant_id varchar(20) default '000000' comment '绉熸埛缂栧彿',
+ dept_id bigint(20) not null comment '閮ㄩ棬id',
+ post_code varchar(64) not null comment '宀椾綅缂栫爜',
+ post_category varchar(100) default null comment '宀椾綅绫诲埆缂栫爜',
+ post_name varchar(50) not null comment '宀椾綅鍚嶇О',
+ post_sort int(4) not null comment '鏄剧ず椤哄簭',
+ status char(1) not null comment '鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ remark varchar(500) default null comment '澶囨敞',
+ primary key (post_id)
+) engine=innodb comment = '宀椾綅淇℃伅琛�';
+
+-- ----------------------------
+-- 鍒濆鍖�-宀椾綅淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+insert into sys_post values(1, '000000', 103, 'ceo', null, '钁d簨闀�', 1, '0', 103, 1, sysdate(), null, null, '');
+insert into sys_post values(2, '000000', 100, 'se', null, '椤圭洰缁忕悊', 2, '0', 103, 1, sysdate(), null, null, '');
+insert into sys_post values(3, '000000', 100, 'hr', null, '浜哄姏璧勬簮', 3, '0', 103, 1, sysdate(), null, null, '');
+insert into sys_post values(4, '000000', 100, 'user', null, '鏅�氬憳宸�', 4, '0', 103, 1, sysdate(), null, null, '');
+
+
+-- ----------------------------
+-- 4銆佽鑹蹭俊鎭〃
+-- ----------------------------
+create table sys_role (
+ role_id bigint(20) not null comment '瑙掕壊ID',
+ tenant_id varchar(20) default '000000' comment '绉熸埛缂栧彿',
+ role_name varchar(30) not null comment '瑙掕壊鍚嶇О',
+ role_key varchar(100) not null comment '瑙掕壊鏉冮檺瀛楃涓�',
+ role_sort int(4) not null comment '鏄剧ず椤哄簭',
+ data_scope char(1) default '1' comment '鏁版嵁鑼冨洿锛�1锛氬叏閮ㄦ暟鎹潈闄� 2锛氳嚜瀹氭暟鎹潈闄� 3锛氭湰閮ㄩ棬鏁版嵁鏉冮檺 4锛氭湰閮ㄩ棬鍙婁互涓嬫暟鎹潈闄� 5锛氫粎鏈汉鏁版嵁鏉冮檺 6锛氶儴闂ㄥ強浠ヤ笅鎴栨湰浜烘暟鎹潈闄愶級',
+ menu_check_strictly tinyint(1) default 1 comment '鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�',
+ dept_check_strictly tinyint(1) default 1 comment '閮ㄩ棬鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�',
+ status char(1) not null comment '瑙掕壊鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ del_flag char(1) default '0' comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ remark varchar(500) default null comment '澶囨敞',
+ primary key (role_id)
+) engine=innodb comment = '瑙掕壊淇℃伅琛�';
+
+-- ----------------------------
+-- 鍒濆鍖�-瑙掕壊淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+insert into sys_role values(1, '000000', '瓒呯骇绠$悊鍛�', 'superadmin', 1, 1, 1, 1, '0', '0', 103, 1, sysdate(), null, null, '瓒呯骇绠$悊鍛�');
+insert into sys_role values(3, '000000', '鏈儴闂ㄥ強浠ヤ笅', 'test1', 3, 4, 1, 1, '0', '0', 103, 1, sysdate(), null, null, '');
+insert into sys_role values(4, '000000', '浠呮湰浜�', 'test2', 4, 5, 1, 1, '0', '0', 103, 1, sysdate(), null, null, '');
+
+-- ----------------------------
+-- 5銆佽彍鍗曟潈闄愯〃
+-- ----------------------------
+create table sys_menu (
+ menu_id bigint(20) not null comment '鑿滃崟ID',
+ menu_name varchar(50) not null comment '鑿滃崟鍚嶇О',
+ parent_id bigint(20) default 0 comment '鐖惰彍鍗旾D',
+ order_num int(4) default 0 comment '鏄剧ず椤哄簭',
+ path varchar(200) default '' comment '璺敱鍦板潃',
+ component varchar(255) default null comment '缁勪欢璺緞',
+ query_param varchar(255) default null comment '璺敱鍙傛暟',
+ is_frame int(1) default 1 comment '鏄惁涓哄閾撅紙0鏄� 1鍚︼級',
+ is_cache int(1) default 0 comment '鏄惁缂撳瓨锛�0缂撳瓨 1涓嶇紦瀛橈級',
+ menu_type char(1) default '' comment '鑿滃崟绫诲瀷锛圡鐩綍 C鑿滃崟 F鎸夐挳锛�',
+ visible char(1) default 0 comment '鏄剧ず鐘舵�侊紙0鏄剧ず 1闅愯棌锛�',
+ status char(1) default 0 comment '鑿滃崟鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ perms varchar(100) default null comment '鏉冮檺鏍囪瘑',
+ icon varchar(100) default '#' comment '鑿滃崟鍥炬爣',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ remark varchar(500) default '' comment '澶囨敞',
+ primary key (menu_id)
+) engine=innodb comment = '鑿滃崟鏉冮檺琛�';
+
+-- ----------------------------
+-- 鍒濆鍖�-鑿滃崟淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+-- 涓�绾ц彍鍗�
+insert into sys_menu values('1', '绯荤粺绠$悊', '0', '1', 'system', null, '', 1, 0, 'M', '0', '0', '', 'system', 103, 1, sysdate(), null, null, '绯荤粺绠$悊鐩綍');
+insert into sys_menu values('6', '绉熸埛绠$悊', '0', '2', 'tenant', null, '', 1, 0, 'M', '0', '0', '', 'chart', 103, 1, sysdate(), null, null, '绉熸埛绠$悊鐩綍');
+insert into sys_menu values('2', '绯荤粺鐩戞帶', '0', '3', 'monitor', null, '', 1, 0, 'M', '0', '0', '', 'monitor', 103, 1, sysdate(), null, null, '绯荤粺鐩戞帶鐩綍');
+insert into sys_menu values('3', '绯荤粺宸ュ叿', '0', '4', 'tool', null, '', 1, 0, 'M', '0', '0', '', 'tool', 103, 1, sysdate(), null, null, '绯荤粺宸ュ叿鐩綍');
+insert into sys_menu values('4', 'PLUS瀹樼綉', '0', '5', 'https://gitee.com/dromara/RuoYi-Vue-Plus', null, '', 0, 0, 'M', '0', '0', '', 'guide', 103, 1, sysdate(), null, null, 'RuoYi-Vue-Plus瀹樼綉鍦板潃');
+insert into sys_menu values('5', '娴嬭瘯鑿滃崟', '0', '5', 'demo', null, '', 1, 0, 'M', '0', '0', '', 'star', 103, 1, sysdate(), null, null, '娴嬭瘯鑿滃崟');
+-- 浜岀骇鑿滃崟
+insert into sys_menu values('100', '鐢ㄦ埛绠$悊', '1', '1', 'user', 'system/user/index', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 103, 1, sysdate(), null, null, '鐢ㄦ埛绠$悊鑿滃崟');
+insert into sys_menu values('101', '瑙掕壊绠$悊', '1', '2', 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 103, 1, sysdate(), null, null, '瑙掕壊绠$悊鑿滃崟');
+insert into sys_menu values('102', '鑿滃崟绠$悊', '1', '3', 'menu', 'system/menu/index', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 103, 1, sysdate(), null, null, '鑿滃崟绠$悊鑿滃崟');
+insert into sys_menu values('103', '閮ㄩ棬绠$悊', '1', '4', 'dept', 'system/dept/index', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 103, 1, sysdate(), null, null, '閮ㄩ棬绠$悊鑿滃崟');
+insert into sys_menu values('104', '宀椾綅绠$悊', '1', '5', 'post', 'system/post/index', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 103, 1, sysdate(), null, null, '宀椾綅绠$悊鑿滃崟');
+insert into sys_menu values('105', '瀛楀吀绠$悊', '1', '6', 'dict', 'system/dict/index', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 103, 1, sysdate(), null, null, '瀛楀吀绠$悊鑿滃崟');
+insert into sys_menu values('106', '鍙傛暟璁剧疆', '1', '7', 'config', 'system/config/index', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 103, 1, sysdate(), null, null, '鍙傛暟璁剧疆鑿滃崟');
+insert into sys_menu values('107', '閫氱煡鍏憡', '1', '8', 'notice', 'system/notice/index', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 103, 1, sysdate(), null, null, '閫氱煡鍏憡鑿滃崟');
+insert into sys_menu values('108', '鏃ュ織绠$悊', '1', '9', 'log', '', '', 1, 0, 'M', '0', '0', '', 'log', 103, 1, sysdate(), null, null, '鏃ュ織绠$悊鑿滃崟');
+insert into sys_menu values('109', '鍦ㄧ嚎鐢ㄦ埛', '2', '1', 'online', 'monitor/online/index', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 103, 1, sysdate(), null, null, '鍦ㄧ嚎鐢ㄦ埛鑿滃崟');
+insert into sys_menu values('113', '缂撳瓨鐩戞帶', '2', '5', 'cache', 'monitor/cache/index', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 103, 1, sysdate(), null, null, '缂撳瓨鐩戞帶鑿滃崟');
+insert into sys_menu values('115', '浠g爜鐢熸垚', '3', '2', 'gen', 'tool/gen/index', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 103, 1, sysdate(), null, null, '浠g爜鐢熸垚鑿滃崟');
+insert into sys_menu values('121', '绉熸埛绠$悊', '6', '1', 'tenant', 'system/tenant/index', '', 1, 0, 'C', '0', '0', 'system:tenant:list', 'list', 103, 1, sysdate(), null, null, '绉熸埛绠$悊鑿滃崟');
+insert into sys_menu values('122', '绉熸埛濂楅绠$悊', '6', '2', 'tenantPackage', 'system/tenantPackage/index', '', 1, 0, 'C', '0', '0', 'system:tenantPackage:list', 'form', 103, 1, sysdate(), null, null, '绉熸埛濂楅绠$悊鑿滃崟');
+insert into sys_menu values('123', '瀹㈡埛绔鐞�', '1', '11', 'client', 'system/client/index', '', 1, 0, 'C', '0', '0', 'system:client:list', 'international', 103, 1, sysdate(), null, null, '瀹㈡埛绔鐞嗚彍鍗�');
+insert into sys_menu values('116', '淇敼鐢熸垚閰嶇疆', '3', '2', 'gen-edit/index/:tableId', 'tool/gen/editTable', '', 1, 1, 'C', '1', '0', 'tool:gen:edit', '#', 103, 1, sysdate(), null, null, '/tool/gen');
+insert into sys_menu values('130', '鍒嗛厤鐢ㄦ埛', '1', '2', 'role-auth/user/:roleId', 'system/role/authUser', '', 1, 1, 'C', '1', '0', 'system:role:edit', '#', 103, 1, sysdate(), null, null, '/system/role');
+insert into sys_menu values('131', '鍒嗛厤瑙掕壊', '1', '1', 'user-auth/role/:userId', 'system/user/authRole', '', 1, 1, 'C', '1', '0', 'system:user:edit', '#', 103, 1, sysdate(), null, null, '/system/user');
+insert into sys_menu values('132', '瀛楀吀鏁版嵁', '1', '6', 'dict-data/index/:dictId', 'system/dict/data', '', 1, 1, 'C', '1', '0', 'system:dict:list', '#', 103, 1, sysdate(), null, null, '/system/dict');
+insert into sys_menu values('133', '鏂囦欢閰嶇疆绠$悊', '1', '10', 'oss-config/index', 'system/oss/config', '', 1, 1, 'C', '1', '0', 'system:ossConfig:list', '#', 103, 1, sysdate(), null, null, '/system/oss');
+
+-- springboot-admin鐩戞帶
+insert into sys_menu values('117', 'Admin鐩戞帶', '2', '5', 'Admin', 'monitor/admin/index', '', 1, 0, 'C', '0', '0', 'monitor:admin:list', 'dashboard', 103, 1, sysdate(), null, null, 'Admin鐩戞帶鑿滃崟');
+-- oss鑿滃崟
+insert into sys_menu values('118', '鏂囦欢绠$悊', '1', '10', 'oss', 'system/oss/index', '', 1, 0, 'C', '0', '0', 'system:oss:list', 'upload', 103, 1, sysdate(), null, null, '鏂囦欢绠$悊鑿滃崟');
+-- snail-job server鎺у埗鍙�
+insert into sys_menu values('120', '浠诲姟璋冨害涓績', '2', '6', 'snailjob', 'monitor/snailjob/index', '', 1, 0, 'C', '0', '0', 'monitor:snailjob:list', 'job', 103, 1, sysdate(), null, null, 'SnailJob鎺у埗鍙拌彍鍗�');
+
+-- 涓夌骇鑿滃崟
+insert into sys_menu values('500', '鎿嶄綔鏃ュ織', '108', '1', 'operlog', 'monitor/operlog/index', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 103, 1, sysdate(), null, null, '鎿嶄綔鏃ュ織鑿滃崟');
+insert into sys_menu values('501', '鐧诲綍鏃ュ織', '108', '2', 'logininfor', 'monitor/logininfor/index', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 103, 1, sysdate(), null, null, '鐧诲綍鏃ュ織鑿滃崟');
+-- 鐢ㄦ埛绠$悊鎸夐挳
+insert into sys_menu values('1001', '鐢ㄦ埛鏌ヨ', '100', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1002', '鐢ㄦ埛鏂板', '100', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1003', '鐢ㄦ埛淇敼', '100', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1004', '鐢ㄦ埛鍒犻櫎', '100', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1005', '鐢ㄦ埛瀵煎嚭', '100', '5', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1006', '鐢ㄦ埛瀵煎叆', '100', '6', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1007', '閲嶇疆瀵嗙爜', '100', '7', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 103, 1, sysdate(), null, null, '');
+-- 瑙掕壊绠$悊鎸夐挳
+insert into sys_menu values('1008', '瑙掕壊鏌ヨ', '101', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1009', '瑙掕壊鏂板', '101', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1010', '瑙掕壊淇敼', '101', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1011', '瑙掕壊鍒犻櫎', '101', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1012', '瑙掕壊瀵煎嚭', '101', '5', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 103, 1, sysdate(), null, null, '');
+-- 鑿滃崟绠$悊鎸夐挳
+insert into sys_menu values('1013', '鑿滃崟鏌ヨ', '102', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1014', '鑿滃崟鏂板', '102', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1015', '鑿滃崟淇敼', '102', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1016', '鑿滃崟鍒犻櫎', '102', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 103, 1, sysdate(), null, null, '');
+-- 閮ㄩ棬绠$悊鎸夐挳
+insert into sys_menu values('1017', '閮ㄩ棬鏌ヨ', '103', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1018', '閮ㄩ棬鏂板', '103', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1019', '閮ㄩ棬淇敼', '103', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1020', '閮ㄩ棬鍒犻櫎', '103', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 103, 1, sysdate(), null, null, '');
+-- 宀椾綅绠$悊鎸夐挳
+insert into sys_menu values('1021', '宀椾綅鏌ヨ', '104', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1022', '宀椾綅鏂板', '104', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1023', '宀椾綅淇敼', '104', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1024', '宀椾綅鍒犻櫎', '104', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1025', '宀椾綅瀵煎嚭', '104', '5', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 103, 1, sysdate(), null, null, '');
+-- 瀛楀吀绠$悊鎸夐挳
+insert into sys_menu values('1026', '瀛楀吀鏌ヨ', '105', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1027', '瀛楀吀鏂板', '105', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1028', '瀛楀吀淇敼', '105', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1029', '瀛楀吀鍒犻櫎', '105', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1030', '瀛楀吀瀵煎嚭', '105', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 103, 1, sysdate(), null, null, '');
+-- 鍙傛暟璁剧疆鎸夐挳
+insert into sys_menu values('1031', '鍙傛暟鏌ヨ', '106', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1032', '鍙傛暟鏂板', '106', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1033', '鍙傛暟淇敼', '106', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1034', '鍙傛暟鍒犻櫎', '106', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1035', '鍙傛暟瀵煎嚭', '106', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 103, 1, sysdate(), null, null, '');
+-- 閫氱煡鍏憡鎸夐挳
+insert into sys_menu values('1036', '鍏憡鏌ヨ', '107', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1037', '鍏憡鏂板', '107', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1038', '鍏憡淇敼', '107', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1039', '鍏憡鍒犻櫎', '107', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 103, 1, sysdate(), null, null, '');
+-- 鎿嶄綔鏃ュ織鎸夐挳
+insert into sys_menu values('1040', '鎿嶄綔鏌ヨ', '500', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1041', '鎿嶄綔鍒犻櫎', '500', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1042', '鏃ュ織瀵煎嚭', '500', '4', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 103, 1, sysdate(), null, null, '');
+-- 鐧诲綍鏃ュ織鎸夐挳
+insert into sys_menu values('1043', '鐧诲綍鏌ヨ', '501', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1044', '鐧诲綍鍒犻櫎', '501', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1045', '鏃ュ織瀵煎嚭', '501', '3', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1050', '璐︽埛瑙i攣', '501', '4', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 103, 1, sysdate(), null, null, '');
+-- 鍦ㄧ嚎鐢ㄦ埛鎸夐挳
+insert into sys_menu values('1046', '鍦ㄧ嚎鏌ヨ', '109', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1047', '鎵归噺寮洪��', '109', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1048', '鍗曟潯寮洪��', '109', '3', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 103, 1, sysdate(), null, null, '');
+-- 浠g爜鐢熸垚鎸夐挳
+insert into sys_menu values('1055', '鐢熸垚鏌ヨ', '115', '1', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1056', '鐢熸垚淇敼', '115', '2', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1057', '鐢熸垚鍒犻櫎', '115', '3', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1058', '瀵煎叆浠g爜', '115', '2', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1059', '棰勮浠g爜', '115', '4', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1060', '鐢熸垚浠g爜', '115', '5', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 103, 1, sysdate(), null, null, '');
+-- oss鐩稿叧鎸夐挳
+insert into sys_menu values('1600', '鏂囦欢鏌ヨ', '118', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:oss:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1601', '鏂囦欢涓婁紶', '118', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:oss:upload', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1602', '鏂囦欢涓嬭浇', '118', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:oss:download', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1603', '鏂囦欢鍒犻櫎', '118', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:oss:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1620', '閰嶇疆鍒楄〃', '118', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:list', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1621', '閰嶇疆娣诲姞', '118', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1622', '閰嶇疆缂栬緫', '118', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1623', '閰嶇疆鍒犻櫎', '118', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:remove', '#', 103, 1, sysdate(), null, null, '');
+
+-- 绉熸埛绠$悊鐩稿叧鎸夐挳
+insert into sys_menu values ('1606', '绉熸埛鏌ヨ', '121', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenant:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values ('1607', '绉熸埛鏂板', '121', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenant:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values ('1608', '绉熸埛淇敼', '121', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenant:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values ('1609', '绉熸埛鍒犻櫎', '121', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenant:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values ('1610', '绉熸埛瀵煎嚭', '121', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenant:export', '#', 103, 1, sysdate(), null, null, '');
+-- 绉熸埛濂楅绠$悊鐩稿叧鎸夐挳
+insert into sys_menu values ('1611', '绉熸埛濂楅鏌ヨ', '122', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values ('1612', '绉熸埛濂楅鏂板', '122', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values ('1613', '绉熸埛濂楅淇敼', '122', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values ('1614', '绉熸埛濂楅鍒犻櫎', '122', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values ('1615', '绉熸埛濂楅瀵煎嚭', '122', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:export', '#', 103, 1, sysdate(), null, null, '');
+-- 瀹㈡埛绔鐞嗘寜閽�
+insert into sys_menu values('1061', '瀹㈡埛绔鐞嗘煡璇�', '123', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1062', '瀹㈡埛绔鐞嗘柊澧�', '123', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1063', '瀹㈡埛绔鐞嗕慨鏀�', '123', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1064', '瀹㈡埛绔鐞嗗垹闄�', '123', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1065', '瀹㈡埛绔鐞嗗鍑�', '123', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:export', '#', 103, 1, sysdate(), null, null, '');
+-- 娴嬭瘯鑿滃崟
+insert into sys_menu values('1500', '娴嬭瘯鍗曡〃', '5', '1', 'demo', 'demo/demo/index', '', 1, 0, 'C', '0', '0', 'demo:demo:list', '#', 103, 1, sysdate(), null, null, '娴嬭瘯鍗曡〃鑿滃崟');
+insert into sys_menu values('1501', '娴嬭瘯鍗曡〃鏌ヨ', '1500', '1', '#', '', '', 1, 0, 'F', '0', '0', 'demo:demo:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1502', '娴嬭瘯鍗曡〃鏂板', '1500', '2', '#', '', '', 1, 0, 'F', '0', '0', 'demo:demo:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1503', '娴嬭瘯鍗曡〃淇敼', '1500', '3', '#', '', '', 1, 0, 'F', '0', '0', 'demo:demo:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1504', '娴嬭瘯鍗曡〃鍒犻櫎', '1500', '4', '#', '', '', 1, 0, 'F', '0', '0', 'demo:demo:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1505', '娴嬭瘯鍗曡〃瀵煎嚭', '1500', '5', '#', '', '', 1, 0, 'F', '0', '0', 'demo:demo:export', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1506', '娴嬭瘯鏍戣〃', '5', '1', 'tree', 'demo/tree/index', '', 1, 0, 'C', '0', '0', 'demo:tree:list', '#', 103, 1, sysdate(), null, null, '娴嬭瘯鏍戣〃鑿滃崟');
+insert into sys_menu values('1507', '娴嬭瘯鏍戣〃鏌ヨ', '1506', '1', '#', '', '', 1, 0, 'F', '0', '0', 'demo:tree:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1508', '娴嬭瘯鏍戣〃鏂板', '1506', '2', '#', '', '', 1, 0, 'F', '0', '0', 'demo:tree:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1509', '娴嬭瘯鏍戣〃淇敼', '1506', '3', '#', '', '', 1, 0, 'F', '0', '0', 'demo:tree:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1510', '娴嬭瘯鏍戣〃鍒犻櫎', '1506', '4', '#', '', '', 1, 0, 'F', '0', '0', 'demo:tree:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1511', '娴嬭瘯鏍戣〃瀵煎嚭', '1506', '5', '#', '', '', 1, 0, 'F', '0', '0', 'demo:tree:export', '#', 103, 1, sysdate(), null, null, '');
+
+-- ----------------------------
+-- 6銆佺敤鎴峰拰瑙掕壊鍏宠仈琛� 鐢ㄦ埛N-1瑙掕壊
+-- ----------------------------
+create table sys_user_role (
+ user_id bigint(20) not null comment '鐢ㄦ埛ID',
+ role_id bigint(20) not null comment '瑙掕壊ID',
+ primary key(user_id, role_id)
+) engine=innodb comment = '鐢ㄦ埛鍜岃鑹插叧鑱旇〃';
+
+-- ----------------------------
+-- 鍒濆鍖�-鐢ㄦ埛鍜岃鑹插叧鑱旇〃鏁版嵁
+-- ----------------------------
+insert into sys_user_role values ('1', '1');
+insert into sys_user_role values ('3', '3');
+insert into sys_user_role values ('4', '4');
+
+-- ----------------------------
+-- 7銆佽鑹插拰鑿滃崟鍏宠仈琛� 瑙掕壊1-N鑿滃崟
+-- ----------------------------
+create table sys_role_menu (
+ role_id bigint(20) not null comment '瑙掕壊ID',
+ menu_id bigint(20) not null comment '鑿滃崟ID',
+ primary key(role_id, menu_id)
+) engine=innodb comment = '瑙掕壊鍜岃彍鍗曞叧鑱旇〃';
+
+-- ----------------------------
+-- 鍒濆鍖�-瑙掕壊鍜岃彍鍗曞叧鑱旇〃鏁版嵁
+-- ----------------------------
+insert into sys_role_menu values ('3', '1');
+insert into sys_role_menu values ('3', '5');
+insert into sys_role_menu values ('3', '100');
+insert into sys_role_menu values ('3', '101');
+insert into sys_role_menu values ('3', '102');
+insert into sys_role_menu values ('3', '103');
+insert into sys_role_menu values ('3', '104');
+insert into sys_role_menu values ('3', '105');
+insert into sys_role_menu values ('3', '106');
+insert into sys_role_menu values ('3', '107');
+insert into sys_role_menu values ('3', '108');
+insert into sys_role_menu values ('3', '118');
+insert into sys_role_menu values ('3', '123');
+insert into sys_role_menu values ('3', '130');
+insert into sys_role_menu values ('3', '131');
+insert into sys_role_menu values ('3', '132');
+insert into sys_role_menu values ('3', '133');
+insert into sys_role_menu values ('3', '500');
+insert into sys_role_menu values ('3', '501');
+insert into sys_role_menu values ('3', '1001');
+insert into sys_role_menu values ('3', '1002');
+insert into sys_role_menu values ('3', '1003');
+insert into sys_role_menu values ('3', '1004');
+insert into sys_role_menu values ('3', '1005');
+insert into sys_role_menu values ('3', '1006');
+insert into sys_role_menu values ('3', '1007');
+insert into sys_role_menu values ('3', '1008');
+insert into sys_role_menu values ('3', '1009');
+insert into sys_role_menu values ('3', '1010');
+insert into sys_role_menu values ('3', '1011');
+insert into sys_role_menu values ('3', '1012');
+insert into sys_role_menu values ('3', '1013');
+insert into sys_role_menu values ('3', '1014');
+insert into sys_role_menu values ('3', '1015');
+insert into sys_role_menu values ('3', '1016');
+insert into sys_role_menu values ('3', '1017');
+insert into sys_role_menu values ('3', '1018');
+insert into sys_role_menu values ('3', '1019');
+insert into sys_role_menu values ('3', '1020');
+insert into sys_role_menu values ('3', '1021');
+insert into sys_role_menu values ('3', '1022');
+insert into sys_role_menu values ('3', '1023');
+insert into sys_role_menu values ('3', '1024');
+insert into sys_role_menu values ('3', '1025');
+insert into sys_role_menu values ('3', '1026');
+insert into sys_role_menu values ('3', '1027');
+insert into sys_role_menu values ('3', '1028');
+insert into sys_role_menu values ('3', '1029');
+insert into sys_role_menu values ('3', '1030');
+insert into sys_role_menu values ('3', '1031');
+insert into sys_role_menu values ('3', '1032');
+insert into sys_role_menu values ('3', '1033');
+insert into sys_role_menu values ('3', '1034');
+insert into sys_role_menu values ('3', '1035');
+insert into sys_role_menu values ('3', '1036');
+insert into sys_role_menu values ('3', '1037');
+insert into sys_role_menu values ('3', '1038');
+insert into sys_role_menu values ('3', '1039');
+insert into sys_role_menu values ('3', '1040');
+insert into sys_role_menu values ('3', '1041');
+insert into sys_role_menu values ('3', '1042');
+insert into sys_role_menu values ('3', '1043');
+insert into sys_role_menu values ('3', '1044');
+insert into sys_role_menu values ('3', '1045');
+insert into sys_role_menu values ('3', '1050');
+insert into sys_role_menu values ('3', '1061');
+insert into sys_role_menu values ('3', '1062');
+insert into sys_role_menu values ('3', '1063');
+insert into sys_role_menu values ('3', '1064');
+insert into sys_role_menu values ('3', '1065');
+insert into sys_role_menu values ('3', '1500');
+insert into sys_role_menu values ('3', '1501');
+insert into sys_role_menu values ('3', '1502');
+insert into sys_role_menu values ('3', '1503');
+insert into sys_role_menu values ('3', '1504');
+insert into sys_role_menu values ('3', '1505');
+insert into sys_role_menu values ('3', '1506');
+insert into sys_role_menu values ('3', '1507');
+insert into sys_role_menu values ('3', '1508');
+insert into sys_role_menu values ('3', '1509');
+insert into sys_role_menu values ('3', '1510');
+insert into sys_role_menu values ('3', '1511');
+insert into sys_role_menu values ('3', '1600');
+insert into sys_role_menu values ('3', '1601');
+insert into sys_role_menu values ('3', '1602');
+insert into sys_role_menu values ('3', '1603');
+insert into sys_role_menu values ('3', '1620');
+insert into sys_role_menu values ('3', '1621');
+insert into sys_role_menu values ('3', '1622');
+insert into sys_role_menu values ('3', '1623');
+insert into sys_role_menu values ('3', '11616');
+insert into sys_role_menu values ('3', '11618');
+insert into sys_role_menu values ('3', '11619');
+insert into sys_role_menu values ('3', '11622');
+insert into sys_role_menu values ('3', '11623');
+insert into sys_role_menu values ('3', '11629');
+insert into sys_role_menu values ('3', '11632');
+insert into sys_role_menu values ('3', '11633');
+insert into sys_role_menu values ('3', '11638');
+insert into sys_role_menu values ('3', '11639');
+insert into sys_role_menu values ('3', '11640');
+insert into sys_role_menu values ('3', '11641');
+insert into sys_role_menu values ('3', '11642');
+insert into sys_role_menu values ('3', '11643');
+insert into sys_role_menu values ('3', '11701');
+insert into sys_role_menu values ('4', '5');
+insert into sys_role_menu values ('4', '1500');
+insert into sys_role_menu values ('4', '1501');
+insert into sys_role_menu values ('4', '1502');
+insert into sys_role_menu values ('4', '1503');
+insert into sys_role_menu values ('4', '1504');
+insert into sys_role_menu values ('4', '1505');
+insert into sys_role_menu values ('4', '1506');
+insert into sys_role_menu values ('4', '1507');
+insert into sys_role_menu values ('4', '1508');
+insert into sys_role_menu values ('4', '1509');
+insert into sys_role_menu values ('4', '1510');
+insert into sys_role_menu values ('4', '1511');
+
+-- ----------------------------
+-- 8銆佽鑹插拰閮ㄩ棬鍏宠仈琛� 瑙掕壊1-N閮ㄩ棬
+-- ----------------------------
+create table sys_role_dept (
+ role_id bigint(20) not null comment '瑙掕壊ID',
+ dept_id bigint(20) not null comment '閮ㄩ棬ID',
+ primary key(role_id, dept_id)
+) engine=innodb comment = '瑙掕壊鍜岄儴闂ㄥ叧鑱旇〃';
+
+-- ----------------------------
+-- 9銆佺敤鎴蜂笌宀椾綅鍏宠仈琛� 鐢ㄦ埛1-N宀椾綅
+-- ----------------------------
+create table sys_user_post
+(
+ user_id bigint(20) not null comment '鐢ㄦ埛ID',
+ post_id bigint(20) not null comment '宀椾綅ID',
+ primary key (user_id, post_id)
+) engine=innodb comment = '鐢ㄦ埛涓庡矖浣嶅叧鑱旇〃';
+
+-- ----------------------------
+-- 鍒濆鍖�-鐢ㄦ埛涓庡矖浣嶅叧鑱旇〃鏁版嵁
+-- ----------------------------
+insert into sys_user_post values ('1', '1');
+
+-- ----------------------------
+-- 10銆佹搷浣滄棩蹇楄褰�
+-- ----------------------------
+create table sys_oper_log (
+ oper_id bigint(20) not null comment '鏃ュ織涓婚敭',
+ tenant_id varchar(20) default '000000' comment '绉熸埛缂栧彿',
+ title varchar(50) default '' comment '妯″潡鏍囬',
+ business_type int(2) default 0 comment '涓氬姟绫诲瀷锛�0鍏跺畠 1鏂板 2淇敼 3鍒犻櫎锛�',
+ method varchar(100) default '' comment '鏂规硶鍚嶇О',
+ request_method varchar(10) default '' comment '璇锋眰鏂瑰紡',
+ operator_type int(1) default 0 comment '鎿嶄綔绫诲埆锛�0鍏跺畠 1鍚庡彴鐢ㄦ埛 2鎵嬫満绔敤鎴凤級',
+ oper_name varchar(50) default '' comment '鎿嶄綔浜哄憳',
+ dept_name varchar(50) default '' comment '閮ㄩ棬鍚嶇О',
+ oper_url varchar(255) default '' comment '璇锋眰URL',
+ oper_ip varchar(128) default '' comment '涓绘満鍦板潃',
+ oper_location varchar(255) default '' comment '鎿嶄綔鍦扮偣',
+ oper_param varchar(4000) default '' comment '璇锋眰鍙傛暟',
+ json_result varchar(4000) default '' comment '杩斿洖鍙傛暟',
+ status int(1) default 0 comment '鎿嶄綔鐘舵�侊紙0姝e父 1寮傚父锛�',
+ error_msg varchar(4000) default '' comment '閿欒娑堟伅',
+ oper_time datetime comment '鎿嶄綔鏃堕棿',
+ cost_time bigint(20) default 0 comment '娑堣�楁椂闂�',
+ primary key (oper_id),
+ key idx_sys_oper_log_bt (business_type),
+ key idx_sys_oper_log_s (status),
+ key idx_sys_oper_log_ot (oper_time)
+) engine=innodb comment = '鎿嶄綔鏃ュ織璁板綍';
+
+
+-- ----------------------------
+-- 11銆佸瓧鍏哥被鍨嬭〃
+-- ----------------------------
+create table sys_dict_type
+(
+ dict_id bigint(20) not null comment '瀛楀吀涓婚敭',
+ tenant_id varchar(20) default '000000' comment '绉熸埛缂栧彿',
+ dict_name varchar(100) default '' comment '瀛楀吀鍚嶇О',
+ dict_type varchar(100) default '' comment '瀛楀吀绫诲瀷',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ remark varchar(500) default null comment '澶囨敞',
+ primary key (dict_id),
+ unique (tenant_id, dict_type)
+) engine=innodb comment = '瀛楀吀绫诲瀷琛�';
+
+insert into sys_dict_type values(1, '000000', '鐢ㄦ埛鎬у埆', 'sys_user_sex', 103, 1, sysdate(), null, null, '鐢ㄦ埛鎬у埆鍒楄〃');
+insert into sys_dict_type values(2, '000000', '鑿滃崟鐘舵��', 'sys_show_hide', 103, 1, sysdate(), null, null, '鑿滃崟鐘舵�佸垪琛�');
+insert into sys_dict_type values(3, '000000', '绯荤粺寮�鍏�', 'sys_normal_disable', 103, 1, sysdate(), null, null, '绯荤粺寮�鍏冲垪琛�');
+insert into sys_dict_type values(6, '000000', '绯荤粺鏄惁', 'sys_yes_no', 103, 1, sysdate(), null, null, '绯荤粺鏄惁鍒楄〃');
+insert into sys_dict_type values(7, '000000', '閫氱煡绫诲瀷', 'sys_notice_type', 103, 1, sysdate(), null, null, '閫氱煡绫诲瀷鍒楄〃');
+insert into sys_dict_type values(8, '000000', '閫氱煡鐘舵��', 'sys_notice_status', 103, 1, sysdate(), null, null, '閫氱煡鐘舵�佸垪琛�');
+insert into sys_dict_type values(9, '000000', '鎿嶄綔绫诲瀷', 'sys_oper_type', 103, 1, sysdate(), null, null, '鎿嶄綔绫诲瀷鍒楄〃');
+insert into sys_dict_type values(10, '000000', '绯荤粺鐘舵��', 'sys_common_status', 103, 1, sysdate(), null, null, '鐧诲綍鐘舵�佸垪琛�');
+insert into sys_dict_type values(11, '000000', '鎺堟潈绫诲瀷', 'sys_grant_type', 103, 1, sysdate(), null, null, '璁よ瘉鎺堟潈绫诲瀷');
+insert into sys_dict_type values(12, '000000', '璁惧绫诲瀷', 'sys_device_type', 103, 1, sysdate(), null, null, '瀹㈡埛绔澶囩被鍨�');
+
+
+-- ----------------------------
+-- 12銆佸瓧鍏告暟鎹〃
+-- ----------------------------
+create table sys_dict_data
+(
+ dict_code bigint(20) not null comment '瀛楀吀缂栫爜',
+ tenant_id varchar(20) default '000000' comment '绉熸埛缂栧彿',
+ dict_sort int(4) default 0 comment '瀛楀吀鎺掑簭',
+ dict_label varchar(100) default '' comment '瀛楀吀鏍囩',
+ dict_value varchar(100) default '' comment '瀛楀吀閿��',
+ dict_type varchar(100) default '' comment '瀛楀吀绫诲瀷',
+ css_class varchar(100) default null comment '鏍峰紡灞炴�э紙鍏朵粬鏍峰紡鎵╁睍锛�',
+ list_class varchar(100) default null comment '琛ㄦ牸鍥炴樉鏍峰紡',
+ is_default char(1) default 'N' comment '鏄惁榛樿锛圷鏄� N鍚︼級',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ remark varchar(500) default null comment '澶囨敞',
+ primary key (dict_code)
+) engine=innodb comment = '瀛楀吀鏁版嵁琛�';
+
+insert into sys_dict_data values(1, '000000', 1, '鐢�', '0', 'sys_user_sex', '', '', 'Y', 103, 1, sysdate(), null, null, '鎬у埆鐢�');
+insert into sys_dict_data values(2, '000000', 2, '濂�', '1', 'sys_user_sex', '', '', 'N', 103, 1, sysdate(), null, null, '鎬у埆濂�');
+insert into sys_dict_data values(3, '000000', 3, '鏈煡', '2', 'sys_user_sex', '', '', 'N', 103, 1, sysdate(), null, null, '鎬у埆鏈煡');
+insert into sys_dict_data values(4, '000000', 1, '鏄剧ず', '0', 'sys_show_hide', '', 'primary', 'Y', 103, 1, sysdate(), null, null, '鏄剧ず鑿滃崟');
+insert into sys_dict_data values(5, '000000', 2, '闅愯棌', '1', 'sys_show_hide', '', 'danger', 'N', 103, 1, sysdate(), null, null, '闅愯棌鑿滃崟');
+insert into sys_dict_data values(6, '000000', 1, '姝e父', '0', 'sys_normal_disable', '', 'primary', 'Y', 103, 1, sysdate(), null, null, '姝e父鐘舵��');
+insert into sys_dict_data values(7, '000000', 2, '鍋滅敤', '1', 'sys_normal_disable', '', 'danger', 'N', 103, 1, sysdate(), null, null, '鍋滅敤鐘舵��');
+insert into sys_dict_data values(12, '000000', 1, '鏄�', 'Y', 'sys_yes_no', '', 'primary', 'Y', 103, 1, sysdate(), null, null, '绯荤粺榛樿鏄�');
+insert into sys_dict_data values(13, '000000', 2, '鍚�', 'N', 'sys_yes_no', '', 'danger', 'N', 103, 1, sysdate(), null, null, '绯荤粺榛樿鍚�');
+insert into sys_dict_data values(14, '000000', 1, '閫氱煡', '1', 'sys_notice_type', '', 'warning', 'Y', 103, 1, sysdate(), null, null, '閫氱煡');
+insert into sys_dict_data values(15, '000000', 2, '鍏憡', '2', 'sys_notice_type', '', 'success', 'N', 103, 1, sysdate(), null, null, '鍏憡');
+insert into sys_dict_data values(16, '000000', 1, '姝e父', '0', 'sys_notice_status', '', 'primary', 'Y', 103, 1, sysdate(), null, null, '姝e父鐘舵��');
+insert into sys_dict_data values(17, '000000', 2, '鍏抽棴', '1', 'sys_notice_status', '', 'danger', 'N', 103, 1, sysdate(), null, null, '鍏抽棴鐘舵��');
+insert into sys_dict_data values(29, '000000', 99, '鍏朵粬', '0', 'sys_oper_type', '', 'info', 'N', 103, 1, sysdate(), null, null, '鍏朵粬鎿嶄綔');
+insert into sys_dict_data values(18, '000000', 1, '鏂板', '1', 'sys_oper_type', '', 'info', 'N', 103, 1, sysdate(), null, null, '鏂板鎿嶄綔');
+insert into sys_dict_data values(19, '000000', 2, '淇敼', '2', 'sys_oper_type', '', 'info', 'N', 103, 1, sysdate(), null, null, '淇敼鎿嶄綔');
+insert into sys_dict_data values(20, '000000', 3, '鍒犻櫎', '3', 'sys_oper_type', '', 'danger', 'N', 103, 1, sysdate(), null, null, '鍒犻櫎鎿嶄綔');
+insert into sys_dict_data values(21, '000000', 4, '鎺堟潈', '4', 'sys_oper_type', '', 'primary', 'N', 103, 1, sysdate(), null, null, '鎺堟潈鎿嶄綔');
+insert into sys_dict_data values(22, '000000', 5, '瀵煎嚭', '5', 'sys_oper_type', '', 'warning', 'N', 103, 1, sysdate(), null, null, '瀵煎嚭鎿嶄綔');
+insert into sys_dict_data values(23, '000000', 6, '瀵煎叆', '6', 'sys_oper_type', '', 'warning', 'N', 103, 1, sysdate(), null, null, '瀵煎叆鎿嶄綔');
+insert into sys_dict_data values(24, '000000', 7, '寮洪��', '7', 'sys_oper_type', '', 'danger', 'N', 103, 1, sysdate(), null, null, '寮洪��鎿嶄綔');
+insert into sys_dict_data values(25, '000000', 8, '鐢熸垚浠g爜', '8', 'sys_oper_type', '', 'warning', 'N', 103, 1, sysdate(), null, null, '鐢熸垚鎿嶄綔');
+insert into sys_dict_data values(26, '000000', 9, '娓呯┖鏁版嵁', '9', 'sys_oper_type', '', 'danger', 'N', 103, 1, sysdate(), null, null, '娓呯┖鎿嶄綔');
+insert into sys_dict_data values(27, '000000', 1, '鎴愬姛', '0', 'sys_common_status', '', 'primary', 'N', 103, 1, sysdate(), null, null, '姝e父鐘舵��');
+insert into sys_dict_data values(28, '000000', 2, '澶辫触', '1', 'sys_common_status', '', 'danger', 'N', 103, 1, sysdate(), null, null, '鍋滅敤鐘舵��');
+insert into sys_dict_data values(30, '000000', 0, '瀵嗙爜璁よ瘉', 'password', 'sys_grant_type', 'el-check-tag', 'default', 'N', 103, 1, sysdate(), null, null, '瀵嗙爜璁よ瘉');
+insert into sys_dict_data values(31, '000000', 0, '鐭俊璁よ瘉', 'sms', 'sys_grant_type', 'el-check-tag', 'default', 'N', 103, 1, sysdate(), null, null, '鐭俊璁よ瘉');
+insert into sys_dict_data values(32, '000000', 0, '閭欢璁よ瘉', 'email', 'sys_grant_type', 'el-check-tag', 'default', 'N', 103, 1, sysdate(), null, null, '閭欢璁よ瘉');
+insert into sys_dict_data values(33, '000000', 0, '灏忕▼搴忚璇�', 'xcx', 'sys_grant_type', 'el-check-tag', 'default', 'N', 103, 1, sysdate(), null, null, '灏忕▼搴忚璇�');
+insert into sys_dict_data values(34, '000000', 0, '涓夋柟鐧诲綍璁よ瘉', 'social', 'sys_grant_type', 'el-check-tag', 'default', 'N', 103, 1, sysdate(), null, null, '涓夋柟鐧诲綍璁よ瘉');
+insert into sys_dict_data values(35, '000000', 0, 'PC', 'pc', 'sys_device_type', '', 'default', 'N', 103, 1, sysdate(), null, null, 'PC');
+insert into sys_dict_data values(36, '000000', 0, '瀹夊崜', 'android', 'sys_device_type', '', 'default', 'N', 103, 1, sysdate(), null, null, '瀹夊崜');
+insert into sys_dict_data values(37, '000000', 0, 'iOS', 'ios', 'sys_device_type', '', 'default', 'N', 103, 1, sysdate(), null, null, 'iOS');
+insert into sys_dict_data values(38, '000000', 0, '灏忕▼搴�', 'xcx', 'sys_device_type', '', 'default', 'N', 103, 1, sysdate(), null, null, '灏忕▼搴�');
+
+
+-- ----------------------------
+-- 13銆佸弬鏁伴厤缃〃
+-- ----------------------------
+create table sys_config (
+ config_id bigint(20) not null comment '鍙傛暟涓婚敭',
+ tenant_id varchar(20) default '000000' comment '绉熸埛缂栧彿',
+ config_name varchar(100) default '' comment '鍙傛暟鍚嶇О',
+ config_key varchar(100) default '' comment '鍙傛暟閿悕',
+ config_value varchar(500) default '' comment '鍙傛暟閿��',
+ config_type char(1) default 'N' comment '绯荤粺鍐呯疆锛圷鏄� N鍚︼級',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ remark varchar(500) default null comment '澶囨敞',
+ primary key (config_id)
+) engine=innodb comment = '鍙傛暟閰嶇疆琛�';
+
+insert into sys_config values(1, '000000', '涓绘鏋堕〉-榛樿鐨偆鏍峰紡鍚嶇О', 'sys.index.skinName', 'skin-blue', 'Y', 103, 1, sysdate(), null, null, '钃濊壊 skin-blue銆佺豢鑹� skin-green銆佺传鑹� skin-purple銆佺孩鑹� skin-red銆侀粍鑹� skin-yellow' );
+insert into sys_config values(2, '000000', '鐢ㄦ埛绠$悊-璐﹀彿鍒濆瀵嗙爜', 'sys.user.initPassword', '123456', 'Y', 103, 1, sysdate(), null, null, '鍒濆鍖栧瘑鐮� 123456' );
+insert into sys_config values(3, '000000', '涓绘鏋堕〉-渚ц竟鏍忎富棰�', 'sys.index.sideTheme', 'theme-dark', 'Y', 103, 1, sysdate(), null, null, '娣辫壊涓婚theme-dark锛屾祬鑹蹭富棰榯heme-light' );
+insert into sys_config values(5, '000000', '璐﹀彿鑷姪-鏄惁寮�鍚敤鎴锋敞鍐屽姛鑳�', 'sys.account.registerUser', 'false', 'Y', 103, 1, sysdate(), null, null, '鏄惁寮�鍚敞鍐岀敤鎴峰姛鑳斤紙true寮�鍚紝false鍏抽棴锛�');
+insert into sys_config values(11, '000000', 'OSS棰勮鍒楄〃璧勬簮寮�鍏�', 'sys.oss.previewListResource', 'true', 'Y', 103, 1, sysdate(), null, null, 'true:寮�鍚�, false:鍏抽棴');
+
+
+-- ----------------------------
+-- 14銆佺郴缁熻闂褰�
+-- ----------------------------
+create table sys_logininfor (
+ info_id bigint(20) not null comment '璁块棶ID',
+ tenant_id varchar(20) default '000000' comment '绉熸埛缂栧彿',
+ user_name varchar(50) default '' comment '鐢ㄦ埛璐﹀彿',
+ client_key varchar(32) default '' comment '瀹㈡埛绔�',
+ device_type varchar(32) default '' comment '璁惧绫诲瀷',
+ ipaddr varchar(128) default '' comment '鐧诲綍IP鍦板潃',
+ login_location varchar(255) default '' comment '鐧诲綍鍦扮偣',
+ browser varchar(50) default '' comment '娴忚鍣ㄧ被鍨�',
+ os varchar(50) default '' comment '鎿嶄綔绯荤粺',
+ status char(1) default '0' comment '鐧诲綍鐘舵�侊紙0鎴愬姛 1澶辫触锛�',
+ msg varchar(255) default '' comment '鎻愮ず娑堟伅',
+ login_time datetime comment '璁块棶鏃堕棿',
+ primary key (info_id),
+ key idx_sys_logininfor_s (status),
+ key idx_sys_logininfor_lt (login_time)
+) engine=innodb comment = '绯荤粺璁块棶璁板綍';
+
+
+-- ----------------------------
+-- 17銆侀�氱煡鍏憡琛�
+-- ----------------------------
+create table sys_notice (
+ notice_id bigint(20) not null comment '鍏憡ID',
+ tenant_id varchar(20) default '000000' comment '绉熸埛缂栧彿',
+ notice_title varchar(50) not null comment '鍏憡鏍囬',
+ notice_type char(1) not null comment '鍏憡绫诲瀷锛�1閫氱煡 2鍏憡锛�',
+ notice_content longblob default null comment '鍏憡鍐呭',
+ status char(1) default '0' comment '鍏憡鐘舵�侊紙0姝e父 1鍏抽棴锛�',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ remark varchar(255) default null comment '澶囨敞',
+ primary key (notice_id)
+) engine=innodb comment = '閫氱煡鍏憡琛�';
+
+-- ----------------------------
+-- 鍒濆鍖�-鍏憡淇℃伅琛ㄦ暟鎹�
+-- ----------------------------
+insert into sys_notice values('1', '000000', '娓╅Θ鎻愰啋锛�2018-07-01 鏂扮増鏈彂甯冨暒', '2', '鏂扮増鏈唴瀹�', '0', 103, 1, sysdate(), null, null, '绠$悊鍛�');
+insert into sys_notice values('2', '000000', '缁存姢閫氱煡锛�2018-07-01 绯荤粺鍑屾櫒缁存姢', '1', '缁存姢鍐呭', '0', 103, 1, sysdate(), null, null, '绠$悊鍛�');
+
+
+-- ----------------------------
+-- 18銆佷唬鐮佺敓鎴愪笟鍔¤〃
+-- ----------------------------
+create table gen_table (
+ table_id bigint(20) not null comment '缂栧彿',
+ data_name varchar(200) default '' comment '鏁版嵁婧愬悕绉�',
+ table_name varchar(200) default '' comment '琛ㄥ悕绉�',
+ table_comment varchar(500) default '' comment '琛ㄦ弿杩�',
+ sub_table_name varchar(64) default null comment '鍏宠仈瀛愯〃鐨勮〃鍚�',
+ sub_table_fk_name varchar(64) default null comment '瀛愯〃鍏宠仈鐨勫閿悕',
+ class_name varchar(100) default '' comment '瀹炰綋绫诲悕绉�',
+ tpl_category varchar(200) default 'crud' comment '浣跨敤鐨勬ā鏉匡紙crud鍗曡〃鎿嶄綔 tree鏍戣〃鎿嶄綔锛�',
+ package_name varchar(100) comment '鐢熸垚鍖呰矾寰�',
+ module_name varchar(30) comment '鐢熸垚妯″潡鍚�',
+ business_name varchar(30) comment '鐢熸垚涓氬姟鍚�',
+ function_name varchar(50) comment '鐢熸垚鍔熻兘鍚�',
+ function_author varchar(50) comment '鐢熸垚鍔熻兘浣滆��',
+ gen_type char(1) default '0' comment '鐢熸垚浠g爜鏂瑰紡锛�0zip鍘嬬缉鍖� 1鑷畾涔夎矾寰勶級',
+ gen_path varchar(200) default '/' comment '鐢熸垚璺緞锛堜笉濉粯璁ら」鐩矾寰勶級',
+ options varchar(1000) comment '鍏跺畠鐢熸垚閫夐」',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ remark varchar(500) default null comment '澶囨敞',
+ primary key (table_id)
+) engine=innodb comment = '浠g爜鐢熸垚涓氬姟琛�';
+
+
+-- ----------------------------
+-- 19銆佷唬鐮佺敓鎴愪笟鍔¤〃瀛楁
+-- ----------------------------
+create table gen_table_column (
+ column_id bigint(20) not null comment '缂栧彿',
+ table_id bigint(20) comment '褰掑睘琛ㄧ紪鍙�',
+ column_name varchar(200) comment '鍒楀悕绉�',
+ column_comment varchar(500) comment '鍒楁弿杩�',
+ column_type varchar(100) comment '鍒楃被鍨�',
+ java_type varchar(500) comment 'JAVA绫诲瀷',
+ java_field varchar(200) comment 'JAVA瀛楁鍚�',
+ is_pk char(1) comment '鏄惁涓婚敭锛�1鏄級',
+ is_increment char(1) comment '鏄惁鑷锛�1鏄級',
+ is_required char(1) comment '鏄惁蹇呭~锛�1鏄級',
+ is_insert char(1) comment '鏄惁涓烘彃鍏ュ瓧娈碉紙1鏄級',
+ is_edit char(1) comment '鏄惁缂栬緫瀛楁锛�1鏄級',
+ is_list char(1) comment '鏄惁鍒楄〃瀛楁锛�1鏄級',
+ is_query char(1) comment '鏄惁鏌ヨ瀛楁锛�1鏄級',
+ query_type varchar(200) default 'EQ' comment '鏌ヨ鏂瑰紡锛堢瓑浜庛�佷笉绛変簬銆佸ぇ浜庛�佸皬浜庛�佽寖鍥达級',
+ html_type varchar(200) comment '鏄剧ず绫诲瀷锛堟枃鏈銆佹枃鏈煙銆佷笅鎷夋銆佸閫夋銆佸崟閫夋銆佹棩鏈熸帶浠讹級',
+ dict_type varchar(200) default '' comment '瀛楀吀绫诲瀷',
+ sort int comment '鎺掑簭',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ primary key (column_id)
+) engine=innodb comment = '浠g爜鐢熸垚涓氬姟琛ㄥ瓧娈�';
+
+-- ----------------------------
+-- OSS瀵硅薄瀛樺偍琛�
+-- ----------------------------
+create table sys_oss (
+ oss_id bigint(20) not null comment '瀵硅薄瀛樺偍涓婚敭',
+ tenant_id varchar(20) default '000000' comment '绉熸埛缂栧彿',
+ file_name varchar(255) not null default '' comment '鏂囦欢鍚�',
+ original_name varchar(255) not null default '' comment '鍘熷悕',
+ file_suffix varchar(10) not null default '' comment '鏂囦欢鍚庣紑鍚�',
+ url varchar(500) not null comment 'URL鍦板潃',
+ ext1 text default null comment '鎵╁睍瀛楁',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_time datetime default null comment '鍒涘缓鏃堕棿',
+ create_by bigint(20) default null comment '涓婁紶浜�',
+ update_time datetime default null comment '鏇存柊鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊浜�',
+ service varchar(20) not null default 'minio' comment '鏈嶅姟鍟�',
+ primary key (oss_id)
+) engine=innodb comment ='OSS瀵硅薄瀛樺偍琛�';
+
+-- ----------------------------
+-- OSS瀵硅薄瀛樺偍鍔ㄦ�侀厤缃〃
+-- ----------------------------
+create table sys_oss_config (
+ oss_config_id bigint(20) not null comment '涓婚敭',
+ tenant_id varchar(20) default '000000'comment '绉熸埛缂栧彿',
+ config_key varchar(20) not null default '' comment '閰嶇疆key',
+ access_key varchar(255) default '' comment 'accessKey',
+ secret_key varchar(255) default '' comment '绉橀挜',
+ bucket_name varchar(255) default '' comment '妗跺悕绉�',
+ prefix varchar(255) default '' comment '鍓嶇紑',
+ endpoint varchar(255) default '' comment '璁块棶绔欑偣',
+ domain varchar(255) default '' comment '鑷畾涔夊煙鍚�',
+ is_https char(1) default 'N' comment '鏄惁https锛圷=鏄�,N=鍚︼級',
+ region varchar(255) default '' comment '鍩�',
+ access_policy char(1) not null default '1' comment '妗舵潈闄愮被鍨�(0=private 1=public 2=custom)',
+ status char(1) default '1' comment '鏄惁榛樿锛�0=鏄�,1=鍚︼級',
+ ext1 varchar(255) default '' comment '鎵╁睍瀛楁',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime default null comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime default null comment '鏇存柊鏃堕棿',
+ remark varchar(500) default null comment '澶囨敞',
+ primary key (oss_config_id)
+) engine=innodb comment='瀵硅薄瀛樺偍閰嶇疆琛�';
+
+insert into sys_oss_config values (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '','N', '', '1' ,'0', '', 103, 1, sysdate(), 1, sysdate(), null);
+insert into sys_oss_config values (2, '000000', 'qiniu', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 's3-cn-north-1.qiniucs.com', '','N', '', '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
+insert into sys_oss_config values (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 'oss-cn-beijing.aliyuncs.com', '','N', '', '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
+insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi-1240000000', '', 'cos.ap-beijing.myqcloud.com', '','N', 'ap-beijing', '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
+insert into sys_oss_config values (5, '000000', 'image', 'ruoyi', 'ruoyi123', 'ruoyi', 'image', '127.0.0.1:9000', '','N', '', '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
+
+-- ----------------------------
+-- 绯荤粺鎺堟潈琛�
+-- ----------------------------
+create table sys_client (
+ id bigint(20) not null comment 'id',
+ client_id varchar(64) default null comment '瀹㈡埛绔痠d',
+ client_key varchar(32) default null comment '瀹㈡埛绔痥ey',
+ client_secret varchar(255) default null comment '瀹㈡埛绔閽�',
+ grant_type varchar(255) default null comment '鎺堟潈绫诲瀷',
+ device_type varchar(32) default null comment '璁惧绫诲瀷',
+ active_timeout int(11) default 1800 comment 'token娲昏穬瓒呮椂鏃堕棿',
+ timeout int(11) default 604800 comment 'token鍥哄畾瓒呮椂',
+ status char(1) default '0' comment '鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ del_flag char(1) default '0' comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime default null comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime default null comment '鏇存柊鏃堕棿',
+ primary key (id)
+) engine=innodb comment='绯荤粺鎺堟潈琛�';
+
+insert into sys_client values (1, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 103, 1, sysdate(), 1, sysdate());
+insert into sys_client values (2, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'android', 1800, 604800, 0, 0, 103, 1, sysdate(), 1, sysdate());
+
+
+CREATE TABLE test_demo
+(
+ id bigint(0) NOT NULL COMMENT '涓婚敭',
+ tenant_id varchar(20) NULL DEFAULT '000000' COMMENT '绉熸埛缂栧彿',
+ dept_id bigint(0) NULL DEFAULT NULL COMMENT '閮ㄩ棬id',
+ user_id bigint(0) NULL DEFAULT NULL COMMENT '鐢ㄦ埛id',
+ order_num int(0) NULL DEFAULT 0 COMMENT '鎺掑簭鍙�',
+ test_key varchar(255) NULL DEFAULT NULL COMMENT 'key閿�',
+ value varchar(255) NULL DEFAULT NULL COMMENT '鍊�',
+ version int(0) NULL DEFAULT 0 COMMENT '鐗堟湰',
+ create_dept bigint(0) NULL DEFAULT NULL COMMENT '鍒涘缓閮ㄩ棬',
+ create_time datetime(0) NULL DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+ create_by bigint(0) NULL DEFAULT NULL COMMENT '鍒涘缓浜�',
+ update_time datetime(0) NULL DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+ update_by bigint(0) NULL DEFAULT NULL COMMENT '鏇存柊浜�',
+ del_flag int(0) NULL DEFAULT 0 COMMENT '鍒犻櫎鏍囧織',
+ PRIMARY KEY (id) USING BTREE
+) ENGINE = InnoDB COMMENT = '娴嬭瘯鍗曡〃';
+
+CREATE TABLE test_tree
+(
+ id bigint(0) NOT NULL COMMENT '涓婚敭',
+ tenant_id varchar(20) NULL DEFAULT '000000' COMMENT '绉熸埛缂栧彿',
+ parent_id bigint(0) NULL DEFAULT 0 COMMENT '鐖秈d',
+ dept_id bigint(0) NULL DEFAULT NULL COMMENT '閮ㄩ棬id',
+ user_id bigint(0) NULL DEFAULT NULL COMMENT '鐢ㄦ埛id',
+ tree_name varchar(255) NULL DEFAULT NULL COMMENT '鍊�',
+ version int(0) NULL DEFAULT 0 COMMENT '鐗堟湰',
+ create_dept bigint(0) NULL DEFAULT NULL COMMENT '鍒涘缓閮ㄩ棬',
+ create_time datetime(0) NULL DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+ create_by bigint(0) NULL DEFAULT NULL COMMENT '鍒涘缓浜�',
+ update_time datetime(0) NULL DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+ update_by bigint(0) NULL DEFAULT NULL COMMENT '鏇存柊浜�',
+ del_flag int(0) NULL DEFAULT 0 COMMENT '鍒犻櫎鏍囧織',
+ PRIMARY KEY (id) USING BTREE
+) ENGINE = InnoDB COMMENT = '娴嬭瘯鏍戣〃';
+
+INSERT INTO test_demo VALUES (1, '000000', 102, 4, 1, '娴嬭瘯鏁版嵁鏉冮檺', '娴嬭瘯', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (2, '000000', 102, 3, 2, '瀛愯妭鐐�1', '111', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (3, '000000', 102, 3, 3, '瀛愯妭鐐�2', '222', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (4, '000000', 108, 4, 4, '娴嬭瘯鏁版嵁', 'demo', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (5, '000000', 108, 3, 13, '瀛愯妭鐐�11', '1111', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (6, '000000', 108, 3, 12, '瀛愯妭鐐�22', '2222', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (7, '000000', 108, 3, 11, '瀛愯妭鐐�33', '3333', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (8, '000000', 108, 3, 10, '瀛愯妭鐐�44', '4444', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (9, '000000', 108, 3, 9, '瀛愯妭鐐�55', '5555', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (10, '000000', 108, 3, 8, '瀛愯妭鐐�66', '6666', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (11, '000000', 108, 3, 7, '瀛愯妭鐐�77', '7777', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (12, '000000', 108, 3, 6, '瀛愯妭鐐�88', '8888', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_demo VALUES (13, '000000', 108, 3, 5, '瀛愯妭鐐�99', '9999', 0, 103, sysdate(), 1, NULL, NULL, 0);
+
+INSERT INTO test_tree VALUES (1, '000000', 0, 102, 4, '娴嬭瘯鏁版嵁鏉冮檺', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (2, '000000', 1, 102, 3, '瀛愯妭鐐�1', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (3, '000000', 2, 102, 3, '瀛愯妭鐐�2', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (4, '000000', 0, 108, 4, '娴嬭瘯鏍�1', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (5, '000000', 4, 108, 3, '瀛愯妭鐐�11', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (6, '000000', 4, 108, 3, '瀛愯妭鐐�22', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (7, '000000', 4, 108, 3, '瀛愯妭鐐�33', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (8, '000000', 5, 108, 3, '瀛愯妭鐐�44', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (9, '000000', 6, 108, 3, '瀛愯妭鐐�55', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (10, '000000', 7, 108, 3, '瀛愯妭鐐�66', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (11, '000000', 7, 108, 3, '瀛愯妭鐐�77', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (12, '000000', 10, 108, 3, '瀛愯妭鐐�88', 0, 103, sysdate(), 1, NULL, NULL, 0);
+INSERT INTO test_tree VALUES (13, '000000', 10, 108, 3, '瀛愯妭鐐�99', 0, 103, sysdate(), 1, NULL, NULL, 0);
diff --git a/RuoYi-Vue-Plus/script/sql/ry_workflow.sql b/RuoYi-Vue-Plus/script/sql/ry_workflow.sql
new file mode 100755
index 0000000..c911a5b
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/ry_workflow.sql
@@ -0,0 +1,319 @@
+-- ----------------------------
+-- 0銆亀arm-flow-all.sql锛屽湴鍧�锛歨ttps://gitee.com/dromara/warm-flow/blob/master/sql/mysql/warm-flow-all.sql
+-- ----------------------------
+CREATE TABLE `flow_definition`
+(
+ `id` bigint NOT NULL COMMENT '涓婚敭id',
+ `flow_code` varchar(40) NOT NULL COMMENT '娴佺▼缂栫爜',
+ `flow_name` varchar(100) NOT NULL COMMENT '娴佺▼鍚嶇О',
+ `model_value` varchar(40) NOT NULL DEFAULT 'CLASSICS' COMMENT '璁捐鍣ㄦā鍨嬶紙CLASSICS缁忓吀妯″瀷 MIMIC浠块拤閽夋ā鍨嬶級',
+ `category` varchar(100) DEFAULT NULL COMMENT '娴佺▼绫诲埆',
+ `version` varchar(20) NOT NULL COMMENT '娴佺▼鐗堟湰',
+ `is_publish` tinyint(1) NOT NULL DEFAULT '0' COMMENT '鏄惁鍙戝竷锛�0鏈彂甯� 1宸插彂甯� 9澶辨晥锛�',
+ `form_custom` char(1) DEFAULT 'N' COMMENT '瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級',
+ `form_path` varchar(100) DEFAULT NULL COMMENT '瀹℃壒琛ㄥ崟璺緞',
+ `activity_status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '娴佺▼婵�娲荤姸鎬侊紙0鎸傝捣 1婵�娲伙級',
+ `listener_type` varchar(100) DEFAULT NULL COMMENT '鐩戝惉鍣ㄧ被鍨�',
+ `listener_path` varchar(400) DEFAULT NULL COMMENT '鐩戝惉鍣ㄨ矾寰�',
+ `ext` varchar(500) DEFAULT NULL COMMENT '涓氬姟璇︽儏 瀛樹笟鍔¤〃瀵硅薄json瀛楃涓�',
+ `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+ `create_by` varchar(64) DEFAULT '' COMMENT '鍒涘缓浜�',
+ `update_time` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+ `update_by` varchar(64) DEFAULT '' COMMENT '鏇存柊浜�',
+ `del_flag` char(1) DEFAULT '0' COMMENT '鍒犻櫎鏍囧織',
+ `tenant_id` varchar(40) DEFAULT NULL COMMENT '绉熸埛id',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB COMMENT ='娴佺▼瀹氫箟琛�';
+
+CREATE TABLE `flow_node`
+(
+ `id` bigint NOT NULL COMMENT '涓婚敭id',
+ `node_type` tinyint(1) NOT NULL COMMENT '鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�',
+ `definition_id` bigint NOT NULL COMMENT '娴佺▼瀹氫箟id',
+ `node_code` varchar(100) NOT NULL COMMENT '娴佺▼鑺傜偣缂栫爜',
+ `node_name` varchar(100) DEFAULT NULL COMMENT '娴佺▼鑺傜偣鍚嶇О',
+ `permission_flag` varchar(200) DEFAULT NULL COMMENT '鏉冮檺鏍囪瘑锛堟潈闄愮被鍨�:鏉冮檺鏍囪瘑锛屽彲浠ュ涓紝鐢ˊ@闅斿紑)',
+ `node_ratio` varchar(200) DEFAULT NULL COMMENT '娴佺▼绛剧讲姣斾緥鍊�',
+ `coordinate` varchar(100) DEFAULT NULL COMMENT '鍧愭爣',
+ `any_node_skip` varchar(100) DEFAULT NULL COMMENT '浠绘剰缁撶偣璺宠浆',
+ `listener_type` varchar(100) DEFAULT NULL COMMENT '鐩戝惉鍣ㄧ被鍨�',
+ `listener_path` varchar(400) DEFAULT NULL COMMENT '鐩戝惉鍣ㄨ矾寰�',
+ `form_custom` char(1) DEFAULT 'N' COMMENT '瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級',
+ `form_path` varchar(100) DEFAULT NULL COMMENT '瀹℃壒琛ㄥ崟璺緞',
+ `version` varchar(20) NOT NULL COMMENT '鐗堟湰',
+ `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+ `create_by` varchar(64) DEFAULT '' COMMENT '鍒涘缓浜�',
+ `update_time` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+ `update_by` varchar(64) DEFAULT '' COMMENT '鏇存柊浜�',
+ `ext` text COMMENT '鑺傜偣鎵╁睍灞炴��',
+ `del_flag` char(1) DEFAULT '0' COMMENT '鍒犻櫎鏍囧織',
+ `tenant_id` varchar(40) DEFAULT NULL COMMENT '绉熸埛id',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB COMMENT ='娴佺▼鑺傜偣琛�';
+
+CREATE TABLE `flow_skip`
+(
+ `id` bigint NOT NULL COMMENT '涓婚敭id',
+ `definition_id` bigint NOT NULL COMMENT '娴佺▼瀹氫箟id',
+ `now_node_code` varchar(100) NOT NULL COMMENT '褰撳墠娴佺▼鑺傜偣鐨勭紪鐮�',
+ `now_node_type` tinyint(1) DEFAULT NULL COMMENT '褰撳墠鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�',
+ `next_node_code` varchar(100) NOT NULL COMMENT '涓嬩竴涓祦绋嬭妭鐐圭殑缂栫爜',
+ `next_node_type` tinyint(1) DEFAULT NULL COMMENT '涓嬩竴涓妭鐐圭被鍨嬶紙0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�',
+ `skip_name` varchar(100) DEFAULT NULL COMMENT '璺宠浆鍚嶇О',
+ `skip_type` varchar(40) DEFAULT NULL COMMENT '璺宠浆绫诲瀷锛圥ASS瀹℃壒閫氳繃 REJECT閫�鍥烇級',
+ `skip_condition` varchar(200) DEFAULT NULL COMMENT '璺宠浆鏉′欢',
+ `coordinate` varchar(100) DEFAULT NULL COMMENT '鍧愭爣',
+ `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+ `create_by` varchar(64) DEFAULT '' COMMENT '鍒涘缓浜�',
+ `update_time` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+ `update_by` varchar(64) DEFAULT '' COMMENT '鏇存柊浜�',
+ `del_flag` char(1) DEFAULT '0' COMMENT '鍒犻櫎鏍囧織',
+ `tenant_id` varchar(40) DEFAULT NULL COMMENT '绉熸埛id',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB COMMENT ='鑺傜偣璺宠浆鍏宠仈琛�';
+
+CREATE TABLE `flow_instance`
+(
+ `id` bigint NOT NULL COMMENT '涓婚敭id',
+ `definition_id` bigint NOT NULL COMMENT '瀵瑰簲flow_definition琛ㄧ殑id',
+ `business_id` varchar(40) NOT NULL COMMENT '涓氬姟id',
+ `node_type` tinyint(1) NOT NULL COMMENT '鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�',
+ `node_code` varchar(40) NOT NULL COMMENT '娴佺▼鑺傜偣缂栫爜',
+ `node_name` varchar(100) DEFAULT NULL COMMENT '娴佺▼鑺傜偣鍚嶇О',
+ `variable` text COMMENT '浠诲姟鍙橀噺',
+ `flow_status` varchar(20) NOT NULL COMMENT '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�',
+ `activity_status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '娴佺▼婵�娲荤姸鎬侊紙0鎸傝捣 1婵�娲伙級',
+ `def_json` text COMMENT '娴佺▼瀹氫箟json',
+ `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+ `create_by` varchar(64) DEFAULT '' COMMENT '鍒涘缓浜�',
+ `update_time` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+ `update_by` varchar(64) DEFAULT '' COMMENT '鏇存柊浜�',
+ `ext` varchar(500) DEFAULT NULL COMMENT '鎵╁睍瀛楁锛岄鐣欑粰涓氬姟绯荤粺浣跨敤',
+ `del_flag` char(1) DEFAULT '0' COMMENT '鍒犻櫎鏍囧織',
+ `tenant_id` varchar(40) DEFAULT NULL COMMENT '绉熸埛id',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB COMMENT ='娴佺▼瀹炰緥琛�';
+
+CREATE TABLE `flow_task`
+(
+ `id` bigint NOT NULL COMMENT '涓婚敭id',
+ `definition_id` bigint NOT NULL COMMENT '瀵瑰簲flow_definition琛ㄧ殑id',
+ `instance_id` bigint NOT NULL COMMENT '瀵瑰簲flow_instance琛ㄧ殑id',
+ `node_code` varchar(100) NOT NULL COMMENT '鑺傜偣缂栫爜',
+ `node_name` varchar(100) DEFAULT NULL COMMENT '鑺傜偣鍚嶇О',
+ `node_type` tinyint(1) NOT NULL COMMENT '鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�',
+ `flow_status` varchar(20) NOT NULL COMMENT '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�',
+ `form_custom` char(1) DEFAULT 'N' COMMENT '瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級',
+ `form_path` varchar(100) DEFAULT NULL COMMENT '瀹℃壒琛ㄥ崟璺緞',
+ `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+ `create_by` varchar(64) DEFAULT '' COMMENT '鍒涘缓浜�',
+ `update_time` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+ `update_by` varchar(64) DEFAULT '' COMMENT '鏇存柊浜�',
+ `del_flag` char(1) DEFAULT '0' COMMENT '鍒犻櫎鏍囧織',
+ `tenant_id` varchar(40) DEFAULT NULL COMMENT '绉熸埛id',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB COMMENT ='寰呭姙浠诲姟琛�';
+
+CREATE TABLE `flow_his_task`
+(
+ `id` bigint(20) NOT NULL COMMENT '涓婚敭id',
+ `definition_id` bigint(20) NOT NULL COMMENT '瀵瑰簲flow_definition琛ㄧ殑id',
+ `instance_id` bigint(20) NOT NULL COMMENT '瀵瑰簲flow_instance琛ㄧ殑id',
+ `task_id` bigint(20) NOT NULL COMMENT '瀵瑰簲flow_task琛ㄧ殑id',
+ `node_code` varchar(100) DEFAULT NULL COMMENT '寮�濮嬭妭鐐圭紪鐮�',
+ `node_name` varchar(100) DEFAULT NULL COMMENT '寮�濮嬭妭鐐瑰悕绉�',
+ `node_type` tinyint(1) DEFAULT NULL COMMENT '寮�濮嬭妭鐐圭被鍨嬶紙0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�',
+ `target_node_code` varchar(200) DEFAULT NULL COMMENT '鐩爣鑺傜偣缂栫爜',
+ `target_node_name` varchar(200) DEFAULT NULL COMMENT '缁撴潫鑺傜偣鍚嶇О',
+ `approver` varchar(40) DEFAULT NULL COMMENT '瀹℃壒浜�',
+ `cooperate_type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '鍗忎綔鏂瑰紡(1瀹℃壒 2杞姙 3濮旀淳 4浼氱 5绁ㄧ 6鍔犵 7鍑忕)',
+ `collaborator` varchar(500) DEFAULT NULL COMMENT '鍗忎綔浜�',
+ `skip_type` varchar(10) NOT NULL COMMENT '娴佽浆绫诲瀷锛圥ASS閫氳繃 REJECT閫�鍥� NONE鏃犲姩浣滐級',
+ `flow_status` varchar(20) NOT NULL COMMENT '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�',
+ `form_custom` char(1) DEFAULT 'N' COMMENT '瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級',
+ `form_path` varchar(100) DEFAULT NULL COMMENT '瀹℃壒琛ㄥ崟璺緞',
+ `message` varchar(500) DEFAULT NULL COMMENT '瀹℃壒鎰忚',
+ `variable` TEXT DEFAULT NULL COMMENT '浠诲姟鍙橀噺',
+ `ext` TEXT DEFAULT NULL COMMENT '涓氬姟璇︽儏 瀛樹笟鍔¤〃瀵硅薄json瀛楃涓�',
+ `create_time` datetime DEFAULT NULL COMMENT '浠诲姟寮�濮嬫椂闂�',
+ `update_time` datetime DEFAULT NULL COMMENT '瀹℃壒瀹屾垚鏃堕棿',
+ `del_flag` char(1) DEFAULT '0' COMMENT '鍒犻櫎鏍囧織',
+ `tenant_id` varchar(40) DEFAULT NULL COMMENT '绉熸埛id',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB COMMENT ='鍘嗗彶浠诲姟璁板綍琛�';
+
+
+CREATE TABLE `flow_user`
+(
+ `id` bigint NOT NULL COMMENT '涓婚敭id',
+ `type` char(1) NOT NULL COMMENT '浜哄憳绫诲瀷锛�1寰呭姙浠诲姟鐨勫鎵逛汉鏉冮檺 2寰呭姙浠诲姟鐨勮浆鍔炰汉鏉冮檺 3寰呭姙浠诲姟鐨勫鎵樹汉鏉冮檺锛�',
+ `processed_by` varchar(80) DEFAULT NULL COMMENT '鏉冮檺浜�',
+ `associated` bigint NOT NULL COMMENT '浠诲姟琛╥d',
+ `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+ `create_by` varchar(80) DEFAULT NULL COMMENT '鍒涘缓浜�',
+ `update_time` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+ `update_by` varchar(64) DEFAULT '' COMMENT '鍒涘缓浜�',
+ `del_flag` char(1) DEFAULT '0' COMMENT '鍒犻櫎鏍囧織',
+ `tenant_id` varchar(40) DEFAULT NULL COMMENT '绉熸埛id',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `user_processed_type` (`processed_by`, `type`),
+ KEY `user_associated` (`associated`) USING BTREE
+) ENGINE = InnoDB COMMENT ='娴佺▼鐢ㄦ埛琛�';
+
+-- ----------------------------
+-- 娴佺▼鍒嗙被琛�
+-- ----------------------------
+create table flow_category
+(
+ category_id bigint(20) not null comment '娴佺▼鍒嗙被ID',
+ tenant_id varchar(20) default '000000' comment '绉熸埛缂栧彿',
+ parent_id bigint(20) default 0 comment '鐖舵祦绋嬪垎绫籭d',
+ ancestors varchar(500) default '' comment '绁栫骇鍒楄〃',
+ category_name varchar(30) not null comment '娴佺▼鍒嗙被鍚嶇О',
+ order_num int(4) default 0 comment '鏄剧ず椤哄簭',
+ del_flag char(1) default '0' comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�',
+ create_dept bigint(20) null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) null comment '鍒涘缓鑰�',
+ create_time datetime null comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) null comment '鏇存柊鑰�',
+ update_time datetime null comment '鏇存柊鏃堕棿',
+ primary key (category_id)
+) engine = innodb comment = '娴佺▼鍒嗙被';
+
+INSERT INTO flow_category values (100, '000000', 0, '0', 'OA瀹℃壒', 0, '0', 103, 1, sysdate(), null, null);
+INSERT INTO flow_category values (101, '000000', 100, '0,100', '鍋囧嫟绠$悊', 0, '0', 103, 1, sysdate(), null, null);
+INSERT INTO flow_category values (102, '000000', 100, '0,100', '浜轰簨绠$悊', 1, '0', 103, 1, sysdate(), null, null);
+INSERT INTO flow_category values (103, '000000', 101, '0,100,101', '璇峰亣', 0, '0', 103, 1, sysdate(), null, null);
+INSERT INTO flow_category values (104, '000000', 101, '0,100,101', '鍑哄樊', 1, '0', 103, 1, sysdate(), null, null);
+INSERT INTO flow_category values (105, '000000', 101, '0,100,101', '鍔犵彮', 2, '0', 103, 1, sysdate(), null, null);
+INSERT INTO flow_category values (106, '000000', 101, '0,100,101', '鎹㈢彮', 3, '0', 103, 1, sysdate(), null, null);
+INSERT INTO flow_category values (107, '000000', 101, '0,100,101', '澶栧嚭', 4, '0', 103, 1, sysdate(), null, null);
+INSERT INTO flow_category values (108, '000000', 102, '0,100,102', '杞', 1, '0', 103, 1, sysdate(), null, null);
+INSERT INTO flow_category values (109, '000000', 102, '0,100,102', '绂昏亴', 2, '0', 103, 1, sysdate(), null, null);
+
+-- ----------------------------
+-- 娴佺▼spel琛ㄨ揪寮忓畾涔夎〃
+-- ----------------------------
+
+CREATE TABLE flow_spel (
+ id bigint(20) NOT NULL COMMENT '涓婚敭id',
+ component_name varchar(255) DEFAULT NULL COMMENT '缁勪欢鍚嶇О',
+ method_name varchar(255) DEFAULT NULL COMMENT '鏂规硶鍚�',
+ method_params varchar(255) DEFAULT NULL COMMENT '鍙傛暟',
+ view_spel varchar(255) DEFAULT NULL COMMENT '棰勮spel琛ㄨ揪寮�',
+ remark varchar(255) DEFAULT NULL COMMENT '澶囨敞',
+ status char(1) DEFAULT '0' COMMENT '鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ del_flag char(1) DEFAULT '0' COMMENT '鍒犻櫎鏍囧織',
+ create_dept bigint(20) DEFAULT NULL COMMENT '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) DEFAULT NULL COMMENT '鍒涘缓鑰�',
+ create_time datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+ update_by bigint(20) DEFAULT NULL COMMENT '鏇存柊鑰�',
+ update_time datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+ PRIMARY KEY (id)
+) ENGINE = InnoDB COMMENT='娴佺▼spel琛ㄨ揪寮忓畾涔夎〃';
+
+INSERT INTO flow_spel VALUES (1, 'spelRuleComponent', 'selectDeptLeaderById', 'initiatorDeptId', '#{@spelRuleComponent.selectDeptLeaderById(#initiatorDeptId)}', '鏍规嵁閮ㄩ棬id鑾峰彇閮ㄩ棬璐熻矗浜�', '0', '0', 103, 1, sysdate(), 1, sysdate());
+INSERT INTO flow_spel VALUES (2, NULL, NULL, 'initiator', '${initiator}', '娴佺▼鍙戣捣浜�', '0', '0', 103, 1, sysdate(), 1, sysdate());
+
+-- ----------------------------
+-- 娴佺▼瀹炰緥涓氬姟鎵╁睍琛�
+-- ----------------------------
+
+create table flow_instance_biz_ext (
+ id bigint not null comment '涓婚敭id',
+ tenant_id varchar(20) default '000000' null comment '绉熸埛缂栧彿',
+ create_dept bigint null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint null comment '鍒涘缓鑰�',
+ create_time datetime null comment '鍒涘缓鏃堕棿',
+ update_by bigint null comment '鏇存柊鑰�',
+ update_time datetime null comment '鏇存柊鏃堕棿',
+ business_code varchar(255) null comment '涓氬姟缂栫爜',
+ business_title varchar(1000) null comment '涓氬姟鏍囬',
+ del_flag char default '0' null comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�',
+ instance_id bigint null comment '娴佺▼瀹炰緥Id',
+ business_id varchar(255) null comment '涓氬姟Id',
+ PRIMARY KEY (id)
+) ENGINE = InnoDB COMMENT '娴佺▼瀹炰緥涓氬姟鎵╁睍琛�';
+
+-- ----------------------------
+-- 璇峰亣鍗曚俊鎭�
+-- ----------------------------
+
+create table test_leave
+(
+ id bigint(20) not null comment 'id',
+ tenant_id varchar(20) default '000000' comment '绉熸埛缂栧彿',
+ apply_code varchar(50) not null comment '鐢宠缂栧彿',
+ leave_type varchar(255) not null comment '璇峰亣绫诲瀷',
+ start_date datetime not null comment '寮�濮嬫椂闂�',
+ end_date datetime not null comment '缁撴潫鏃堕棿',
+ leave_days int(10) not null comment '璇峰亣澶╂暟',
+ remark varchar(255) null comment '璇峰亣鍘熷洜',
+ status varchar(255) null comment '鐘舵��',
+ create_dept bigint null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint null comment '鍒涘缓鑰�',
+ create_time datetime null comment '鍒涘缓鏃堕棿',
+ update_by bigint null comment '鏇存柊鑰�',
+ update_time datetime null comment '鏇存柊鏃堕棿',
+ PRIMARY KEY (id) USING BTREE
+) ENGINE = InnoDB COMMENT = '璇峰亣鐢宠琛�';
+
+insert into sys_menu values ('11616', '宸ヤ綔娴�', '0', '6', 'workflow', '', '', '1', '0', 'M', '0', '0', '', 'workflow', 103, 1, sysdate(),NULL, NULL, '');
+insert into sys_menu values ('11618', '鎴戠殑浠诲姟', '0', '7', 'task', '', '', '1', '0', 'M', '0', '0', '', 'my-task', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values ('11619', '鎴戠殑寰呭姙', '11618', '2', 'taskWaiting', 'workflow/task/taskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values ('11632', '鎴戠殑宸插姙', '11618', '3', 'taskFinish', 'workflow/task/taskFinish', '', '1', '1', 'C', '0', '0', '', 'finish', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values ('11633', '鎴戠殑鎶勯��', '11618', '4', 'taskCopyList', 'workflow/task/taskCopyList', '', '1', '1', 'C', '0', '0', '', 'my-copy', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values ('11620', '娴佺▼瀹氫箟', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'process-definition', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values ('11621', '娴佺▼瀹炰緥', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values ('11622', '娴佺▼鍒嗙被', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'category', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11801', '娴佺▼琛ㄨ揪寮�', '11616', '2', 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, sysdate(), 1, sysdate(), '娴佺▼杈惧紡瀹氫箟鑿滃崟');
+insert into sys_menu values ('11629', '鎴戝彂璧风殑', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'guide', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values ('11630', '娴佺▼鐩戞帶', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values ('11631', '寰呭姙浠诲姟', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values ('11700', '娴佺▼璁捐', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', 1, 1, 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), null, null, '/workflow/processDefinition');
+insert into sys_menu values ('11701', '璇峰亣鐢宠', '11616', '6', 'leaveEdit/index', 'workflow/leave/leaveEdit', '', 1, 1, 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), null, null, '');
+-- 娴佺▼鍒嗙被绠$悊鐩稿叧鎸夐挳
+insert into sys_menu values ('11623', '娴佺▼鍒嗙被鏌ヨ', '11622', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:query', '#', 103, 1,sysdate(), null, null, '');
+insert into sys_menu values ('11624', '娴佺▼鍒嗙被鏂板', '11622', '2', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:add', '#', 103, 1,sysdate(), null, null, '');
+insert into sys_menu values ('11625', '娴佺▼鍒嗙被淇敼', '11622', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:edit', '#', 103, 1,sysdate(), null, null, '');
+insert into sys_menu values ('11626', '娴佺▼鍒嗙被鍒犻櫎', '11622', '4', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:remove', '#', 103,1, sysdate(), null, null, '');
+insert into sys_menu values ('11627', '娴佺▼鍒嗙被瀵煎嚭', '11622', '5', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:export', '#', 103,1, sysdate(), null, null, '');
+-- 娴佺▼琛ㄨ揪寮忕鐞嗙浉鍏虫寜閽�
+INSERT INTO sys_menu VALUES ('11802', '娴佺▼杈惧紡瀹氫箟鏌ヨ', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11803', '娴佺▼杈惧紡瀹氫箟鏂板', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11804', '娴佺▼杈惧紡瀹氫箟淇敼', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11805', '娴佺▼杈惧紡瀹氫箟鍒犻櫎', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11806', '娴佺▼杈惧紡瀹氫箟瀵煎嚭', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, sysdate(), NULL, NULL, '');
+-- 璇峰亣娴嬭瘯鐩稿叧鎸夐挳
+insert into sys_menu VALUES ('11638', '璇峰亣鐢宠', 5, 1, 'leave', 'workflow/leave/index', '', 1, 0, 'C', '0', '0', 'workflow:leave:list', '#', 103, 1, sysdate(), NULL, NULL, '璇峰亣鐢宠鑿滃崟');
+insert into sys_menu VALUES ('11639', '璇峰亣鐢宠鏌ヨ', '11638', 1, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:query', '#', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu VALUES ('11640', '璇峰亣鐢宠鏂板', '11638', 2, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:add', '#', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu VALUES ('11641', '璇峰亣鐢宠淇敼', '11638', 3, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu VALUES ('11642', '璇峰亣鐢宠鍒犻櫎', '11638', 4, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:remove', '#', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu VALUES ('11643', '璇峰亣鐢宠瀵煎嚭', '11638', 5, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:export', '#', 103, 1, sysdate(), NULL, NULL, '');
+
+INSERT INTO sys_dict_type VALUES (13, '000000', '涓氬姟鐘舵��', 'wf_business_status', 103, 1, sysdate(), NULL, NULL, '涓氬姟鐘舵�佸垪琛�');
+INSERT INTO sys_dict_type VALUES (14, '000000', '琛ㄥ崟绫诲瀷', 'wf_form_type', 103, 1, sysdate(), NULL, NULL, '琛ㄥ崟绫诲瀷鍒楄〃');
+INSERT INTO sys_dict_type VALUES (15, '000000', '浠诲姟鐘舵��', 'wf_task_status', 103, 1, sysdate(), NULL, NULL, '浠诲姟鐘舵��');
+INSERT INTO sys_dict_data VALUES (39, '000000', 1, '宸叉挙閿�', 'cancel', 'wf_business_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL,'宸叉挙閿�');
+INSERT INTO sys_dict_data VALUES (40, '000000', 2, '鑽夌', 'draft', 'wf_business_status', '', 'info', 'N', 103, 1, sysdate(), NULL, NULL, '鑽夌');
+INSERT INTO sys_dict_data VALUES (41, '000000', 3, '寰呭鏍�', 'waiting', 'wf_business_status', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL,'寰呭鏍�');
+INSERT INTO sys_dict_data VALUES (42, '000000', 4, '宸插畬鎴�', 'finish', 'wf_business_status', '', 'success', 'N', 103, 1, sysdate(), NULL, NULL,'宸插畬鎴�');
+INSERT INTO sys_dict_data VALUES (43, '000000', 5, '宸蹭綔搴�', 'invalid', 'wf_business_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL,'宸蹭綔搴�');
+INSERT INTO sys_dict_data VALUES (44, '000000', 6, '宸查��鍥�', 'back', 'wf_business_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL,'宸查��鍥�');
+INSERT INTO sys_dict_data VALUES (45, '000000', 7, '宸茬粓姝�', 'termination', 'wf_business_status', '', 'danger', 'N', 103, 1, sysdate(), NULL,NULL, '宸茬粓姝�');
+INSERT INTO sys_dict_data VALUES (46, '000000', 1, '鑷畾涔夎〃鍗�', 'static', 'wf_form_type', '', 'success', 'N', 103, 1, sysdate(), NULL, NULL,'鑷畾涔夎〃鍗�');
+INSERT INTO sys_dict_data VALUES (47, '000000', 2, '鍔ㄦ�佽〃鍗�', 'dynamic', 'wf_form_type', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL,'鍔ㄦ�佽〃鍗�');
+INSERT INTO sys_dict_data VALUES (48, '000000', 1, '鎾ら攢', 'cancel', 'wf_task_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '鎾ら攢');
+INSERT INTO sys_dict_data VALUES (49, '000000', 2, '閫氳繃', 'pass', 'wf_task_status', '', 'success', 'N', 103, 1, sysdate(), NULL, NULL, '閫氳繃');
+INSERT INTO sys_dict_data VALUES (50, '000000', 3, '寰呭鏍�', 'waiting', 'wf_task_status', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL, '寰呭鏍�');
+INSERT INTO sys_dict_data VALUES (51, '000000', 4, '浣滃簾', 'invalid', 'wf_task_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '浣滃簾');
+INSERT INTO sys_dict_data VALUES (52, '000000', 5, '閫�鍥�', 'back', 'wf_task_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '閫�鍥�');
+INSERT INTO sys_dict_data VALUES (53, '000000', 6, '缁堟', 'termination', 'wf_task_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '缁堟');
+INSERT INTO sys_dict_data VALUES (54, '000000', 7, '杞姙', 'transfer', 'wf_task_status', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL, '杞姙');
+INSERT INTO sys_dict_data VALUES (55, '000000', 8, '濮旀墭', 'depute', 'wf_task_status', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL, '濮旀墭');
+INSERT INTO sys_dict_data VALUES (56, '000000', 9, '鎶勯��', 'copy', 'wf_task_status', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL, '鎶勯��');
+INSERT INTO sys_dict_data VALUES (57, '000000', 10, '鍔犵', 'sign', 'wf_task_status', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL, '鍔犵');
+INSERT INTO sys_dict_data VALUES (58, '000000', 11, '鍑忕', 'sign_off', 'wf_task_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '鍑忕');
+INSERT INTO sys_dict_data VALUES (59, '000000', 11, '瓒呮椂', 'timeout', 'wf_task_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '瓒呮椂');
+
diff --git a/RuoYi-Vue-Plus/script/sql/sqlserver/sqlserver_ry_job.sql b/RuoYi-Vue-Plus/script/sql/sqlserver/sqlserver_ry_job.sql
new file mode 100755
index 0000000..ceb8c45
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/sqlserver/sqlserver_ry_job.sql
@@ -0,0 +1,2891 @@
+/*
+ SnailJob Database Transfer Tool
+ Source Server Type : MySQL
+ Target Server Type : Microsoft SQL Server
+ Date: 2025-06-21 23:34:44
+*/
+
+
+-- sj_namespace
+CREATE TABLE sj_namespace
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ name nvarchar(64) NOT NULL,
+ unique_id nvarchar(64) NOT NULL,
+ description nvarchar(256) NOT NULL DEFAULT '',
+ deleted tinyint NOT NULL DEFAULT 0,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_namespace_01 ON sj_namespace (name)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_namespace',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_namespace',
+ 'COLUMN', N'name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍞竴id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_namespace',
+ 'COLUMN', N'unique_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎻忚堪',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_namespace',
+ 'COLUMN', N'description'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫昏緫鍒犻櫎 1銆佸垹闄�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_namespace',
+ 'COLUMN', N'deleted'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_namespace',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_namespace',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_namespace'
+GO
+
+INSERT INTO sj_namespace(name, unique_id, description, deleted, create_dt, update_dt) VALUES (N'Development', N'dev', N'', 0, getdate(), getdate())
+GO
+INSERT INTO sj_namespace(name, unique_id, description, deleted, create_dt, update_dt) VALUES (N'Production', N'prod', N'', 0, getdate(), getdate())
+GO
+
+-- sj_group_config
+CREATE TABLE sj_group_config
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL DEFAULT '',
+ description nvarchar(256) NOT NULL DEFAULT '',
+ token nvarchar(64) NOT NULL DEFAULT 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT',
+ group_status tinyint NOT NULL DEFAULT 0,
+ version int NOT NULL,
+ group_partition int NOT NULL,
+ id_generator_mode tinyint NOT NULL DEFAULT 1,
+ init_scene tinyint NOT NULL DEFAULT 0,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE UNIQUE INDEX uk_sj_group_config_01 ON sj_group_config (namespace_id, group_name)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_group_config',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_group_config',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_group_config',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勬弿杩�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_group_config',
+ 'COLUMN', N'description'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'token',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_group_config',
+ 'COLUMN', N'token'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勭姸鎬� 0銆佹湭鍚敤 1銆佸惎鐢�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_group_config',
+ 'COLUMN', N'group_status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐗堟湰鍙�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_group_config',
+ 'COLUMN', N'version'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒嗗尯',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_group_config',
+ 'COLUMN', N'group_partition'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍞竴id鐢熸垚妯″紡 榛樿鍙锋妯″紡',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_group_config',
+ 'COLUMN', N'id_generator_mode'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏄惁鍒濆鍖栧満鏅� 0:鍚� 1:鏄�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_group_config',
+ 'COLUMN', N'init_scene'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_group_config',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_group_config',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勯厤缃�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_group_config'
+GO
+
+INSERT INTO sj_group_config(namespace_id, group_name, description, token, group_status, version, group_partition, id_generator_mode, init_scene, create_dt, update_dt) VALUES (N'dev', N'ruoyi_group', N'', N'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', N'1', N'1', N'0', N'1', N'1', getdate(), getdate())
+GO
+INSERT INTO sj_group_config(namespace_id, group_name, description, token, group_status, version, group_partition, id_generator_mode, init_scene, create_dt, update_dt) VALUES (N'prod', N'ruoyi_group', N'', N'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', N'1', N'1', N'0', N'1', N'1', getdate(), getdate())
+GO
+
+-- sj_notify_config
+CREATE TABLE sj_notify_config
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL,
+ notify_name nvarchar(64) NOT NULL DEFAULT '',
+ system_task_type tinyint NOT NULL DEFAULT 3,
+ notify_status tinyint NOT NULL DEFAULT 0,
+ recipient_ids nvarchar(128) NOT NULL,
+ notify_threshold int NOT NULL DEFAULT 0,
+ notify_scene tinyint NOT NULL DEFAULT 0,
+ rate_limiter_status tinyint NOT NULL DEFAULT 0,
+ rate_limiter_threshold int NOT NULL DEFAULT 0,
+ description nvarchar(256) NOT NULL DEFAULT '',
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_notify_config_01 ON sj_notify_config (namespace_id, group_name)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫氱煡鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'notify_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟绫诲瀷 1. 閲嶈瘯浠诲姟 2. 閲嶈瘯鍥炶皟 3銆丣OB浠诲姟 4銆乄ORKFLOW浠诲姟',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'system_task_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫氱煡鐘舵�� 0銆佹湭鍚敤 1銆佸惎鐢�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'notify_status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎺ユ敹浜篿d鍒楄〃',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'recipient_ids'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫氱煡闃堝��',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'notify_threshold'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫氱煡鍦烘櫙',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'notify_scene'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'闄愭祦鐘舵�� 0銆佹湭鍚敤 1銆佸惎鐢�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'rate_limiter_status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'姣忕闄愭祦闃堝��',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'rate_limiter_threshold'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎻忚堪',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'description'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫氱煡閰嶇疆',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_config'
+GO
+
+-- sj_notify_recipient
+CREATE TABLE sj_notify_recipient
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ recipient_name nvarchar(64) NOT NULL,
+ notify_type tinyint NOT NULL DEFAULT 0,
+ notify_attribute nvarchar(512) NOT NULL,
+ description nvarchar(256) NOT NULL DEFAULT '',
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_notify_recipient_01 ON sj_notify_recipient (namespace_id)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_recipient',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_recipient',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎺ユ敹浜哄悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_recipient',
+ 'COLUMN', N'recipient_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫氱煡绫诲瀷 1銆侀拤閽� 2銆侀偖浠� 3銆佷紒涓氬井淇� 4 椋炰功 5 webhook',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_recipient',
+ 'COLUMN', N'notify_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閰嶇疆灞炴��',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_recipient',
+ 'COLUMN', N'notify_attribute'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎻忚堪',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_recipient',
+ 'COLUMN', N'description'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_recipient',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_recipient',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛婅閫氱煡鎺ユ敹浜�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_notify_recipient'
+GO
+
+-- sj_retry_dead_letter
+CREATE TABLE sj_retry_dead_letter
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL,
+ group_id bigint NOT NULL,
+ scene_name nvarchar(64) NOT NULL,
+ scene_id bigint NOT NULL,
+ idempotent_id nvarchar(64) NOT NULL,
+ biz_no nvarchar(64) NOT NULL DEFAULT '',
+ executor_name nvarchar(512) NOT NULL DEFAULT '',
+ serializer_name nvarchar(32) NOT NULL DEFAULT 'jackson',
+ args_str nvarchar(max) NOT NULL,
+ ext_attrs nvarchar(max) NOT NULL,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_retry_dead_letter_01 ON sj_retry_dead_letter (namespace_id, group_name, scene_name)
+GO
+CREATE INDEX idx_sj_retry_dead_letter_02 ON sj_retry_dead_letter (idempotent_id)
+GO
+CREATE INDEX idx_sj_retry_dead_letter_03 ON sj_retry_dead_letter (biz_no)
+GO
+CREATE INDEX idx_sj_retry_dead_letter_04 ON sj_retry_dead_letter (create_dt)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁処d',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter',
+ 'COLUMN', N'group_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍦烘櫙鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter',
+ 'COLUMN', N'scene_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍦烘櫙ID',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter',
+ 'COLUMN', N'scene_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'骞傜瓑id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter',
+ 'COLUMN', N'idempotent_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓氬姟缂栧彿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter',
+ 'COLUMN', N'biz_no'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц鍣ㄥ悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter',
+ 'COLUMN', N'executor_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц鏂规硶鍙傛暟搴忓垪鍖栧櫒鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter',
+ 'COLUMN', N'serializer_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц鏂规硶鍙傛暟',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter',
+ 'COLUMN', N'args_str'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵╁睍瀛楁',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter',
+ 'COLUMN', N'ext_attrs'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'姝讳俊闃熷垪琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_dead_letter'
+GO
+
+-- sj_retry
+CREATE TABLE sj_retry
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL,
+ group_id bigint NOT NULL,
+ scene_name nvarchar(64) NOT NULL,
+ scene_id bigint NOT NULL,
+ idempotent_id nvarchar(64) NOT NULL,
+ biz_no nvarchar(64) NOT NULL DEFAULT '',
+ executor_name nvarchar(512) NOT NULL DEFAULT '',
+ args_str nvarchar(max) NOT NULL,
+ ext_attrs nvarchar(max) NOT NULL,
+ serializer_name nvarchar(32) NOT NULL DEFAULT 'jackson',
+ next_trigger_at bigint NOT NULL,
+ retry_count int NOT NULL DEFAULT 0,
+ retry_status tinyint NOT NULL DEFAULT 0,
+ task_type tinyint NOT NULL DEFAULT 1,
+ bucket_index int NOT NULL DEFAULT 0,
+ parent_id bigint NOT NULL DEFAULT 0,
+ deleted bigint NOT NULL DEFAULT 0,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE UNIQUE INDEX uk_sj_retry_01 ON sj_retry (scene_id, task_type, idempotent_id, deleted)
+GO
+
+CREATE INDEX idx_sj_retry_01 ON sj_retry (biz_no)
+GO
+CREATE INDEX idx_sj_retry_02 ON sj_retry (idempotent_id)
+GO
+CREATE INDEX idx_sj_retry_03 ON sj_retry (retry_status, bucket_index)
+GO
+CREATE INDEX idx_sj_retry_04 ON sj_retry (parent_id)
+GO
+CREATE INDEX idx_sj_retry_05 ON sj_retry (create_dt)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁処d',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'group_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍦烘櫙鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'scene_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍦烘櫙ID',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'scene_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'骞傜瓑id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'idempotent_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓氬姟缂栧彿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'biz_no'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц鍣ㄥ悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'executor_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц鏂规硶鍙傛暟',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'args_str'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵╁睍瀛楁',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'ext_attrs'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц鏂规硶鍙傛暟搴忓垪鍖栧櫒鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'serializer_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓嬫瑙﹀彂鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'next_trigger_at'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閲嶈瘯娆℃暟',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'retry_count'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閲嶈瘯鐘舵�� 0銆侀噸璇曚腑 1銆佹垚鍔� 2銆佹渶澶ч噸璇曟鏁�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'retry_status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟绫诲瀷 1銆侀噸璇曟暟鎹� 2銆佸洖璋冩暟鎹�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'task_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'bucket',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'bucket_index'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐖惰妭鐐筰d',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'parent_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫昏緫鍒犻櫎',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'deleted'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閲嶈瘯淇℃伅琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry'
+GO
+
+-- sj_retry_task
+CREATE TABLE sj_retry_task
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL,
+ scene_name nvarchar(64) NOT NULL,
+ retry_id bigint NOT NULL,
+ ext_attrs nvarchar(max) NOT NULL,
+ task_status tinyint NOT NULL DEFAULT 1,
+ task_type tinyint NOT NULL DEFAULT 1,
+ operation_reason tinyint NOT NULL DEFAULT 0,
+ client_info nvarchar(128) NULL DEFAULT NULL,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_retry_task_01 ON sj_retry_task (namespace_id, group_name, scene_name)
+GO
+CREATE INDEX idx_sj_retry_task_02 ON sj_retry_task (task_status)
+GO
+CREATE INDEX idx_sj_retry_task_03 ON sj_retry_task (create_dt)
+GO
+CREATE INDEX idx_sj_retry_task_04 ON sj_retry_task (retry_id)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍦烘櫙鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task',
+ 'COLUMN', N'scene_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閲嶈瘯淇℃伅Id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task',
+ 'COLUMN', N'retry_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵╁睍瀛楁',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task',
+ 'COLUMN', N'ext_attrs'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閲嶈瘯鐘舵��',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task',
+ 'COLUMN', N'task_status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟绫诲瀷 1銆侀噸璇曟暟鎹� 2銆佸洖璋冩暟鎹�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task',
+ 'COLUMN', N'task_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎿嶄綔鍘熷洜',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task',
+ 'COLUMN', N'operation_reason'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'瀹㈡埛绔湴鍧� clientId#ip:port',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task',
+ 'COLUMN', N'client_info'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閲嶈瘯浠诲姟琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task'
+GO
+
+-- sj_retry_task_log_message
+CREATE TABLE sj_retry_task_log_message
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL,
+ retry_id bigint NOT NULL,
+ retry_task_id bigint NOT NULL,
+ message nvarchar(max) NOT NULL,
+ log_num int NOT NULL DEFAULT 1,
+ real_time bigint NOT NULL DEFAULT 0,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_retry_task_log_message_01 ON sj_retry_task_log_message (namespace_id, group_name, retry_task_id)
+GO
+CREATE INDEX idx_sj_retry_task_log_message_02 ON sj_retry_task_log_message (create_dt)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task_log_message',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task_log_message',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task_log_message',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閲嶈瘯淇℃伅Id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task_log_message',
+ 'COLUMN', N'retry_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閲嶈瘯浠诲姟Id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task_log_message',
+ 'COLUMN', N'retry_task_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'寮傚父淇℃伅',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task_log_message',
+ 'COLUMN', N'message'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏃ュ織鏁伴噺',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task_log_message',
+ 'COLUMN', N'log_num'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婃姤鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task_log_message',
+ 'COLUMN', N'real_time'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task_log_message',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟璋冨害鏃ュ織淇℃伅璁板綍琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_task_log_message'
+GO
+
+-- sj_retry_scene_config
+CREATE TABLE sj_retry_scene_config
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ scene_name nvarchar(64) NOT NULL,
+ group_name nvarchar(64) NOT NULL,
+ scene_status tinyint NOT NULL DEFAULT 0,
+ max_retry_count int NOT NULL DEFAULT 5,
+ back_off tinyint NOT NULL DEFAULT 1,
+ trigger_interval nvarchar(16) NOT NULL DEFAULT '',
+ notify_ids nvarchar(128) NOT NULL DEFAULT '',
+ deadline_request bigint NOT NULL DEFAULT 60000,
+ executor_timeout int NOT NULL DEFAULT 5,
+ route_key tinyint NOT NULL DEFAULT 4,
+ block_strategy tinyint NOT NULL DEFAULT 1,
+ cb_status tinyint NOT NULL DEFAULT 0,
+ cb_trigger_type tinyint NOT NULL DEFAULT 1,
+ cb_max_count int NOT NULL DEFAULT 16,
+ cb_trigger_interval nvarchar(16) NOT NULL DEFAULT '',
+ owner_id bigint NULL DEFAULT NULL,
+ labels nvarchar(512) NULL DEFAULT '',
+ description nvarchar(256) NOT NULL DEFAULT '',
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE UNIQUE INDEX uk_sj_retry_scene_config_01 ON sj_retry_scene_config (namespace_id, group_name, scene_name)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍦烘櫙鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'scene_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勭姸鎬� 0銆佹湭鍚敤 1銆佸惎鐢�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'scene_status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏈�澶ч噸璇曟鏁�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'max_retry_count'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'1銆侀粯璁ょ瓑绾� 2銆佸浐瀹氶棿闅旀椂闂� 3銆丆RON 琛ㄨ揪寮�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'back_off'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'闂撮殧鏃堕暱',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'trigger_interval'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫氱煡鍛婅鍦烘櫙閰嶇疆id鍒楄〃',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'notify_ids'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'Deadline Request 璋冪敤閾捐秴鏃� 鍗曚綅姣',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'deadline_request'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟鎵ц瓒呮椂鏃堕棿锛屽崟浣嶇',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'executor_timeout'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'璺敱绛栫暐',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'route_key'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'闃诲绛栫暐 1銆佷涪寮� 2銆佽鐩� 3銆佸苟琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'block_strategy'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍥炶皟鐘舵�� 0銆佷笉寮�鍚� 1銆佸紑鍚�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'cb_status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'1銆侀粯璁ょ瓑绾� 2銆佸浐瀹氶棿闅旀椂闂� 3銆丆RON 琛ㄨ揪寮�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'cb_trigger_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍥炶皟鐨勬渶澶ф墽琛屾鏁�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'cb_max_count'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍥炶皟鐨勬渶澶ф墽琛屾鏁�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'cb_trigger_interval'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'璐熻矗浜篿d',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'owner_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏍囩',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'labels'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎻忚堪',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'description'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍦烘櫙閰嶇疆',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_scene_config'
+GO
+
+-- sj_server_node
+CREATE TABLE sj_server_node
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL,
+ host_id nvarchar(64) NOT NULL,
+ host_ip nvarchar(64) NOT NULL,
+ host_port int NOT NULL,
+ expire_at datetime2 NOT NULL,
+ node_type tinyint NOT NULL,
+ ext_attrs nvarchar(256) NULL DEFAULT '',
+ labels nvarchar(512) NULL DEFAULT '',
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE UNIQUE INDEX uk_sj_server_node_01 ON sj_server_node (host_id, host_ip)
+GO
+
+CREATE INDEX idx_sj_server_node_01 ON sj_server_node (namespace_id, group_name)
+GO
+CREATE INDEX idx_sj_server_node_02 ON sj_server_node (expire_at, node_type)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_server_node',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_server_node',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_server_node',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓绘満id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_server_node',
+ 'COLUMN', N'host_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏈哄櫒ip',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_server_node',
+ 'COLUMN', N'host_ip'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏈哄櫒绔彛',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_server_node',
+ 'COLUMN', N'host_port'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'杩囨湡鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_server_node',
+ 'COLUMN', N'expire_at'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鑺傜偣绫诲瀷 1銆佸鎴风 2銆佹槸鏈嶅姟绔�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_server_node',
+ 'COLUMN', N'node_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵╁睍瀛楁',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_server_node',
+ 'COLUMN', N'ext_attrs'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏍囩',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_server_node',
+ 'COLUMN', N'labels'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_server_node',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_server_node',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏈嶅姟鍣ㄨ妭鐐�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_server_node'
+GO
+
+-- sj_distributed_lock
+CREATE TABLE sj_distributed_lock
+(
+ name nvarchar(64) NOT NULL PRIMARY KEY,
+ lock_until datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ locked_at datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ locked_by nvarchar(255) NOT NULL,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閿佸悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_distributed_lock',
+ 'COLUMN', N'name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閿佸畾鏃堕暱',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_distributed_lock',
+ 'COLUMN', N'lock_until'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閿佸畾鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_distributed_lock',
+ 'COLUMN', N'locked_at'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閿佸畾鑰�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_distributed_lock',
+ 'COLUMN', N'locked_by'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_distributed_lock',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_distributed_lock',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閿佸畾琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_distributed_lock'
+GO
+
+-- sj_system_user
+CREATE TABLE sj_system_user
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ username nvarchar(64) NOT NULL,
+ password nvarchar(128) NOT NULL,
+ role tinyint NOT NULL DEFAULT 0,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'璐﹀彿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user',
+ 'COLUMN', N'username'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'瀵嗙爜',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user',
+ 'COLUMN', N'password'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'瑙掕壊锛�1-鏅�氱敤鎴枫��2-绠$悊鍛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user',
+ 'COLUMN', N'role'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'绯荤粺鐢ㄦ埛琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user'
+GO
+
+INSERT INTO sj_system_user (username, password, role)
+VALUES (N'admin', N'465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2)
+GO
+
+-- sj_system_user_permission
+CREATE TABLE sj_system_user_permission
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ group_name nvarchar(64) NOT NULL,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ system_user_id bigint NOT NULL,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE UNIQUE INDEX uk_sj_system_user_permission_01 ON sj_system_user_permission (namespace_id, group_name, system_user_id)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user_permission',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user_permission',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user_permission',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'绯荤粺鐢ㄦ埛id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user_permission',
+ 'COLUMN', N'system_user_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user_permission',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user_permission',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'绯荤粺鐢ㄦ埛鏉冮檺琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_system_user_permission'
+GO
+
+-- sj_job
+CREATE TABLE sj_job
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL,
+ job_name nvarchar(64) NOT NULL,
+ args_str nvarchar(max) NULL DEFAULT NULL,
+ args_type tinyint NOT NULL DEFAULT 1,
+ next_trigger_at bigint NOT NULL,
+ job_status tinyint NOT NULL DEFAULT 1,
+ task_type tinyint NOT NULL DEFAULT 1,
+ route_key tinyint NOT NULL DEFAULT 4,
+ executor_type tinyint NOT NULL DEFAULT 1,
+ executor_info nvarchar(255) NULL DEFAULT NULL,
+ trigger_type tinyint NOT NULL,
+ trigger_interval nvarchar(255) NOT NULL,
+ block_strategy tinyint NOT NULL DEFAULT 1,
+ executor_timeout int NOT NULL DEFAULT 0,
+ max_retry_times int NOT NULL DEFAULT 0,
+ parallel_num int NOT NULL DEFAULT 1,
+ retry_interval int NOT NULL DEFAULT 0,
+ bucket_index int NOT NULL DEFAULT 0,
+ resident tinyint NOT NULL DEFAULT 0,
+ notify_ids nvarchar(128) NOT NULL DEFAULT '',
+ owner_id bigint NULL DEFAULT NULL,
+ labels nvarchar(512) NULL DEFAULT '',
+ description nvarchar(256) NOT NULL DEFAULT '',
+ ext_attrs nvarchar(256) NULL DEFAULT '',
+ deleted tinyint NOT NULL DEFAULT 0,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_job_01 ON sj_job (namespace_id, group_name)
+GO
+CREATE INDEX idx_sj_job_02 ON sj_job (job_status, bucket_index)
+GO
+CREATE INDEX idx_sj_job_03 ON sj_job (create_dt)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'job_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц鏂规硶鍙傛暟',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'args_str'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍙傛暟绫诲瀷 ',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'args_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓嬫瑙﹀彂鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'next_trigger_at'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟鐘舵�� 0銆佸叧闂��1銆佸紑鍚�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'job_status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟绫诲瀷 1銆侀泦缇� 2銆佸箍鎾� 3銆佸垏鐗�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'task_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'璺敱绛栫暐',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'route_key'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц鍣ㄧ被鍨�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'executor_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц鍣ㄥ悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'executor_info'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'瑙﹀彂绫诲瀷 1.CRON 琛ㄨ揪寮� 2. 鍥哄畾鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'trigger_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'闂撮殧鏃堕暱',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'trigger_interval'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'闃诲绛栫暐 1銆佷涪寮� 2銆佽鐩� 3銆佸苟琛� 4銆佹仮澶�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'block_strategy'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟鎵ц瓒呮椂鏃堕棿锛屽崟浣嶇',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'executor_timeout'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏈�澶ч噸璇曟鏁�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'max_retry_times'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'骞惰鏁�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'parallel_num'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閲嶈瘯闂撮殧 ( s)',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'retry_interval'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'bucket',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'bucket_index'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏄惁鏄父椹讳换鍔�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'resident'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫氱煡鍛婅鍦烘櫙閰嶇疆id鍒楄〃',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'notify_ids'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'璐熻矗浜篿d',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'owner_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏍囩',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'labels'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎻忚堪',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'description'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵╁睍瀛楁',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'ext_attrs'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫昏緫鍒犻櫎 1銆佸垹闄�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'deleted'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟淇℃伅',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job'
+GO
+
+INSERT INTO sj_job (namespace_id, group_name, job_name, args_str, args_type, next_trigger_at, job_status, task_type, route_key, executor_type, executor_info, trigger_type, trigger_interval, block_strategy,executor_timeout, max_retry_times, parallel_num, retry_interval, bucket_index, resident, notify_ids, owner_id, labels, description, ext_attrs, deleted, create_dt, update_dt) VALUES (N'dev', N'ruoyi_group', N'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, N'testJobExecutor', 2, N'60', 1, 60, 3, 1, 1, 116, 0, N'', 1, N'', N'', N'', 0, getdate(), getdate())
+GO
+
+-- sj_job_log_message
+CREATE TABLE sj_job_log_message
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL,
+ job_id bigint NOT NULL,
+ task_batch_id bigint NOT NULL,
+ task_id bigint NOT NULL,
+ message nvarchar(max) NOT NULL,
+ log_num int NOT NULL DEFAULT 1,
+ real_time bigint NOT NULL DEFAULT 0,
+ ext_attrs nvarchar(256) NULL DEFAULT '',
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_job_log_message_01 ON sj_job_log_message (task_batch_id, task_id)
+GO
+CREATE INDEX idx_sj_job_log_message_02 ON sj_job_log_message (create_dt)
+GO
+CREATE INDEX idx_sj_job_log_message_03 ON sj_job_log_message (namespace_id, group_name)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_log_message',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_log_message',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_log_message',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟淇℃伅id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_log_message',
+ 'COLUMN', N'job_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟鎵规id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_log_message',
+ 'COLUMN', N'task_batch_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'璋冨害浠诲姟id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_log_message',
+ 'COLUMN', N'task_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'璋冨害淇℃伅',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_log_message',
+ 'COLUMN', N'message'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏃ュ織鏁伴噺',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_log_message',
+ 'COLUMN', N'log_num'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婃姤鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_log_message',
+ 'COLUMN', N'real_time'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵╁睍瀛楁',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_log_message',
+ 'COLUMN', N'ext_attrs'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_log_message',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'璋冨害鏃ュ織',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_log_message'
+GO
+
+-- sj_job_task
+CREATE TABLE sj_job_task
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL,
+ job_id bigint NOT NULL,
+ task_batch_id bigint NOT NULL,
+ parent_id bigint NOT NULL DEFAULT 0,
+ task_status tinyint NOT NULL DEFAULT 0,
+ retry_count int NOT NULL DEFAULT 0,
+ mr_stage tinyint NULL DEFAULT NULL,
+ leaf tinyint NOT NULL DEFAULT '1',
+ task_name nvarchar(255) NOT NULL DEFAULT '',
+ client_info nvarchar(128) NULL DEFAULT NULL,
+ wf_context nvarchar(max) NULL DEFAULT NULL,
+ result_message nvarchar(max) NOT NULL,
+ args_str nvarchar(max) NULL DEFAULT NULL,
+ args_type tinyint NOT NULL DEFAULT 1,
+ ext_attrs nvarchar(256) NULL DEFAULT '',
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_job_task_01 ON sj_job_task (task_batch_id, task_status)
+GO
+CREATE INDEX idx_sj_job_task_02 ON sj_job_task (create_dt)
+GO
+CREATE INDEX idx_sj_job_task_03 ON sj_job_task (namespace_id, group_name)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟淇℃伅id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'job_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'璋冨害浠诲姟id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'task_batch_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐖舵墽琛屽櫒id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'parent_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц鐨勭姸鎬� 0銆佸け璐� 1銆佹垚鍔�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'task_status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閲嶈瘯娆℃暟',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'retry_count'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍔ㄦ�佸垎鐗囨墍澶勯樁娈� 1:map 2:reduce 3:mergeReduce',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'mr_stage'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍙跺瓙鑺傜偣',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'leaf'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'task_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'瀹㈡埛绔湴鍧� clientId#ip:port',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'client_info'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'宸ヤ綔娴佸叏灞�涓婁笅鏂�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'wf_context'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц缁撴灉',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'result_message'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц鏂规硶鍙傛暟',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'args_str'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍙傛暟绫诲瀷 ',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'args_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵╁睍瀛楁',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'ext_attrs'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟瀹炰緥',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task'
+GO
+
+-- sj_job_task_batch
+CREATE TABLE sj_job_task_batch
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL,
+ job_id bigint NOT NULL,
+ workflow_node_id bigint NOT NULL DEFAULT 0,
+ parent_workflow_node_id bigint NOT NULL DEFAULT 0,
+ workflow_task_batch_id bigint NOT NULL DEFAULT 0,
+ task_batch_status tinyint NOT NULL DEFAULT 0,
+ operation_reason tinyint NOT NULL DEFAULT 0,
+ execution_at bigint NOT NULL DEFAULT 0,
+ system_task_type tinyint NOT NULL DEFAULT 3,
+ parent_id nvarchar(64) NOT NULL DEFAULT '',
+ ext_attrs nvarchar(256) NULL DEFAULT '',
+ deleted tinyint NOT NULL DEFAULT 0,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_job_task_batch_01 ON sj_job_task_batch (job_id, task_batch_status)
+GO
+CREATE INDEX idx_sj_job_task_batch_02 ON sj_job_task_batch (create_dt)
+GO
+CREATE INDEX idx_sj_job_task_batch_03 ON sj_job_task_batch (namespace_id, group_name)
+GO
+CREATE INDEX idx_sj_job_task_batch_04 ON sj_job_task_batch (workflow_task_batch_id, workflow_node_id)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'job_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'宸ヤ綔娴佽妭鐐筰d',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'workflow_node_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'宸ヤ綔娴佷换鍔$埗鎵规id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'parent_workflow_node_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'宸ヤ綔娴佷换鍔℃壒娆d',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'workflow_task_batch_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟鎵规鐘舵�� 0銆佸け璐� 1銆佹垚鍔�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'task_batch_status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎿嶄綔鍘熷洜',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'operation_reason'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟鎵ц鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'execution_at'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟绫诲瀷 3銆丣OB浠诲姟 4銆乄ORKFLOW浠诲姟',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'system_task_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐖惰妭鐐�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'parent_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵╁睍瀛楁',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'ext_attrs'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫昏緫鍒犻櫎 1銆佸垹闄�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'deleted'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟鎵规',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_task_batch'
+GO
+
+-- sj_job_summary
+CREATE TABLE sj_job_summary
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL DEFAULT '',
+ business_id bigint NOT NULL,
+ system_task_type tinyint NOT NULL DEFAULT 3,
+ trigger_at datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ success_num int NOT NULL DEFAULT 0,
+ fail_num int NOT NULL DEFAULT 0,
+ fail_reason nvarchar(512) NOT NULL DEFAULT '',
+ stop_num int NOT NULL DEFAULT 0,
+ stop_reason nvarchar(512) NOT NULL DEFAULT '',
+ cancel_num int NOT NULL DEFAULT 0,
+ cancel_reason nvarchar(512) NOT NULL DEFAULT '',
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE UNIQUE INDEX uk_sj_job_summary_01 ON sj_job_summary (trigger_at, system_task_type, business_id)
+GO
+
+CREATE INDEX idx_sj_job_summary_01 ON sj_job_summary (namespace_id, group_name, business_id)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓氬姟id ( job_id鎴杦orkflow_id)',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'business_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟绫诲瀷 3銆丣OB浠诲姟 4銆乄ORKFLOW浠诲姟',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'system_task_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁熻鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'trigger_at'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц鎴愬姛-鏃ュ織鏁伴噺',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'success_num'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц澶辫触-鏃ュ織鏁伴噺',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'fail_num'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'澶辫触鍘熷洜',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'fail_reason'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц澶辫触-鏃ュ織鏁伴噺',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'stop_num'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'澶辫触鍘熷洜',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'stop_reason'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵ц澶辫触-鏃ュ織鏁伴噺',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'cancel_num'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'澶辫触鍘熷洜',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'cancel_reason'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'DashBoard_Job',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_summary'
+GO
+
+-- sj_retry_summary
+CREATE TABLE sj_retry_summary
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL DEFAULT '',
+ scene_name nvarchar(64) NOT NULL DEFAULT '',
+ trigger_at datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ running_num int NOT NULL DEFAULT 0,
+ finish_num int NOT NULL DEFAULT 0,
+ max_count_num int NOT NULL DEFAULT 0,
+ suspend_num int NOT NULL DEFAULT 0,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE UNIQUE INDEX uk_sj_retry_summary_01 ON sj_retry_summary (namespace_id, group_name, scene_name, trigger_at)
+GO
+
+CREATE INDEX idx_sj_retry_summary_01 ON sj_retry_summary (trigger_at)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_summary',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_summary',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_summary',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍦烘櫙鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_summary',
+ 'COLUMN', N'scene_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁熻鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_summary',
+ 'COLUMN', N'trigger_at'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閲嶈瘯涓�-鏃ュ織鏁伴噺',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_summary',
+ 'COLUMN', N'running_num'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閲嶈瘯瀹屾垚-鏃ュ織鏁伴噺',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_summary',
+ 'COLUMN', N'finish_num'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閲嶈瘯鍒拌揪鏈�澶ф鏁�-鏃ュ織鏁伴噺',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_summary',
+ 'COLUMN', N'max_count_num'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏆傚仠閲嶈瘯-鏃ュ織鏁伴噺',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_summary',
+ 'COLUMN', N'suspend_num'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_summary',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_summary',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'DashBoard_Retry',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_retry_summary'
+GO
+
+-- sj_workflow
+CREATE TABLE sj_workflow
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ workflow_name nvarchar(64) NOT NULL,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL,
+ workflow_status tinyint NOT NULL DEFAULT 1,
+ trigger_type tinyint NOT NULL,
+ trigger_interval nvarchar(255) NOT NULL,
+ next_trigger_at bigint NOT NULL,
+ block_strategy tinyint NOT NULL DEFAULT 1,
+ executor_timeout int NOT NULL DEFAULT 0,
+ description nvarchar(256) NOT NULL DEFAULT '',
+ flow_info nvarchar(max) NULL DEFAULT NULL,
+ wf_context nvarchar(max) NULL DEFAULT NULL,
+ notify_ids nvarchar(128) NOT NULL DEFAULT '',
+ bucket_index int NOT NULL DEFAULT 0,
+ version int NOT NULL,
+ owner_id bigint NULL DEFAULT NULL,
+ ext_attrs nvarchar(256) NULL DEFAULT '',
+ deleted tinyint NOT NULL DEFAULT 0,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_workflow_01 ON sj_workflow (create_dt)
+GO
+CREATE INDEX idx_sj_workflow_02 ON sj_workflow (namespace_id, group_name)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'宸ヤ綔娴佸悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'workflow_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'宸ヤ綔娴佺姸鎬� 0銆佸叧闂��1銆佸紑鍚�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'workflow_status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'瑙﹀彂绫诲瀷 1.CRON 琛ㄨ揪寮� 2. 鍥哄畾鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'trigger_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'闂撮殧鏃堕暱',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'trigger_interval'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓嬫瑙﹀彂鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'next_trigger_at'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'闃诲绛栫暐 1銆佷涪寮� 2銆佽鐩� 3銆佸苟琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'block_strategy'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟鎵ц瓒呮椂鏃堕棿锛屽崟浣嶇',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'executor_timeout'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎻忚堪',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'description'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'娴佺▼淇℃伅',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'flow_info'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婁笅鏂�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'wf_context'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫氱煡鍛婅鍦烘櫙閰嶇疆id鍒楄〃',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'notify_ids'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'bucket',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'bucket_index'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐗堟湰鍙�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'version'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'璐熻矗浜篿d',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'owner_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵╁睍瀛楁',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'ext_attrs'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫昏緫鍒犻櫎 1銆佸垹闄�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'deleted'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'宸ヤ綔娴�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow'
+GO
+
+-- sj_workflow_node
+CREATE TABLE sj_workflow_node
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ node_name nvarchar(64) NOT NULL,
+ group_name nvarchar(64) NOT NULL,
+ job_id bigint NOT NULL,
+ workflow_id bigint NOT NULL,
+ node_type tinyint NOT NULL DEFAULT 1,
+ expression_type tinyint NOT NULL DEFAULT 0,
+ fail_strategy tinyint NOT NULL DEFAULT 1,
+ workflow_node_status tinyint NOT NULL DEFAULT 1,
+ priority_level int NOT NULL DEFAULT 1,
+ node_info nvarchar(max) NULL DEFAULT NULL,
+ version int NOT NULL,
+ ext_attrs nvarchar(256) NULL DEFAULT '',
+ deleted tinyint NOT NULL DEFAULT 0,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_workflow_node_01 ON sj_workflow_node (create_dt)
+GO
+CREATE INDEX idx_sj_workflow_node_02 ON sj_workflow_node (namespace_id, group_name)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鑺傜偣鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'node_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟淇℃伅id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'job_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'宸ヤ綔娴両D',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'workflow_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'1銆佷换鍔¤妭鐐� 2銆佹潯浠惰妭鐐�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'node_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'1銆丼pEl銆�2銆丄viator 3銆丵L',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'expression_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'澶辫触绛栫暐 1銆佽烦杩� 2銆侀樆濉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'fail_strategy'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'宸ヤ綔娴佽妭鐐圭姸鎬� 0銆佸叧闂��1銆佸紑鍚�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'workflow_node_status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浼樺厛绾�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'priority_level'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鑺傜偣淇℃伅 ',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'node_info'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐗堟湰鍙�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'version'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵╁睍瀛楁',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'ext_attrs'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫昏緫鍒犻櫎 1銆佸垹闄�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'deleted'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'宸ヤ綔娴佽妭鐐�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_node'
+GO
+
+-- sj_workflow_task_batch
+CREATE TABLE sj_workflow_task_batch
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL,
+ workflow_id bigint NOT NULL,
+ task_batch_status tinyint NOT NULL DEFAULT 0,
+ operation_reason tinyint NOT NULL DEFAULT 0,
+ flow_info nvarchar(max) NULL DEFAULT NULL,
+ wf_context nvarchar(max) NULL DEFAULT NULL,
+ execution_at bigint NOT NULL DEFAULT 0,
+ ext_attrs nvarchar(256) NULL DEFAULT '',
+ version int NOT NULL DEFAULT 1,
+ deleted tinyint NOT NULL DEFAULT 0,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_workflow_task_batch_01 ON sj_workflow_task_batch (workflow_id, task_batch_status)
+GO
+CREATE INDEX idx_sj_workflow_task_batch_02 ON sj_workflow_task_batch (create_dt)
+GO
+CREATE INDEX idx_sj_workflow_task_batch_03 ON sj_workflow_task_batch (namespace_id, group_name)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'宸ヤ綔娴佷换鍔d',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'workflow_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟鎵规鐘舵�� 0銆佸け璐� 1銆佹垚鍔�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'task_batch_status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎿嶄綔鍘熷洜',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'operation_reason'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'娴佺▼淇℃伅',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'flow_info'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍏ㄥ眬涓婁笅鏂�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'wf_context'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟鎵ц鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'execution_at'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵╁睍瀛楁',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'ext_attrs'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐗堟湰鍙�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'version'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閫昏緫鍒犻櫎 1銆佸垹闄�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'deleted'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'宸ヤ綔娴佹壒娆�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_workflow_task_batch'
+GO
+
+-- sj_job_executor
+CREATE TABLE sj_job_executor
+(
+ id bigint NOT NULL PRIMARY KEY IDENTITY,
+ namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
+ group_name nvarchar(64) NOT NULL,
+ executor_info nvarchar(256) NOT NULL,
+ executor_type nvarchar(3) NOT NULL,
+ create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP
+)
+GO
+
+CREATE INDEX idx_sj_job_executor_01 ON sj_job_executor (namespace_id, group_name)
+GO
+CREATE INDEX idx_sj_job_executor_02 ON sj_job_executor (create_dt)
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_executor',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍛藉悕绌洪棿id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_executor',
+ 'COLUMN', N'namespace_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勫悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_executor',
+ 'COLUMN', N'group_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟鎵ц鍣ㄥ悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_executor',
+ 'COLUMN', N'executor_info'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'1:java 2:python 3:go',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_executor',
+ 'COLUMN', N'executor_type'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_executor',
+ 'COLUMN', N'create_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'淇敼鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_executor',
+ 'COLUMN', N'update_dt'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'浠诲姟鎵ц鍣ㄤ俊鎭�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sj_job_executor'
+GO
diff --git a/RuoYi-Vue-Plus/script/sql/sqlserver/sqlserver_ry_vue_5.X.sql b/RuoYi-Vue-Plus/script/sql/sqlserver/sqlserver_ry_vue_5.X.sql
new file mode 100755
index 0000000..295cc8e
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/sqlserver/sqlserver_ry_vue_5.X.sql
@@ -0,0 +1,3641 @@
+create table sys_social
+(
+ id bigint NOT NULL,
+ user_id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ auth_id nvarchar(255) NOT NULL,
+ source nvarchar(255) NOT NULL,
+ open_id nvarchar(255) NULL,
+ user_name nvarchar(30) NOT NULL,
+ nick_name nvarchar(30) DEFAULT ('') NULL,
+ email nvarchar(255) DEFAULT ('') NULL,
+ avatar nvarchar(500) DEFAULT ('') NULL,
+ access_token nvarchar(2000) NOT NULL,
+ expire_in bigint NULL,
+ refresh_token nvarchar(2000) NULL,
+ access_code nvarchar(255) NULL,
+ union_id nvarchar(255) NULL,
+ scope nvarchar(255) NULL,
+ token_type nvarchar(255) NULL,
+ id_token nvarchar(2000) NULL,
+ mac_algorithm nvarchar(255) NULL,
+ mac_key nvarchar(255) NULL,
+ code nvarchar(255) NULL,
+ oauth_token nvarchar(255) NULL,
+ oauth_token_secret nvarchar(255) NULL,
+ create_dept bigint,
+ create_by bigint,
+ create_time datetime2(7),
+ update_by bigint,
+ update_time datetime2(7),
+ del_flag nchar DEFAULT ('0') NULL,
+ CONSTRAINT PK__sys_social__B21E8F2427725F8A PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'id' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'user_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛id' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'骞冲彴+骞冲彴鍞竴id' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'auth_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鏉ユ簮' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'source'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'骞冲彴缂栧彿鍞竴id' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'open_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐧诲綍璐﹀彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'user_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鏄电О' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'nick_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛閭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'email'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶村儚鍦板潃' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'avatar'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鐨勬巿鏉冧护鐗�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'access_token'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鐨勬巿鏉冧护鐗岀殑鏈夋晥鏈燂紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'expire_in'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒锋柊浠ょ墝锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'refresh_token'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'access_code'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鐨� unionid' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'union_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鎺堜簣鐨勬潈闄愶紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'scope'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'涓埆骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'token_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'id token锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'id_token'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'mac_algorithm'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'mac_key'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鐨勬巿鏉僣ode锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'code'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'oauth_token'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'oauth_token_secret'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'del_flag'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'update_time'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'绀句細鍖栧叧绯昏〃',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social'
+GO
+
+CREATE TABLE sys_tenant
+(
+ id bigint NOT NULL,
+ tenant_id nvarchar(20) NOT NULL,
+ contact_user_name nvarchar(20) NULL,
+ contact_phone nvarchar(20) NULL,
+ company_name nvarchar(30) NULL,
+ license_number nvarchar(30) NULL,
+ address nvarchar(200) NULL,
+ intro nvarchar(200) NULL,
+ domain nvarchar(200) NULL,
+ remark nvarchar(200) NULL,
+ package_id bigint NULL,
+ expire_time datetime2(7) NULL,
+ account_count int DEFAULT ((-1)) NULL,
+ status nchar(1) DEFAULT ('0') NULL,
+ del_flag nchar(1) DEFAULT ('0') NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ CONSTRAINT PK__sys_tenant__B21E8F2427725F8A PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'id' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鑱旂郴浜�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'contact_user_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鑱旂郴鐢佃瘽' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'contact_phone'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'浼佷笟鍚嶇О' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'company_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'缁熶竴绀句細淇$敤浠g爜' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'license_number'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍦板潃' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'address'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'浼佷笟绠�浠�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'intro'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍩熷悕' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'domain'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶囨敞' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'remark'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛濂楅缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'package_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'杩囨湡鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'expire_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鏁伴噺锛�-1涓嶉檺鍒讹級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'account_count'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛鐘舵�侊紙0姝e父 1鍋滅敤锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'status'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'del_flag'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant',
+ 'COLUMN', N'update_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛琛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant'
+GO
+
+INSERT sys_tenant VALUES (1, N'000000', N'绠$悊缁�', N'15888888888', N'XXX鏈夐檺鍏徃', NULL, NULL, N'澶氱鎴烽�氱敤鍚庡彴绠$悊绠$悊绯荤粺', NULL, NULL, NULL, NULL, -1, N'0', N'0', 103, 1, getdate(), NULL, NULL)
+GO
+
+
+CREATE TABLE sys_tenant_package
+(
+ package_id bigint NOT NULL,
+ package_name nvarchar(20) NOT NULL,
+ menu_ids nvarchar(3000) NULL,
+ remark nvarchar(200) NULL,
+ menu_check_strictly tinyint DEFAULT ((1)) NULL,
+ status nchar(1) DEFAULT ('0') NULL,
+ del_flag nchar(1) DEFAULT ('0') NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ CONSTRAINT PK__sys_tenant_package__B21E8F2427725F8A PRIMARY KEY CLUSTERED (package_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛濂楅id' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant_package',
+ 'COLUMN', N'package_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'濂楅鍚嶇О' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant_package',
+ 'COLUMN', N'package_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍏宠仈鑿滃崟id' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant_package',
+ 'COLUMN', N'menu_ids'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶囨敞' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant_package',
+ 'COLUMN', N'remark'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛鐘舵�侊紙0姝e父 1鍋滅敤锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant_package',
+ 'COLUMN', N'status'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant_package',
+ 'COLUMN', N'del_flag'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant_package',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant_package',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant_package',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant_package',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant_package',
+ 'COLUMN', N'update_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛濂楅琛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_tenant_package'
+GO
+
+
+CREATE TABLE gen_table
+(
+ table_id bigint NOT NULL,
+ data_name nvarchar(200) DEFAULT '' NULL,
+ table_name nvarchar(200) DEFAULT '' NULL,
+ table_comment nvarchar(500) DEFAULT '' NULL,
+ sub_table_name nvarchar(64) NULL,
+ sub_table_fk_name nvarchar(64) NULL,
+ class_name nvarchar(100) DEFAULT '' NULL,
+ tpl_category nvarchar(200) DEFAULT ('crud') NULL,
+ package_name nvarchar(100) NULL,
+ module_name nvarchar(30) NULL,
+ business_name nvarchar(30) NULL,
+ function_name nvarchar(50) NULL,
+ function_author nvarchar(50) NULL,
+ gen_type nchar(1) DEFAULT ('0') NULL,
+ gen_path nvarchar(200) DEFAULT ('/') NULL,
+ options nvarchar(1000) NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ remark nvarchar(500) NULL,
+ CONSTRAINT PK__gen_tabl__B21E8F2427725F8A PRIMARY KEY CLUSTERED (table_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'table_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏁版嵁婧愬悕绉�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'data_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'琛ㄥ悕绉�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'table_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'琛ㄦ弿杩�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'table_comment'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍏宠仈瀛愯〃鐨勮〃鍚�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'sub_table_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛愯〃鍏宠仈鐨勫閿悕' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'sub_table_fk_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀹炰綋绫诲悕绉�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'class_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'浣跨敤鐨勬ā鏉匡紙crud鍗曡〃鎿嶄綔 tree鏍戣〃鎿嶄綔锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'tpl_category'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢熸垚鍖呰矾寰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'package_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢熸垚妯″潡鍚�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'module_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢熸垚涓氬姟鍚�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'business_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢熸垚鍔熻兘鍚�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'function_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢熸垚鍔熻兘浣滆��' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'function_author'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢熸垚浠g爜鏂瑰紡锛�0zip鍘嬬缉鍖� 1鑷畾涔夎矾寰勶級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'gen_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢熸垚璺緞锛堜笉濉粯璁ら」鐩矾寰勶級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'gen_path'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍏跺畠鐢熸垚閫夐」' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'options'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'update_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶囨敞' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'remark'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'浠g爜鐢熸垚涓氬姟琛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table'
+GO
+
+CREATE TABLE gen_table_column
+(
+ column_id bigint NOT NULL,
+ table_id bigint NULL,
+ column_name nvarchar(200) NULL,
+ column_comment nvarchar(500) NULL,
+ column_type nvarchar(100) NULL,
+ java_type nvarchar(500) NULL,
+ java_field nvarchar(200) NULL,
+ is_pk nchar(1) NULL,
+ is_increment nchar(1) NULL,
+ is_required nchar(1) NULL,
+ is_insert nchar(1) NULL,
+ is_edit nchar(1) NULL,
+ is_list nchar(1) NULL,
+ is_query nchar(1) NULL,
+ query_type nvarchar(200) DEFAULT ('EQ') NULL,
+ html_type nvarchar(200) NULL,
+ dict_type nvarchar(200) DEFAULT '' NULL,
+ sort int NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ CONSTRAINT PK__gen_tabl__E301851F2E68B4E8 PRIMARY KEY CLUSTERED (column_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'column_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'褰掑睘琛ㄧ紪鍙�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'table_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒楀悕绉�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'column_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒楁弿杩�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'column_comment'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒楃被鍨�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'column_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'JAVA绫诲瀷' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'java_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'JAVA瀛楁鍚�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'java_field'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄惁涓婚敭锛�1鏄級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'is_pk'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄惁鑷锛�1鏄級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'is_increment'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄惁蹇呭~锛�1鏄級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'is_required'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄惁涓烘彃鍏ュ瓧娈碉紙1鏄級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'is_insert'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄惁缂栬緫瀛楁锛�1鏄級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'is_edit'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄惁鍒楄〃瀛楁锛�1鏄級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'is_list'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄惁鏌ヨ瀛楁锛�1鏄級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'is_query'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏌ヨ鏂瑰紡锛堢瓑浜庛�佷笉绛変簬銆佸ぇ浜庛�佸皬浜庛�佽寖鍥达級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'query_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄剧ず绫诲瀷锛堟枃鏈銆佹枃鏈煙銆佷笅鎷夋銆佸閫夋銆佸崟閫夋銆佹棩鏈熸帶浠讹級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'html_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛楀吀绫诲瀷' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'dict_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鎺掑簭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'sort'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column',
+ 'COLUMN', N'update_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'浠g爜鐢熸垚涓氬姟琛ㄥ瓧娈�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table_column'
+GO
+
+CREATE TABLE sys_config
+(
+ config_id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT '000000' NULL,
+ config_name nvarchar(100) DEFAULT '' NULL,
+ config_key nvarchar(100) DEFAULT '' NULL,
+ config_value nvarchar(500) DEFAULT '' NULL,
+ config_type nchar(1) DEFAULT ('N') NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ remark nvarchar(500) NULL,
+ CONSTRAINT PK__sys_conf__4AD1BFF182643682 PRIMARY KEY CLUSTERED (config_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍙傛暟涓婚敭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_config',
+ 'COLUMN', N'config_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_config',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍙傛暟鍚嶇О' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_config',
+ 'COLUMN', N'config_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍙傛暟閿悕' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_config',
+ 'COLUMN', N'config_key'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍙傛暟閿��' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_config',
+ 'COLUMN', N'config_value'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绯荤粺鍐呯疆锛圷鏄� N鍚︼級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_config',
+ 'COLUMN', N'config_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_config',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_config',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_config',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_config',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_config',
+ 'COLUMN', N'update_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶囨敞' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_config',
+ 'COLUMN', N'remark'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍙傛暟閰嶇疆琛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_config'
+GO
+
+INSERT sys_config VALUES (1, N'000000', N'涓绘鏋堕〉-榛樿鐨偆鏍峰紡鍚嶇О', N'sys.index.skinName', N'skin-blue', N'Y', 103, 1, getdate(), NULL, NULL, N'钃濊壊 skin-blue銆佺豢鑹� skin-green銆佺传鑹� skin-purple銆佺孩鑹� skin-red銆侀粍鑹� skin-yellow')
+GO
+INSERT sys_config VALUES (2, N'000000', N'鐢ㄦ埛绠$悊-璐﹀彿鍒濆瀵嗙爜', N'sys.user.initPassword', N'123456', N'Y', 103, 1, getdate(), NULL, NULL, N'鍒濆鍖栧瘑鐮� 123456')
+GO
+INSERT sys_config VALUES (3, N'000000', N'涓绘鏋堕〉-渚ц竟鏍忎富棰�', N'sys.index.sideTheme', N'theme-dark', N'Y', 103, 1, getdate(), NULL, NULL, N'娣辫壊涓婚theme-dark锛屾祬鑹蹭富棰榯heme-light')
+GO
+INSERT sys_config VALUES (5, N'000000', N'璐﹀彿鑷姪-鏄惁寮�鍚敤鎴锋敞鍐屽姛鑳�', N'sys.account.registerUser', N'false', N'Y', 103, 1, getdate(), NULL, NULL, N'鏄惁寮�鍚敞鍐岀敤鎴峰姛鑳斤紙true寮�鍚紝false鍏抽棴锛�')
+GO
+INSERT sys_config VALUES (11, N'000000', N'OSS棰勮鍒楄〃璧勬簮寮�鍏�', N'sys.oss.previewListResource', N'true', N'Y', 103, 1, getdate(), NULL, NULL, N'true:寮�鍚�, false:鍏抽棴');
+GO
+
+CREATE TABLE sys_dept
+(
+ dept_id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ parent_id bigint DEFAULT ((0)) NULL,
+ ancestors nvarchar(500)DEFAULT '' NULL,
+ dept_name nvarchar(30) NULL,
+ dept_category nvarchar(100) DEFAULT '' NULL,
+ order_num int DEFAULT ((0)) NULL,
+ leader bigint NULL,
+ phone nvarchar(11) NULL,
+ email nvarchar(50) NULL,
+ status nchar(1) DEFAULT ('0') NULL,
+ del_flag nchar(1) DEFAULT ('0') NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ CONSTRAINT PK__sys_dept__DCA659747DE13804 PRIMARY KEY CLUSTERED (dept_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬id' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'dept_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐖堕儴闂╥d' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'parent_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绁栫骇鍒楄〃' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'ancestors'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬鍚嶇О' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'dept_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬绫诲埆缂栫爜' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'dept_category'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄剧ず椤哄簭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'order_num'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'璐熻矗浜�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'leader'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鑱旂郴鐢佃瘽' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'phone'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'閭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'email'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬鐘舵�侊紙0姝e父 1鍋滅敤锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'status'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'del_flag'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'update_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬琛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept'
+GO
+
+INSERT sys_dept VALUES (100, N'000000', 0, N'0', N'XXX绉戞妧', NULL, 0, NULL, N'15888888888', N'xxx@qq.com', N'0', N'0', 103, 1, getdate(), NULL, NULL)
+GO
+INSERT sys_dept VALUES (101, N'000000', 100, N'0,100', N'娣卞湷鎬诲叕鍙�', NULL, 1, NULL, N'15888888888', N'xxx@qq.com', N'0', N'0', 103, 1, getdate(), NULL, NULL)
+GO
+INSERT sys_dept VALUES (102, N'000000', 100, N'0,100', N'闀挎矙鍒嗗叕鍙�', NULL, 2, NULL, N'15888888888', N'xxx@qq.com', N'0', N'0', 103, 1, getdate(), NULL, NULL)
+GO
+INSERT sys_dept VALUES (103, N'000000', 101, N'0,100,101', N'鐮斿彂閮ㄩ棬', NULL, 1, 1, N'15888888888', N'xxx@qq.com', N'0', N'0', 103, 1, getdate(), NULL, NULL)
+GO
+INSERT sys_dept VALUES (104, N'000000', 101, N'0,100,101', N'甯傚満閮ㄩ棬', NULL, 2, NULL, N'15888888888', N'xxx@qq.com', N'0', N'0', 103, 1, getdate(), NULL, NULL)
+GO
+INSERT sys_dept VALUES (105, N'000000', 101, N'0,100,101', N'娴嬭瘯閮ㄩ棬', NULL, 3, NULL, N'15888888888', N'xxx@qq.com', N'0', N'0', 103, 1, getdate(), NULL, NULL)
+GO
+INSERT sys_dept VALUES (106, N'000000', 101, N'0,100,101', N'璐㈠姟閮ㄩ棬', NULL, 4, NULL, N'15888888888', N'xxx@qq.com', N'0', N'0', 103, 1, getdate(), NULL, NULL)
+GO
+INSERT sys_dept VALUES (107, N'000000', 101, N'0,100,101', N'杩愮淮閮ㄩ棬', NULL, 5, NULL, N'15888888888', N'xxx@qq.com', N'0', N'0', 103, 1, getdate(), NULL, NULL)
+GO
+INSERT sys_dept VALUES (108, N'000000', 102, N'0,100,102', N'甯傚満閮ㄩ棬', NULL, 1, NULL, N'15888888888', N'xxx@qq.com', N'0', N'0', 103, 1, getdate(), NULL, NULL)
+GO
+INSERT sys_dept VALUES (109, N'000000', 102, N'0,100,102', N'璐㈠姟閮ㄩ棬', NULL, 2, NULL, N'15888888888', N'xxx@qq.com', N'0', N'0', 103, 1, getdate(), NULL, NULL)
+GO
+
+CREATE TABLE sys_dict_data
+(
+ dict_code bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ dict_sort int DEFAULT ((0)) NULL,
+ dict_label nvarchar(100) DEFAULT '' NULL,
+ dict_value nvarchar(100) DEFAULT '' NULL,
+ dict_type nvarchar(100) DEFAULT '' NULL,
+ css_class nvarchar(100) NULL,
+ list_class nvarchar(100) NULL,
+ is_default nchar(1) DEFAULT ('N') NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ remark nvarchar(500) NULL,
+ CONSTRAINT PK__sys_dict__19CBC34B661AF3B3 PRIMARY KEY CLUSTERED (dict_code)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛楀吀缂栫爜' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'dict_code'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛楀吀缂栫爜' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛楀吀鎺掑簭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'dict_sort'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛楀吀鏍囩' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'dict_label'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛楀吀閿��' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'dict_value'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛楀吀绫诲瀷' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'dict_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏍峰紡灞炴�э紙鍏朵粬鏍峰紡鎵╁睍锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'css_class'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'琛ㄦ牸鍥炴樉鏍峰紡' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'list_class'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄惁榛樿锛圷鏄� N鍚︼級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'is_default'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'update_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶囨敞' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data',
+ 'COLUMN', N'remark'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛楀吀鏁版嵁琛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_data'
+GO
+
+INSERT sys_dict_data VALUES (1, N'000000', 1, N'鐢�', N'0', N'sys_user_sex', N'', N'', N'Y', 103, 1, getdate(), NULL, NULL, N'鎬у埆鐢�')
+GO
+INSERT sys_dict_data VALUES (2, N'000000', 2, N'濂�', N'1', N'sys_user_sex', N'', N'', N'N', 103, 1, getdate(), NULL, NULL, N'鎬у埆濂�')
+GO
+INSERT sys_dict_data VALUES (3, N'000000', 3, N'鏈煡', N'2', N'sys_user_sex', N'', N'', N'N', 103, 1, getdate(), NULL, NULL, N'鎬у埆鏈煡')
+GO
+INSERT sys_dict_data VALUES (4, N'000000', 1, N'鏄剧ず', N'0', N'sys_show_hide', N'', N'primary', N'Y', 103, 1, getdate(), NULL, NULL, N'鏄剧ず鑿滃崟')
+GO
+INSERT sys_dict_data VALUES (5, N'000000', 2, N'闅愯棌', N'1', N'sys_show_hide', N'', N'danger', N'N', 103, 1, getdate(), NULL, NULL, N'闅愯棌鑿滃崟')
+GO
+INSERT sys_dict_data VALUES (6, N'000000', 1, N'姝e父', N'0', N'sys_normal_disable', N'', N'primary', N'Y', 103, 1, getdate(), NULL, NULL, N'姝e父鐘舵��')
+GO
+INSERT sys_dict_data VALUES (7, N'000000', 2, N'鍋滅敤', N'1', N'sys_normal_disable', N'', N'danger', N'N', 103, 1, getdate(), NULL, NULL, N'鍋滅敤鐘舵��')
+GO
+INSERT sys_dict_data VALUES (8, N'000000', 1, N'姝e父', N'0', N'sys_job_status', N'', N'primary', N'Y', 103, 1, getdate(), NULL, NULL, N'姝e父鐘舵��')
+GO
+INSERT sys_dict_data VALUES (9, N'000000', 2, N'鏆傚仠', N'1', N'sys_job_status', N'', N'danger', N'N', 103, 1, getdate(), NULL, NULL, N'鍋滅敤鐘舵��')
+GO
+INSERT sys_dict_data VALUES (10, N'000000', 1, N'榛樿', N'DEFAULT', N'sys_job_group', N'', N'', N'Y', 103, 1, getdate(), NULL, NULL, N'榛樿鍒嗙粍')
+GO
+INSERT sys_dict_data VALUES (11, N'000000', 2, N'绯荤粺', N'SYSTEM', N'sys_job_group', N'', N'', N'N', 103, 1, getdate(), NULL, NULL, N'绯荤粺鍒嗙粍')
+GO
+INSERT sys_dict_data VALUES (12, N'000000', 1, N'鏄�', N'Y', N'sys_yes_no', N'', N'primary', N'Y', 103, 1, getdate(), NULL, NULL, N'绯荤粺榛樿鏄�')
+GO
+INSERT sys_dict_data VALUES (13, N'000000', 2, N'鍚�', N'N', N'sys_yes_no', N'', N'danger', N'N', 103, 1, getdate(), NULL, NULL, N'绯荤粺榛樿鍚�')
+GO
+INSERT sys_dict_data VALUES (14, N'000000', 1, N'閫氱煡', N'1', N'sys_notice_type', N'', N'warning', N'Y', 103, 1, getdate(), NULL, NULL, N'閫氱煡')
+GO
+INSERT sys_dict_data VALUES (15, N'000000', 2, N'鍏憡', N'2', N'sys_notice_type', N'', N'success', N'N', 103, 1, getdate(), NULL, NULL, N'鍏憡')
+GO
+INSERT sys_dict_data VALUES (16, N'000000', 1, N'姝e父', N'0', N'sys_notice_status', N'', N'primary', N'Y', 103, 1, getdate(), NULL, NULL, N'姝e父鐘舵��')
+GO
+INSERT sys_dict_data VALUES (17, N'000000', 2, N'鍏抽棴', N'1', N'sys_notice_status', N'', N'danger', N'N', 103, 1, getdate(), NULL, NULL, N'鍏抽棴鐘舵��')
+GO
+INSERT sys_dict_data VALUES (29, N'000000', 99, N'鍏朵粬', N'0', N'sys_oper_type', N'', N'info', N'N', 103, 1, getdate(), NULL, NULL, N'鍏朵粬鎿嶄綔');
+GO
+INSERT sys_dict_data VALUES (18, N'000000', 1, N'鏂板', N'1', N'sys_oper_type', N'', N'info', N'N', 103, 1, getdate(), NULL, NULL, N'鏂板鎿嶄綔')
+GO
+INSERT sys_dict_data VALUES (19, N'000000', 2, N'淇敼', N'2', N'sys_oper_type', N'', N'info', N'N', 103, 1, getdate(), NULL, NULL, N'淇敼鎿嶄綔')
+GO
+INSERT sys_dict_data VALUES (20, N'000000', 3, N'鍒犻櫎', N'3', N'sys_oper_type', N'', N'danger', N'N', 103, 1, getdate(), NULL, NULL, N'鍒犻櫎鎿嶄綔')
+GO
+INSERT sys_dict_data VALUES (21, N'000000', 4, N'鎺堟潈', N'4', N'sys_oper_type', N'', N'primary', N'N', 103, 1, getdate(), NULL, NULL, N'鎺堟潈鎿嶄綔')
+GO
+INSERT sys_dict_data VALUES (22, N'000000', 5, N'瀵煎嚭', N'5', N'sys_oper_type', N'', N'warning', N'N', 103, 1, getdate(), NULL, NULL, N'瀵煎嚭鎿嶄綔')
+GO
+INSERT sys_dict_data VALUES (23, N'000000', 6, N'瀵煎叆', N'6', N'sys_oper_type', N'', N'warning', N'N', 103, 1, getdate(), NULL, NULL, N'瀵煎叆鎿嶄綔')
+GO
+INSERT sys_dict_data VALUES (24, N'000000', 7, N'寮洪��', N'7', N'sys_oper_type', N'', N'danger', N'N', 103, 1, getdate(), NULL, NULL, N'寮洪��鎿嶄綔')
+GO
+INSERT sys_dict_data VALUES (25, N'000000', 8, N'鐢熸垚浠g爜', N'8', N'sys_oper_type', N'', N'warning', N'N', 103, 1, getdate(), NULL, NULL, N'鐢熸垚鎿嶄綔')
+GO
+INSERT sys_dict_data VALUES (26, N'000000', 9, N'娓呯┖鏁版嵁', N'9', N'sys_oper_type', N'', N'danger', N'N', 103, 1, getdate(), NULL, NULL, N'娓呯┖鎿嶄綔')
+GO
+INSERT sys_dict_data VALUES (27, N'000000', 1, N'鎴愬姛', N'0', N'sys_common_status', N'', N'primary', N'N', 103, 1, getdate(), NULL, NULL, N'姝e父鐘舵��')
+GO
+INSERT sys_dict_data VALUES (28, N'000000', 2, N'澶辫触', N'1', N'sys_common_status', N'', N'danger', N'N', 103, 1, getdate(), NULL, NULL, N'鍋滅敤鐘舵��')
+GO
+INSERT sys_dict_data VALUES (30, N'000000', 0, N'瀵嗙爜璁よ瘉', N'password', N'sys_grant_type', N'', N'default', N'N', 103, 1, getdate(), NULL, NULL, N'瀵嗙爜璁よ瘉')
+GO
+INSERT sys_dict_data VALUES (31, N'000000', 0, N'鐭俊璁よ瘉', N'sms', N'sys_grant_type', N'', N'default', N'N', 103, 1, getdate(), NULL, NULL, N'鐭俊璁よ瘉')
+GO
+INSERT sys_dict_data VALUES (32, N'000000', 0, N'閭欢璁よ瘉', N'email', N'sys_grant_type', N'', N'default', N'N', 103, 1, getdate(), NULL, NULL, N'閭欢璁よ瘉')
+GO
+INSERT sys_dict_data VALUES (33, N'000000', 0, N'灏忕▼搴忚璇�', N'xcx', N'sys_grant_type', N'', N'default', N'N', 103, 1, getdate(), NULL, NULL, N'灏忕▼搴忚璇�')
+GO
+INSERT sys_dict_data VALUES (34, N'000000', 0, N'涓夋柟鐧诲綍璁よ瘉', N'social', N'sys_grant_type', N'', N'default', N'N', 103, 1, getdate(), NULL, NULL, N'涓夋柟鐧诲綍璁よ瘉')
+GO
+INSERT sys_dict_data VALUES (35, N'000000', 0, N'PC', N'pc', N'sys_device_type', N'', N'default', N'N', 103, 1, getdate(), NULL, NULL, N'PC')
+GO
+INSERT sys_dict_data VALUES (36, N'000000', 0, N'瀹夊崜', N'android', N'sys_device_type', N'', N'default', N'N', 103, 1, getdate(), NULL, NULL, N'瀹夊崜')
+GO
+INSERT sys_dict_data VALUES (37, N'000000', 0, N'iOS', N'ios', N'sys_device_type', N'', N'default', N'N', 103, 1, getdate(), NULL, NULL, N'iOS')
+GO
+INSERT sys_dict_data VALUES (38, N'000000', 0, N'灏忕▼搴�', N'xcx', N'sys_device_type', N'', N'default', N'N', 103, 1, getdate(), NULL, NULL, N'灏忕▼搴�')
+GO
+
+CREATE TABLE sys_dict_type
+(
+ dict_id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ dict_name nvarchar(100) DEFAULT '' NULL,
+ dict_type nvarchar(100) DEFAULT '' NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ remark nvarchar(500) NULL,
+ CONSTRAINT PK__sys_dict__3BD4186C409C5391 PRIMARY KEY CLUSTERED (dict_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+CREATE NONCLUSTERED INDEX sys_dict_type_index1 ON sys_dict_type (tenant_id, dict_type)
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛楀吀涓婚敭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_type',
+ 'COLUMN', N'dict_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛楀吀涓婚敭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_type',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛楀吀鍚嶇О' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_type',
+ 'COLUMN', N'dict_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛楀吀绫诲瀷' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_type',
+ 'COLUMN', N'dict_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_type',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_type',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_type',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_type',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_type',
+ 'COLUMN', N'update_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶囨敞' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_type',
+ 'COLUMN', N'remark'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀛楀吀绫诲瀷琛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dict_type'
+GO
+
+INSERT sys_dict_type VALUES (1, N'000000', N'鐢ㄦ埛鎬у埆', N'sys_user_sex', 103, 1, getdate(), NULL, NULL, N'鐢ㄦ埛鎬у埆鍒楄〃')
+GO
+INSERT sys_dict_type VALUES (2, N'000000', N'鑿滃崟鐘舵��', N'sys_show_hide', 103, 1, getdate(), NULL, NULL, N'鑿滃崟鐘舵�佸垪琛�')
+GO
+INSERT sys_dict_type VALUES (3, N'000000', N'绯荤粺寮�鍏�', N'sys_normal_disable', 103, 1, getdate(), NULL, NULL, N'绯荤粺寮�鍏冲垪琛�')
+GO
+INSERT sys_dict_type VALUES (4, N'000000', N'浠诲姟鐘舵��', N'sys_job_status', 103, 1, getdate(), NULL, NULL, N'浠诲姟鐘舵�佸垪琛�')
+GO
+INSERT sys_dict_type VALUES (5, N'000000', N'浠诲姟鍒嗙粍', N'sys_job_group', 103, 1, getdate(), NULL, NULL, N'浠诲姟鍒嗙粍鍒楄〃')
+GO
+INSERT sys_dict_type VALUES (6, N'000000', N'绯荤粺鏄惁', N'sys_yes_no', 103, 1, getdate(), NULL, NULL, N'绯荤粺鏄惁鍒楄〃')
+GO
+INSERT sys_dict_type VALUES (7, N'000000', N'閫氱煡绫诲瀷', N'sys_notice_type', 103, 1, getdate(), NULL, NULL, N'閫氱煡绫诲瀷鍒楄〃')
+GO
+INSERT sys_dict_type VALUES (8, N'000000', N'閫氱煡鐘舵��', N'sys_notice_status', 103, 1, getdate(), NULL, NULL, N'閫氱煡鐘舵�佸垪琛�')
+GO
+INSERT sys_dict_type VALUES (9, N'000000', N'鎿嶄綔绫诲瀷', N'sys_oper_type', 103, 1, getdate(), NULL, NULL, N'鎿嶄綔绫诲瀷鍒楄〃')
+GO
+INSERT sys_dict_type VALUES (10, N'000000', N'绯荤粺鐘舵��', N'sys_common_status', 103, 1, getdate(), NULL, NULL, N'鐧诲綍鐘舵�佸垪琛�')
+GO
+INSERT sys_dict_type VALUES (11, N'000000', N'鎺堟潈绫诲瀷', N'sys_grant_type', 103, 1, getdate(), NULL, NULL, N'璁よ瘉鎺堟潈绫诲瀷')
+GO
+INSERT sys_dict_type VALUES (12, N'000000', N'璁惧绫诲瀷', N'sys_device_type', 103, 1, getdate(), NULL, NULL, N'瀹㈡埛绔澶囩被鍨�')
+GO
+
+CREATE TABLE sys_logininfor
+(
+ info_id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ user_name nvarchar(50) DEFAULT '' NULL,
+ client_key nvarchar(32) DEFAULT '' NULL,
+ device_type nvarchar(32) DEFAULT '' NULL,
+ ipaddr nvarchar(128) DEFAULT '' NULL,
+ login_location nvarchar(255) DEFAULT '' NULL,
+ browser nvarchar(50) DEFAULT '' NULL,
+ os nvarchar(50) DEFAULT '' NULL,
+ status nchar(1) DEFAULT ('0') NULL,
+ msg nvarchar(255) DEFAULT '' NULL,
+ login_time datetime2(7) NULL,
+ CONSTRAINT PK__sys_logi__3D8A9C1A1854AE10 PRIMARY KEY CLUSTERED (info_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+CREATE NONCLUSTERED INDEX idx_sys_logininfor_s ON sys_logininfor (status)
+GO
+CREATE NONCLUSTERED INDEX idx_sys_logininfor_lt ON sys_logininfor (login_time)
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'璁块棶ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'info_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛璐﹀彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'user_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀹㈡埛绔�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'client_key'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'璁惧绫诲瀷' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'device_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐧诲綍IP鍦板潃' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'ipaddr'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐧诲綍鍦扮偣' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'login_location'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'娴忚鍣ㄧ被鍨�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'browser'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鎿嶄綔绯荤粺' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'os'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐧诲綍鐘舵�侊紙0鎴愬姛 1澶辫触锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'status'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鎻愮ず娑堟伅' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'msg'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'璁块棶鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'login_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绯荤粺璁块棶璁板綍' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor'
+GO
+
+CREATE TABLE sys_menu
+(
+ menu_id bigint NOT NULL,
+ menu_name nvarchar(50) NOT NULL,
+ parent_id bigint DEFAULT ((0)) NULL,
+ order_num int DEFAULT ((0)) NULL,
+ path nvarchar(200) DEFAULT '' NULL,
+ component nvarchar(255) NULL,
+ query_param nvarchar(255) NULL,
+ is_frame int DEFAULT ((1)) NULL,
+ is_cache int DEFAULT ((0)) NULL,
+ menu_type nchar(1) DEFAULT '' NULL,
+ visible nchar(1) DEFAULT ((0)) NULL,
+ status nchar(1) DEFAULT ((0)) NULL,
+ perms nvarchar(100) NULL,
+ icon nvarchar(100) DEFAULT ('#') NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ remark nvarchar(500) DEFAULT '' NULL,
+ CONSTRAINT PK__sys_menu__4CA0FADCF8545C58 PRIMARY KEY CLUSTERED (menu_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鑿滃崟ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'menu_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鑿滃崟鍚嶇О' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'menu_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐖惰彍鍗旾D' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'parent_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄剧ず椤哄簭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'order_num'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'璺敱鍦板潃' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'path'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'缁勪欢璺緞' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'component'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'璺敱鍙傛暟' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'query_param'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄惁涓哄閾撅紙0鏄� 1鍚︼級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'is_frame'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄惁缂撳瓨锛�0缂撳瓨 1涓嶇紦瀛橈級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'is_cache'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鑿滃崟绫诲瀷锛圡鐩綍 C鑿滃崟 F鎸夐挳锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'menu_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄剧ず鐘舵�侊紙0鏄剧ず 1闅愯棌锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'visible'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鑿滃崟鐘舵�侊紙0姝e父 1鍋滅敤锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'status'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏉冮檺鏍囪瘑' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'perms'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鑿滃崟鍥炬爣' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'icon'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'update_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶囨敞' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu',
+ 'COLUMN', N'remark'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鑿滃崟鏉冮檺琛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_menu'
+GO
+
+INSERT sys_menu VALUES (1, N'绯荤粺绠$悊', 0, 1, N'system', NULL, N'', 1, 0, N'M', N'0', N'0', N'', N'system', 103, 1, getdate(), NULL, NULL, N'绯荤粺绠$悊鐩綍')
+GO
+INSERT sys_menu VALUES (6, N'绉熸埛绠$悊', 0, 2, N'tenant', NULL, N'', 1, 0, N'M', N'0', N'0', N'', N'chart', 103, 1, getdate(), NULL, NULL, N'绉熸埛绠$悊鐩綍')
+GO
+INSERT sys_menu VALUES (2, N'绯荤粺鐩戞帶', 0, 3, N'monitor', NULL, N'', 1, 0, N'M', N'0', N'0', N'', N'monitor', 103, 1, getdate(), NULL, NULL, N'绯荤粺鐩戞帶鐩綍')
+GO
+INSERT sys_menu VALUES (3, N'绯荤粺宸ュ叿', 0, 4, N'tool', NULL, N'', 1, 0, N'M', N'0', N'0', N'', N'tool', 103, 1, getdate(), NULL, NULL, N'绯荤粺宸ュ叿鐩綍')
+GO
+INSERT sys_menu VALUES (4, N'PLUS瀹樼綉', 0, 5, N'https://gitee.com/dromara/RuoYi-Vue-Plus', null, N'', 0, 0, N'M', N'0', N'0', N'', N'guide', 103, 1, getdate(), null, null, N'RuoYi-Vue-Plus瀹樼綉鍦板潃');
+GO
+INSERT sys_menu VALUES (5, N'娴嬭瘯鑿滃崟', 0, 5, N'demo', NULL, N'', 1, 0, N'M', N'0', N'0', NULL, N'star', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (100, N'鐢ㄦ埛绠$悊', 1, 1, N'user', N'system/user/index', N'', 1, 0, N'C', N'0', N'0', N'system:user:list', N'user', 103, 1, getdate(), NULL, NULL, N'鐢ㄦ埛绠$悊鑿滃崟')
+GO
+INSERT sys_menu VALUES (101, N'瑙掕壊绠$悊', 1, 2, N'role', N'system/role/index', N'', 1, 0, N'C', N'0', N'0', N'system:role:list', N'peoples', 103, 1, getdate(), NULL, NULL, N'瑙掕壊绠$悊鑿滃崟')
+GO
+INSERT sys_menu VALUES (102, N'鑿滃崟绠$悊', 1, 3, N'menu', N'system/menu/index', N'', 1, 0, N'C', N'0', N'0', N'system:menu:list', N'tree-table', 103, 1, getdate(), NULL, NULL, N'鑿滃崟绠$悊鑿滃崟')
+GO
+INSERT sys_menu VALUES (103, N'閮ㄩ棬绠$悊', 1, 4, N'dept', N'system/dept/index', N'', 1, 0, N'C', N'0', N'0', N'system:dept:list', N'tree', 103, 1, getdate(), NULL, NULL, N'閮ㄩ棬绠$悊鑿滃崟')
+GO
+INSERT sys_menu VALUES (104, N'宀椾綅绠$悊', 1, 5, N'post', N'system/post/index', N'', 1, 0, N'C', N'0', N'0', N'system:post:list', N'post', 103, 1, getdate(), NULL, NULL, N'宀椾綅绠$悊鑿滃崟')
+GO
+INSERT sys_menu VALUES (105, N'瀛楀吀绠$悊', 1, 6, N'dict', N'system/dict/index', N'', 1, 0, N'C', N'0', N'0', N'system:dict:list', N'dict', 103, 1, getdate(), NULL, NULL, N'瀛楀吀绠$悊鑿滃崟')
+GO
+INSERT sys_menu VALUES (106, N'鍙傛暟璁剧疆', 1, 7, N'config', N'system/config/index', N'', 1, 0, N'C', N'0', N'0', N'system:config:list', N'edit', 103, 1, getdate(), NULL, NULL, N'鍙傛暟璁剧疆鑿滃崟')
+GO
+INSERT sys_menu VALUES (107, N'閫氱煡鍏憡', 1, 8, N'notice', N'system/notice/index', N'', 1, 0, N'C', N'0', N'0', N'system:notice:list', N'message', 103, 1, getdate(), NULL, NULL, N'閫氱煡鍏憡鑿滃崟')
+GO
+INSERT sys_menu VALUES (108, N'鏃ュ織绠$悊', 1, 9, N'log', N'', N'', 1, 0, N'M', N'0', N'0', N'', N'log', 103, 1, getdate(), NULL, NULL, N'鏃ュ織绠$悊鑿滃崟')
+GO
+INSERT sys_menu VALUES (109, N'鍦ㄧ嚎鐢ㄦ埛', 2, 1, N'online', N'monitor/online/index', N'', 1, 0, N'C', N'0', N'0', N'monitor:online:list', N'online', 103, 1, getdate(), NULL, NULL, N'鍦ㄧ嚎鐢ㄦ埛鑿滃崟')
+GO
+INSERT sys_menu VALUES (113, N'缂撳瓨鐩戞帶', 2, 5, N'cache', N'monitor/cache/index', N'', 1, 0, N'C', N'0', N'0', N'monitor:cache:list', N'redis', 103, 1, getdate(), NULL, NULL, N'缂撳瓨鐩戞帶鑿滃崟')
+GO
+INSERT sys_menu VALUES (115, N'浠g爜鐢熸垚', 3, 2, N'gen', N'tool/gen/index', N'', 1, 0, N'C', N'0', N'0', N'tool:gen:list', N'code', 103, 1, getdate(), NULL, NULL, N'浠g爜鐢熸垚鑿滃崟')
+GO
+INSERT sys_menu VALUES (121, N'绉熸埛绠$悊', 6, 1, N'tenant', N'system/tenant/index', N'', 1, 0, N'C', N'0', N'0', N'system:tenant:list', N'code', 103, 1, getdate(), NULL, NULL, N'绉熸埛绠$悊鑿滃崟')
+GO
+INSERT sys_menu VALUES (122, N'绉熸埛濂楅绠$悊', 6, 2, N'tenantPackage', N'system/tenantPackage/index', N'', 1, 0, N'C', N'0', N'0', N'system:tenantPackage:list', N'code', 103, 1, getdate(), NULL, NULL, N'绉熸埛濂楅绠$悊鑿滃崟')
+GO
+INSERT sys_menu VALUES (123, N'瀹㈡埛绔鐞�', 1, 11, N'client', N'system/client/index', N'', 1, 0, N'C', N'0', N'0', N'system:client:list', N'international', 103, 1, getdate(), NULL, NULL, N'瀹㈡埛绔鐞嗚彍鍗�')
+GO
+INSERT sys_menu VALUES (116, N'淇敼鐢熸垚閰嶇疆', 3, 2, N'gen-edit/index/:tableId', N'tool/gen/editTable', N'', 1, 1, N'C', N'1', N'0', N'tool:gen:edit', N'#', 103, 1, getdate(), null, null, N'/tool/gen');
+GO
+INSERT sys_menu VALUES (130, N'鍒嗛厤鐢ㄦ埛', 1, 2, N'role-auth/user/:roleId', N'system/role/authUser', N'', 1, 1, N'C', N'1', N'0', N'system:role:edit', N'#', 103, 1, getdate(), null, null, N'/system/role');
+GO
+INSERT sys_menu VALUES (131, N'鍒嗛厤瑙掕壊', 1, 1, N'user-auth/role/:userId', N'system/user/authRole', N'', 1, 1, N'C', N'1', N'0', N'system:user:edit', N'#', 103, 1, getdate(), null, null, N'/system/user');
+GO
+INSERT sys_menu VALUES (132, N'瀛楀吀鏁版嵁', 1, 6, N'dict-data/index/:dictId', N'system/dict/data', N'', 1, 1, N'C', N'1', N'0', N'system:dict:list', N'#', 103, 1, getdate(), null, null, N'/system/dict');
+GO
+INSERT sys_menu VALUES (133, N'鏂囦欢閰嶇疆绠$悊', 1, 10, N'oss-config/index', N'system/oss/config', N'', 1, 1, N'C', N'1', N'0', N'system:ossConfig:list', N'#', 103, 1, getdate(), null, null, N'/system/oss');
+GO
+
+INSERT sys_menu VALUES (117, N'Admin鐩戞帶', 2, 5, N'Admin', N'monitor/admin/index', N'', 1, 0, N'C', N'0', N'0', N'monitor:admin:list', N'dashboard', 103, 1, getdate(), NULL, NULL, N'Admin鐩戞帶鑿滃崟');
+GO
+INSERT sys_menu VALUES (118, N'鏂囦欢绠$悊', 1, 10, N'oss', N'system/oss/index', N'', 1, 0, N'C', '0', N'0', N'system:oss:list', N'upload', 103, 1, getdate(), NULL, NULL, N'鏂囦欢绠$悊鑿滃崟');
+GO
+INSERT sys_menu VALUES (120, N'浠诲姟璋冨害涓績', 2, 5, N'snailjob', N'monitor/snailjob/index', N'', 1, 0, N'C', N'0', N'0', N'monitor:snailjob:list', N'job', 103, 1, getdate(), NULL, NULL, N'SnailJob鎺у埗鍙拌彍鍗�');
+GO
+INSERT sys_menu VALUES (500, N'鎿嶄綔鏃ュ織', 108, 1, N'operlog', N'monitor/operlog/index', N'', 1, 0, N'C', N'0', N'0', N'monitor:operlog:list', N'form', 103, 1, getdate(), NULL, NULL, N'鎿嶄綔鏃ュ織鑿滃崟')
+GO
+INSERT sys_menu VALUES (501, N'鐧诲綍鏃ュ織', 108, 2, N'logininfor', N'monitor/logininfor/index', N'', 1, 0, N'C', N'0', N'0', N'monitor:logininfor:list', N'logininfor', 103, 1, getdate(), NULL, NULL, N'鐧诲綍鏃ュ織鑿滃崟')
+GO
+INSERT sys_menu VALUES (1001, N'鐢ㄦ埛鏌ヨ', 100, 1, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:user:query', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1002, N'鐢ㄦ埛鏂板', 100, 2, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:user:add', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1003, N'鐢ㄦ埛淇敼', 100, 3, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:user:edit', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1004, N'鐢ㄦ埛鍒犻櫎', 100, 4, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:user:remove', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1005, N'鐢ㄦ埛瀵煎嚭', 100, 5, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:user:export', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1006, N'鐢ㄦ埛瀵煎叆', 100, 6, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:user:import', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1007, N'閲嶇疆瀵嗙爜', 100, 7, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:user:resetPwd', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1008, N'瑙掕壊鏌ヨ', 101, 1, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:role:query', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1009, N'瑙掕壊鏂板', 101, 2, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:role:add', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1010, N'瑙掕壊淇敼', 101, 3, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:role:edit', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1011, N'瑙掕壊鍒犻櫎', 101, 4, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:role:remove', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1012, N'瑙掕壊瀵煎嚭', 101, 5, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:role:export', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1013, N'鑿滃崟鏌ヨ', 102, 1, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:menu:query', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1014, N'鑿滃崟鏂板', 102, 2, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:menu:add', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1015, N'鑿滃崟淇敼', 102, 3, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:menu:edit', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1016, N'鑿滃崟鍒犻櫎', 102, 4, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:menu:remove', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1017, N'閮ㄩ棬鏌ヨ', 103, 1, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:dept:query', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1018, N'閮ㄩ棬鏂板', 103, 2, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:dept:add', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1019, N'閮ㄩ棬淇敼', 103, 3, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:dept:edit', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1020, N'閮ㄩ棬鍒犻櫎', 103, 4, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:dept:remove', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1021, N'宀椾綅鏌ヨ', 104, 1, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:post:query', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1022, N'宀椾綅鏂板', 104, 2, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:post:add', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1023, N'宀椾綅淇敼', 104, 3, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:post:edit', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1024, N'宀椾綅鍒犻櫎', 104, 4, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:post:remove', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1025, N'宀椾綅瀵煎嚭', 104, 5, N'', N'', N'', 1, 0, N'F', N'0', N'0', N'system:post:export', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1026, N'瀛楀吀鏌ヨ', 105, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:dict:query', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1027, N'瀛楀吀鏂板', 105, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:dict:add', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1028, N'瀛楀吀淇敼', 105, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:dict:edit', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1029, N'瀛楀吀鍒犻櫎', 105, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:dict:remove', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1030, N'瀛楀吀瀵煎嚭', 105, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:dict:export', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1031, N'鍙傛暟鏌ヨ', 106, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:config:query', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1032, N'鍙傛暟鏂板', 106, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:config:add', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1033, N'鍙傛暟淇敼', 106, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:config:edit', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1034, N'鍙傛暟鍒犻櫎', 106, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:config:remove', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1035, N'鍙傛暟瀵煎嚭', 106, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:config:export', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1036, N'鍏憡鏌ヨ', 107, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:notice:query', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1037, N'鍏憡鏂板', 107, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:notice:add', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1038, N'鍏憡淇敼', 107, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:notice:edit', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1039, N'鍏憡鍒犻櫎', 107, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:notice:remove', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1040, N'鎿嶄綔鏌ヨ', 500, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'monitor:operlog:query', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1041, N'鎿嶄綔鍒犻櫎', 500, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'monitor:operlog:remove', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1042, N'鏃ュ織瀵煎嚭', 500, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'monitor:operlog:export', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1043, N'鐧诲綍鏌ヨ', 501, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'monitor:logininfor:query', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1044, N'鐧诲綍鍒犻櫎', 501, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'monitor:logininfor:remove', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1045, N'鏃ュ織瀵煎嚭', 501, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'monitor:logininfor:export', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1050, N'璐︽埛瑙i攣', 501, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'monitor:logininfor:unlock', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1046, N'鍦ㄧ嚎鏌ヨ', 109, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'monitor:online:query', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1047, N'鎵归噺寮洪��', 109, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'monitor:online:batchLogout', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1048, N'鍗曟潯寮洪��', 109, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'monitor:online:forceLogout', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1055, N'鐢熸垚鏌ヨ', 115, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'tool:gen:query', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1056, N'鐢熸垚淇敼', 115, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'tool:gen:edit', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1057, N'鐢熸垚鍒犻櫎', 115, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'tool:gen:remove', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1058, N'瀵煎叆浠g爜', 115, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'tool:gen:import', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1059, N'棰勮浠g爜', 115, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'tool:gen:preview', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_menu VALUES (1060, N'鐢熸垚浠g爜', 115, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'tool:gen:code', N'#', 103, 1, getdate(), NULL, NULL, N'')
+GO
+-- oss鐩稿叧鎸夐挳
+INSERT sys_menu VALUES (1600, N'鏂囦欢鏌ヨ', 118, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:oss:query', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1601, N'鏂囦欢涓婁紶', 118, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:oss:upload', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1602, N'鏂囦欢涓嬭浇', 118, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:oss:download', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1603, N'鏂囦欢鍒犻櫎', 118, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:oss:remove', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1620, N'閰嶇疆鍒楄〃', 118, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:ossConfig:list', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1621, N'閰嶇疆娣诲姞', 118, 6, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:ossConfig:add', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1622, N'閰嶇疆缂栬緫', 118, 6, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:ossConfig:edit', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1623, N'閰嶇疆鍒犻櫎', 118, 6, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:ossConfig:remove', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+-- 绉熸埛绠$悊鐩稿叧鎸夐挳
+INSERT sys_menu VALUES (1606, N'绉熸埛鏌ヨ', 121, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:tenant:query', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1607, N'绉熸埛鏂板', 121, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:tenant:add', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1608, N'绉熸埛淇敼', 121, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:tenant:edit', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1609, N'绉熸埛鍒犻櫎', 121, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:tenant:remove', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1610, N'绉熸埛瀵煎嚭', 121, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:tenant:export', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+-- 绉熸埛濂楅绠$悊鐩稿叧鎸夐挳
+INSERT sys_menu VALUES (1611, N'绉熸埛濂楅鏌ヨ', 122, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:tenantPackage:query', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1612, N'绉熸埛濂楅鏂板', 122, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:tenantPackage:add', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1613, N'绉熸埛濂楅淇敼', 122, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:tenantPackage:edit', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1614, N'绉熸埛濂楅鍒犻櫎', 122, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:tenantPackage:remove', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1615, N'绉熸埛濂楅瀵煎嚭', 122, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:tenantPackage:export', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+-- 瀹㈡埛绔鐞嗘寜閽�
+INSERT sys_menu VALUES (1061, N'瀹㈡埛绔鐞嗘煡璇�', 123, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:query', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1062, N'瀹㈡埛绔鐞嗘柊澧�', 123, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:add', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1063, N'瀹㈡埛绔鐞嗕慨鏀�', 123, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:edit', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1064, N'瀹㈡埛绔鐞嗗垹闄�', 123, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:remove', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1065, N'瀹㈡埛绔鐞嗗鍑�', 123, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:export', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+-- 娴嬭瘯鑿滃崟
+INSERT sys_menu VALUES (1500, N'娴嬭瘯鍗曡〃', 5, 1, N'demo', N'demo/demo/index', N'', 1, 0, N'C', N'0', N'0', N'demo:demo:list', N'#', 103, 1, getdate(), NULL, NULL, N'娴嬭瘯鍗曡〃鑿滃崟');
+GO
+INSERT sys_menu VALUES (1501, N'娴嬭瘯鍗曡〃鏌ヨ', 1500, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'demo:demo:query', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1502, N'娴嬭瘯鍗曡〃鏂板', 1500, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'demo:demo:add', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1503, N'娴嬭瘯鍗曡〃淇敼', 1500, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'demo:demo:edit', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1504, N'娴嬭瘯鍗曡〃鍒犻櫎', 1500, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'demo:demo:remove', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1505, N'娴嬭瘯鍗曡〃瀵煎嚭', 1500, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'demo:demo:export', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+
+INSERT sys_menu VALUES (1506, N'娴嬭瘯鏍戣〃', 5, 1, N'tree', N'demo/tree/index', N'', 1, 0, N'C', N'0', N'0', N'demo:tree:list', N'#', 103, 1, getdate(), NULL, NULL, N'娴嬭瘯鏍戣〃鑿滃崟');
+GO
+INSERT sys_menu VALUES (1507, N'娴嬭瘯鏍戣〃鏌ヨ', 1506, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'demo:tree:query', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1508, N'娴嬭瘯鏍戣〃鏂板', 1506, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'demo:tree:add', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1509, N'娴嬭瘯鏍戣〃淇敼', 1506, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'demo:tree:edit', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1510, N'娴嬭瘯鏍戣〃鍒犻櫎', 1506, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'demo:tree:remove', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1511, N'娴嬭瘯鏍戣〃瀵煎嚭', 1506, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'demo:tree:export', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+
+CREATE TABLE sys_notice
+(
+ notice_id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ notice_title nvarchar(50) NOT NULL,
+ notice_type nchar(1) NOT NULL,
+ notice_content nvarchar(max) NULL,
+ status nchar(1) DEFAULT ('0') NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ remark nvarchar(255) NULL,
+ CONSTRAINT PK__sys_noti__3E82A5DB0EC94801 PRIMARY KEY CLUSTERED (notice_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+TEXTIMAGE_ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍏憡ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_notice',
+ 'COLUMN', N'notice_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_notice',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍏憡鏍囬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_notice',
+ 'COLUMN', N'notice_title'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍏憡绫诲瀷锛�1閫氱煡 2鍏憡锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_notice',
+ 'COLUMN', N'notice_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍏憡鍐呭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_notice',
+ 'COLUMN', N'notice_content'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍏憡鐘舵�侊紙0姝e父 1鍏抽棴锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_notice',
+ 'COLUMN', N'status'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_notice',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_notice',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_notice',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_notice',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_notice',
+ 'COLUMN', N'update_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶囨敞' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_notice',
+ 'COLUMN', N'remark'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'閫氱煡鍏憡琛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_notice'
+GO
+
+INSERT sys_notice VALUES (1, N'000000', N'娓╅Θ鎻愰啋锛�2018-07-01 鑻ヤ緷鏂扮増鏈彂甯冨暒', N'2', N'鏂扮増鏈唴瀹�', N'0', 103, 1, getdate(), NULL, NULL, N'绠$悊鍛�')
+GO
+INSERT sys_notice VALUES (2, N'000000', N'缁存姢閫氱煡锛�2018-07-01 鑻ヤ緷绯荤粺鍑屾櫒缁存姢', N'1', N'缁存姢鍐呭', N'0', 103, 1, getdate(), NULL, NULL, N'绠$悊鍛�')
+GO
+
+CREATE TABLE sys_oper_log
+(
+ oper_id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ title nvarchar(50) DEFAULT '' NULL,
+ business_type int DEFAULT ((0)) NULL,
+ method nvarchar(100) DEFAULT '' NULL,
+ request_method nvarchar(10) DEFAULT '' NULL,
+ operator_type int DEFAULT ((0)) NULL,
+ oper_name nvarchar(50) DEFAULT '' NULL,
+ dept_name nvarchar(50) DEFAULT '' NULL,
+ oper_url nvarchar(255) DEFAULT '' NULL,
+ oper_ip nvarchar(128) DEFAULT '' NULL,
+ oper_location nvarchar(255) DEFAULT '' NULL,
+ oper_param nvarchar(4000) DEFAULT '' NULL,
+ json_result nvarchar(4000) DEFAULT '' NULL,
+ status int DEFAULT ((0)) NULL,
+ error_msg nvarchar(4000) DEFAULT '' NULL,
+ oper_time datetime2(7) NULL,
+ cost_time bigint DEFAULT ((0)) NULL,
+ CONSTRAINT PK__sys_oper__34723BF9BD954573 PRIMARY KEY CLUSTERED (oper_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+CREATE NONCLUSTERED INDEX idx_sys_oper_log_bt ON sys_oper_log (business_type)
+GO
+CREATE NONCLUSTERED INDEX idx_sys_oper_log_s ON sys_oper_log (status)
+GO
+CREATE NONCLUSTERED INDEX idx_sys_oper_log_ot ON sys_oper_log (oper_time)
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏃ュ織涓婚敭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'oper_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'妯″潡鏍囬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'title'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'涓氬姟绫诲瀷锛�0鍏跺畠 1鏂板 2淇敼 3鍒犻櫎锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'business_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏂规硶鍚嶇О' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'method'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'璇锋眰鏂瑰紡' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'request_method'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鎿嶄綔绫诲埆锛�0鍏跺畠 1鍚庡彴鐢ㄦ埛 2鎵嬫満绔敤鎴凤級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'operator_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鎿嶄綔浜哄憳' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'oper_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬鍚嶇О' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'dept_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'璇锋眰URL' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'oper_url'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'涓绘満鍦板潃' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'oper_ip'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鎿嶄綔鍦扮偣' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'oper_location'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'璇锋眰鍙傛暟' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'oper_param'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'杩斿洖鍙傛暟' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'json_result'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鎿嶄綔鐘舵�侊紙0姝e父 1寮傚父锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'status'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'閿欒娑堟伅' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'error_msg'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鎿嶄綔鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'oper_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'娑堣�楁椂闂�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log',
+ 'COLUMN', N'cost_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鎿嶄綔鏃ュ織璁板綍' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oper_log'
+GO
+
+CREATE TABLE sys_post
+(
+ post_id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ dept_id bigint NOT NULL,
+ post_code nvarchar(64) NOT NULL,
+ post_category nvarchar(100) NULL,
+ post_name nvarchar(50) NOT NULL,
+ post_sort int NOT NULL,
+ status nchar(1) NOT NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ remark nvarchar(500) NULL,
+ CONSTRAINT PK__sys_post__3ED7876668E2D081 PRIMARY KEY CLUSTERED (post_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'宀椾綅ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'post_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬id' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'dept_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'宀椾綅缂栫爜' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'post_code'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'宀椾綅绫诲埆缂栫爜' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'post_category'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'宀椾綅鍚嶇О' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'post_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄剧ず椤哄簭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'post_sort'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐘舵�侊紙0姝e父 1鍋滅敤锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'status'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'update_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶囨敞' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'remark'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'宀椾綅淇℃伅琛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post'
+GO
+
+INSERT sys_post VALUES (1, N'000000', 103, N'ceo', NULL, N'钁d簨闀�', 1, N'0', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_post VALUES (2, N'000000', 100, N'se', NULL, N'椤圭洰缁忕悊', 2, N'0', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_post VALUES (3, N'000000', 100, N'hr', NULL, N'浜哄姏璧勬簮', 3, N'0', 103, 1, getdate(), NULL, NULL, N'')
+GO
+INSERT sys_post VALUES (4, N'000000', 100, N'user', NULL, N'鏅�氬憳宸�', 4, N'0', 103, 1, getdate(), NULL, NULL, N'')
+GO
+
+CREATE TABLE sys_role
+(
+ role_id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ role_name nvarchar(30) NOT NULL,
+ role_key nvarchar(100) NOT NULL,
+ role_sort int NOT NULL,
+ data_scope nchar(1) DEFAULT ('1') NULL,
+ menu_check_strictly tinyint DEFAULT ((1)) NULL,
+ dept_check_strictly tinyint DEFAULT ((1)) NULL,
+ status nchar(1) NOT NULL,
+ del_flag nchar(1) DEFAULT ('0') NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ remark nvarchar(500) NULL,
+ CONSTRAINT PK__sys_role__760965CCF9383145 PRIMARY KEY CLUSTERED (role_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瑙掕壊ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'role_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瑙掕壊鍚嶇О' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'role_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瑙掕壊鏉冮檺瀛楃涓�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'role_key'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏄剧ず椤哄簭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'role_sort'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏁版嵁鑼冨洿锛�1锛氬叏閮ㄦ暟鎹潈闄� 2锛氳嚜瀹氭暟鎹潈闄� 3锛氭湰閮ㄩ棬鏁版嵁鏉冮檺 4锛氭湰閮ㄩ棬鍙婁互涓嬫暟鎹潈闄� 5锛氫粎鏈汉鏁版嵁鏉冮檺 6锛氶儴闂ㄥ強浠ヤ笅鎴栨湰浜烘暟鎹潈闄愶級' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'data_scope'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'menu_check_strictly'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'dept_check_strictly'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瑙掕壊鐘舵�侊紙0姝e父 1鍋滅敤锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'status'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'del_flag'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'update_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶囨敞' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role',
+ 'COLUMN', N'remark'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瑙掕壊淇℃伅琛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role'
+GO
+
+INSERT sys_role VALUES (1, N'000000', N'瓒呯骇绠$悊鍛�', N'superadmin', 1, N'1', 1, 1, N'0', N'0', 103, 1, getdate(), NULL, NULL, N'瓒呯骇绠$悊鍛�')
+GO
+INSERT sys_role VALUES (3, N'000000', N'鏈儴闂ㄥ強浠ヤ笅', N'test1', 3, N'4', 1, 1, N'0', N'0', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_role VALUES (4, N'000000', N'浠呮湰浜�', N'test2', 4, N'5', 1, 1, N'0', N'0', 103, 1, getdate(), NULL, NULL, N'');
+GO
+
+CREATE TABLE sys_role_dept
+(
+ role_id bigint NOT NULL,
+ dept_id bigint NOT NULL,
+ CONSTRAINT PK__sys_role__2BC3005BABBCA08A PRIMARY KEY CLUSTERED (role_id, dept_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瑙掕壊ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role_dept',
+ 'COLUMN', N'role_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role_dept',
+ 'COLUMN', N'dept_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瑙掕壊鍜岄儴闂ㄥ叧鑱旇〃' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role_dept'
+GO
+
+CREATE TABLE sys_role_menu
+(
+ role_id bigint NOT NULL,
+ menu_id bigint NOT NULL,
+ CONSTRAINT PK__sys_role__A2C36A6187BA4B17 PRIMARY KEY CLUSTERED (role_id, menu_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瑙掕壊ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role_menu',
+ 'COLUMN', N'role_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鑿滃崟ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role_menu',
+ 'COLUMN', N'menu_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瑙掕壊鍜岃彍鍗曞叧鑱旇〃' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_role_menu'
+GO
+
+-- ----------------------------
+-- 鍒濆鍖�-瑙掕壊鍜岃彍鍗曞叧鑱旇〃鏁版嵁
+-- ----------------------------
+INSERT sys_role_menu VALUES (3, 1);
+GO
+INSERT sys_role_menu VALUES (3, 5);
+GO
+INSERT sys_role_menu VALUES (3, 100);
+GO
+INSERT sys_role_menu VALUES (3, 101);
+GO
+INSERT sys_role_menu VALUES (3, 102);
+GO
+INSERT sys_role_menu VALUES (3, 103);
+GO
+INSERT sys_role_menu VALUES (3, 104);
+GO
+INSERT sys_role_menu VALUES (3, 105);
+GO
+INSERT sys_role_menu VALUES (3, 106);
+GO
+INSERT sys_role_menu VALUES (3, 107);
+GO
+INSERT sys_role_menu VALUES (3, 108);
+GO
+INSERT sys_role_menu VALUES (3, 118);
+GO
+INSERT sys_role_menu VALUES (3, 123);
+GO
+INSERT sys_role_menu VALUES (3, 130);
+GO
+INSERT sys_role_menu VALUES (3, 131);
+GO
+INSERT sys_role_menu VALUES (3, 132);
+GO
+INSERT sys_role_menu VALUES (3, 133);
+GO
+INSERT sys_role_menu VALUES (3, 500);
+GO
+INSERT sys_role_menu VALUES (3, 501);
+GO
+INSERT sys_role_menu VALUES (3, 1001);
+GO
+INSERT sys_role_menu VALUES (3, 1002);
+GO
+INSERT sys_role_menu VALUES (3, 1003);
+GO
+INSERT sys_role_menu VALUES (3, 1004);
+GO
+INSERT sys_role_menu VALUES (3, 1005);
+GO
+INSERT sys_role_menu VALUES (3, 1006);
+GO
+INSERT sys_role_menu VALUES (3, 1007);
+GO
+INSERT sys_role_menu VALUES (3, 1008);
+GO
+INSERT sys_role_menu VALUES (3, 1009);
+GO
+INSERT sys_role_menu VALUES (3, 1010);
+GO
+INSERT sys_role_menu VALUES (3, 1011);
+GO
+INSERT sys_role_menu VALUES (3, 1012);
+GO
+INSERT sys_role_menu VALUES (3, 1013);
+GO
+INSERT sys_role_menu VALUES (3, 1014);
+GO
+INSERT sys_role_menu VALUES (3, 1015);
+GO
+INSERT sys_role_menu VALUES (3, 1016);
+GO
+INSERT sys_role_menu VALUES (3, 1017);
+GO
+INSERT sys_role_menu VALUES (3, 1018);
+GO
+INSERT sys_role_menu VALUES (3, 1019);
+GO
+INSERT sys_role_menu VALUES (3, 1020);
+GO
+INSERT sys_role_menu VALUES (3, 1021);
+GO
+INSERT sys_role_menu VALUES (3, 1022);
+GO
+INSERT sys_role_menu VALUES (3, 1023);
+GO
+INSERT sys_role_menu VALUES (3, 1024);
+GO
+INSERT sys_role_menu VALUES (3, 1025);
+GO
+INSERT sys_role_menu VALUES (3, 1026);
+GO
+INSERT sys_role_menu VALUES (3, 1027);
+GO
+INSERT sys_role_menu VALUES (3, 1028);
+GO
+INSERT sys_role_menu VALUES (3, 1029);
+GO
+INSERT sys_role_menu VALUES (3, 1030);
+GO
+INSERT sys_role_menu VALUES (3, 1031);
+GO
+INSERT sys_role_menu VALUES (3, 1032);
+GO
+INSERT sys_role_menu VALUES (3, 1033);
+GO
+INSERT sys_role_menu VALUES (3, 1034);
+GO
+INSERT sys_role_menu VALUES (3, 1035);
+GO
+INSERT sys_role_menu VALUES (3, 1036);
+GO
+INSERT sys_role_menu VALUES (3, 1037);
+GO
+INSERT sys_role_menu VALUES (3, 1038);
+GO
+INSERT sys_role_menu VALUES (3, 1039);
+GO
+INSERT sys_role_menu VALUES (3, 1040);
+GO
+INSERT sys_role_menu VALUES (3, 1041);
+GO
+INSERT sys_role_menu VALUES (3, 1042);
+GO
+INSERT sys_role_menu VALUES (3, 1043);
+GO
+INSERT sys_role_menu VALUES (3, 1044);
+GO
+INSERT sys_role_menu VALUES (3, 1045);
+GO
+INSERT sys_role_menu VALUES (3, 1050);
+GO
+INSERT sys_role_menu VALUES (3, 1061);
+GO
+INSERT sys_role_menu VALUES (3, 1062);
+GO
+INSERT sys_role_menu VALUES (3, 1063);
+GO
+INSERT sys_role_menu VALUES (3, 1064);
+GO
+INSERT sys_role_menu VALUES (3, 1065);
+GO
+INSERT sys_role_menu VALUES (3, 1500);
+GO
+INSERT sys_role_menu VALUES (3, 1501);
+GO
+INSERT sys_role_menu VALUES (3, 1502);
+GO
+INSERT sys_role_menu VALUES (3, 1503);
+GO
+INSERT sys_role_menu VALUES (3, 1504);
+GO
+INSERT sys_role_menu VALUES (3, 1505);
+GO
+INSERT sys_role_menu VALUES (3, 1506);
+GO
+INSERT sys_role_menu VALUES (3, 1507);
+GO
+INSERT sys_role_menu VALUES (3, 1508);
+GO
+INSERT sys_role_menu VALUES (3, 1509);
+GO
+INSERT sys_role_menu VALUES (3, 1510);
+GO
+INSERT sys_role_menu VALUES (3, 1511);
+GO
+INSERT sys_role_menu VALUES (3, 1600);
+GO
+INSERT sys_role_menu VALUES (3, 1601);
+GO
+INSERT sys_role_menu VALUES (3, 1602);
+GO
+INSERT sys_role_menu VALUES (3, 1603);
+GO
+INSERT sys_role_menu VALUES (3, 1620);
+GO
+INSERT sys_role_menu VALUES (3, 1621);
+GO
+INSERT sys_role_menu VALUES (3, 1622);
+GO
+INSERT sys_role_menu VALUES (3, 1623);
+GO
+INSERT sys_role_menu VALUES (3, 11616);
+GO
+INSERT sys_role_menu VALUES (3, 11618);
+GO
+INSERT sys_role_menu VALUES (3, 11619);
+GO
+INSERT sys_role_menu VALUES (3, 11622);
+GO
+INSERT sys_role_menu VALUES (3, 11623);
+GO
+INSERT sys_role_menu VALUES (3, 11629);
+GO
+INSERT sys_role_menu VALUES (3, 11632);
+GO
+INSERT sys_role_menu VALUES (3, 11633);
+GO
+INSERT sys_role_menu VALUES (3, 11638);
+GO
+INSERT sys_role_menu VALUES (3, 11639);
+GO
+INSERT sys_role_menu VALUES (3, 11640);
+GO
+INSERT sys_role_menu VALUES (3, 11641);
+GO
+INSERT sys_role_menu VALUES (3, 11642);
+GO
+INSERT sys_role_menu VALUES (3, 11643);
+GO
+INSERT sys_role_menu VALUES (3, 11701);
+GO
+INSERT sys_role_menu VALUES (4, 5);
+GO
+INSERT sys_role_menu VALUES (4, 1500);
+GO
+INSERT sys_role_menu VALUES (4, 1501);
+GO
+INSERT sys_role_menu VALUES (4, 1502);
+GO
+INSERT sys_role_menu VALUES (4, 1503);
+GO
+INSERT sys_role_menu VALUES (4, 1504);
+GO
+INSERT sys_role_menu VALUES (4, 1505);
+GO
+INSERT sys_role_menu VALUES (4, 1506);
+GO
+INSERT sys_role_menu VALUES (4, 1507);
+GO
+INSERT sys_role_menu VALUES (4, 1508);
+GO
+INSERT sys_role_menu VALUES (4, 1509);
+GO
+INSERT sys_role_menu VALUES (4, 1510);
+GO
+INSERT sys_role_menu VALUES (4, 1511);
+GO
+
+CREATE TABLE sys_user
+(
+ user_id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ dept_id bigint NULL,
+ user_name nvarchar(30) NOT NULL,
+ nick_name nvarchar(30) NOT NULL,
+ user_type nvarchar(10) DEFAULT ('sys_user') NULL,
+ email nvarchar(50) DEFAULT '' NULL,
+ phonenumber nvarchar(11) DEFAULT '' NULL,
+ sex nchar(1) DEFAULT ('0') NULL,
+ avatar bigint NULL,
+ password nvarchar(100) DEFAULT '' NULL,
+ status nchar(1) DEFAULT ('0') NULL,
+ del_flag nchar(1) DEFAULT ('0') NULL,
+ login_ip nvarchar(128) DEFAULT '' NULL,
+ login_date datetime2(7) NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ remark nvarchar(500) NULL,
+ CONSTRAINT PK__sys_user__B9BE370F79170B6A PRIMARY KEY CLUSTERED (user_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'user_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'dept_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛璐﹀彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'user_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鏄电О' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'nick_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛绫诲瀷锛坰ys_user绯荤粺鐢ㄦ埛锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'user_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛閭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'email'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鎵嬫満鍙风爜' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'phonenumber'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鎬у埆锛�0鐢� 1濂� 2鏈煡锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'sex'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶村儚鍦板潃' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'avatar'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀵嗙爜' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'password'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'璐﹀彿鐘舵�侊紙0姝e父 1鍋滅敤锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'status'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'del_flag'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏈�鍚庣櫥褰旾P' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'login_ip'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏈�鍚庣櫥褰曟椂闂�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'login_date'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'update_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶囨敞' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user',
+ 'COLUMN', N'remark'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛淇℃伅琛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user'
+GO
+
+INSERT sys_user VALUES (1, N'000000', 103, N'admin', N'鐤媯鐨勭嫯瀛怢i', N'sys_user', N'crazyLionLi@163.com', N'15888888888', N'1', NULL, N'$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', N'0', N'0', N'127.0.0.1', getdate(), 103, 1, getdate(), NULL, NULL, N'绠$悊鍛�')
+GO
+INSERT sys_user VALUES (3, N'000000', 108, N'test', N'鏈儴闂ㄥ強浠ヤ笅 瀵嗙爜666666', N'sys_user', N'', N'', N'0', NULL, N'$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', N'0', N'0', N'127.0.0.1', getdate(), 103, 1, getdate(), 3, getdate(), NULL);
+GO
+INSERT sys_user VALUES (4, N'000000', 102, N'test1', N'浠呮湰浜� 瀵嗙爜666666', N'sys_user', N'', N'', N'0', NULL, N'$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', N'0', N'0', N'127.0.0.1', getdate(), 103, 1, getdate(), 4, getdate(), NULL);
+GO
+
+CREATE TABLE sys_user_post
+(
+ user_id bigint NOT NULL,
+ post_id bigint NOT NULL,
+ CONSTRAINT PK__sys_user__CA534F799C04589B PRIMARY KEY CLUSTERED (user_id, post_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user_post',
+ 'COLUMN', N'user_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'宀椾綅ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user_post',
+ 'COLUMN', N'post_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛涓庡矖浣嶅叧鑱旇〃' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user_post'
+GO
+
+INSERT sys_user_post VALUES (1, 1)
+GO
+
+CREATE TABLE sys_user_role
+(
+ user_id bigint NOT NULL,
+ role_id bigint NOT NULL,
+ CONSTRAINT PK__sys_user__6EDEA153FB34D8F0 PRIMARY KEY CLUSTERED (user_id, role_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user_role',
+ 'COLUMN', N'user_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瑙掕壊ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user_role',
+ 'COLUMN', N'role_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鍜岃鑹插叧鑱旇〃' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_user_role'
+GO
+
+INSERT sys_user_role VALUES (1, 1)
+GO
+INSERT sys_user_role VALUES (3, 3);
+GO
+INSERT sys_user_role VALUES (4, 4);
+GO
+
+CREATE TABLE sys_oss
+(
+ oss_id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ file_name nvarchar(255) DEFAULT '' NOT NULL,
+ original_name nvarchar(255) DEFAULT '' NOT NULL,
+ file_suffix nvarchar(10) DEFAULT '' NOT NULL,
+ url nvarchar(500) NOT NULL,
+ ext1 nvarchar(500) DEFAULT '' NULL,
+ create_dept bigint NULL,
+ create_time datetime2(7) NULL,
+ create_by bigint NULL,
+ update_time datetime2(7) NULL,
+ update_by bigint NULL,
+ service nvarchar(20) DEFAULT ('minio') NOT NULL,
+ CONSTRAINT PK__sys_oss__91241EA442389F0D PRIMARY KEY CLUSTERED (oss_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'瀵硅薄瀛樺偍涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss',
+ 'COLUMN', N'oss_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏂囦欢鍚�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss',
+ 'COLUMN', N'file_name'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍘熷悕',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss',
+ 'COLUMN', N'original_name'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏂囦欢鍚庣紑鍚�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss',
+ 'COLUMN', N'file_suffix'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'URL鍦板潃',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss',
+ 'COLUMN', N'url'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵╁睍瀛楁',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss',
+ 'COLUMN', N'ext1'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss',
+ 'COLUMN', N'create_time'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婁紶浜�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss',
+ 'COLUMN', N'create_by'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss',
+ 'COLUMN', N'update_time'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊浜�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss',
+ 'COLUMN', N'update_by'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏈嶅姟鍟�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss',
+ 'COLUMN', N'service'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'OSS瀵硅薄瀛樺偍琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss'
+GO
+
+CREATE TABLE sys_oss_config
+(
+ oss_config_id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ config_key nvarchar(20) DEFAULT '' NOT NULL,
+ access_key nvarchar(255) DEFAULT '' NULL,
+ secret_key nvarchar(255) DEFAULT '' NULL,
+ bucket_name nvarchar(255) DEFAULT '' NULL,
+ prefix nvarchar(255) DEFAULT '' NULL,
+ endpoint nvarchar(255) DEFAULT '' NULL,
+ domain nvarchar(255) DEFAULT '' NULL,
+ is_https nchar(1) DEFAULT ('N') NULL,
+ region nvarchar(255) DEFAULT '' NULL,
+ access_policy nchar(1) DEFAULT ('1') NOT NULL,
+ status nchar(1) DEFAULT ('1') NULL,
+ ext1 nvarchar(255) DEFAULT '' NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ remark nvarchar(500) NULL,
+ CONSTRAINT PK__sys_oss___BFBDE87009ED2882 PRIMARY KEY CLUSTERED (oss_config_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'oss_config_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛缂栧彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閰嶇疆key',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'config_key'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'accessKey',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'access_key'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'绉橀挜',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'secret_key'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'妗跺悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'bucket_name'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍓嶇紑',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'prefix'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'璁块棶绔欑偣',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'endpoint'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鑷畾涔夊煙鍚�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'domain'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏄惁https锛圷=鏄�,N=鍚︼級',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'is_https'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍩�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'region'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'妗舵潈闄愮被鍨�(0=private 1=public 2=custom)',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'access_policy'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏄惁榛樿锛�0=鏄�,1=鍚︼級',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'status'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎵╁睍瀛楁',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'ext1'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'create_by'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'create_time'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'update_by'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'update_time'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'澶囨敞',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config',
+ 'COLUMN', N'remark'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'瀵硅薄瀛樺偍閰嶇疆琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_oss_config'
+GO
+
+INSERT INTO sys_oss_config VALUES (N'1', N'000000', N'minio', N'ruoyi', N'ruoyi123', N'ruoyi', N'', N'127.0.0.1:9000', N'',N'N', N'', N'1', N'0', N'', 103, 1, getdate(), 1, getdate(), NULL)
+GO
+INSERT INTO sys_oss_config VALUES (N'2', N'000000', N'qiniu', N'XXXXXXXXXXXXXXXX', N'XXXXXXXXXXXXXXX', N'ruoyi', N'', N's3-cn-north-1.qiniucs.com', N'',N'N', N'', N'1', N'1', N'', 103, 1, getdate(), 1, getdate(), NULL)
+GO
+INSERT INTO sys_oss_config VALUES (N'3', N'000000', N'aliyun', N'XXXXXXXXXXXXXXX', N'XXXXXXXXXXXXXXX', N'ruoyi', N'', N'oss-cn-beijing.aliyuncs.com', N'',N'N', N'', N'1', N'1', N'', 103, 1, getdate(), 1, getdate(), NULL)
+GO
+INSERT INTO sys_oss_config VALUES (N'4', N'000000', N'qcloud', N'XXXXXXXXXXXXXXX', N'XXXXXXXXXXXXXXX', N'ruoyi-1250000000', N'', N'cos.ap-beijing.myqcloud.com', N'',N'N', N'ap-beijing', N'1', N'1', N'', 103, 1, getdate(), 1, getdate(), NULL)
+GO
+INSERT INTO sys_oss_config VALUES (N'5', N'000000', N'image', N'ruoyi', N'ruoyi123', N'ruoyi', N'image', N'127.0.0.1:9000', N'',N'N', N'', N'1', N'1', N'', 103, 1, getdate(), 1, getdate(), NULL)
+GO
+
+
+CREATE TABLE sys_client
+(
+ id bigint NOT NULL,
+ client_id nvarchar(64) DEFAULT '' NULL,
+ client_key nvarchar(32) DEFAULT '' NULL,
+ client_secret nvarchar(255) DEFAULT '' NULL,
+ grant_type nvarchar(255) DEFAULT '' NULL,
+ device_type nvarchar(32) DEFAULT '' NULL,
+ active_timeout int DEFAULT ((1800)) NULL,
+ timeout int DEFAULT ((604800)) NULL,
+ status nchar(1) DEFAULT ('0') NULL,
+ del_flag nchar(1) DEFAULT ('0') NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL
+ CONSTRAINT PK__sys_client___BFBDE87009ED2882 PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'瀹㈡埛绔痠d' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'client_id'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'瀹㈡埛绔痥ey',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'client_key'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'瀹㈡埛绔閽�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'client_secret'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎺堟潈绫诲瀷',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'grant_type'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'璁惧绫诲瀷',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'device_type'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'token娲昏穬瓒呮椂鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'active_timeout'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'token鍥哄畾瓒呮椂',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'timeout'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'status'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'del_flag'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'create_by'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'create_time'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'update_by'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client',
+ 'COLUMN', N'update_time'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'绯荤粺鎺堟潈琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_client'
+GO
+
+INSERT INTO sys_client VALUES (N'1', N'e5cd7e4891bf95d1d19206ce24a7b32e', N'pc', N'pc123', N'password,social', N'pc', 1800, 604800, N'0', N'0', 103, 1, getdate(), 1, getdate())
+GO
+INSERT INTO sys_client VALUES (N'2', N'428a8310cd442757ae699df5d894f051', N'app', N'app123', N'password,sms,social', N'android', 1800, 604800, N'0', N'0', 103, 1, getdate(), 1, getdate())
+GO
+
+CREATE TABLE test_demo
+(
+ id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ dept_id bigint NULL,
+ user_id bigint NULL,
+ order_num int DEFAULT ((0)) NULL,
+ test_key nvarchar(255) NULL,
+ value nvarchar(255) NULL,
+ version int DEFAULT ((0)) NULL,
+ create_dept bigint NULL,
+ create_time datetime2(0) NULL,
+ create_by bigint NULL,
+ update_time datetime2(0) NULL,
+ update_by bigint NULL,
+ del_flag int DEFAULT ((0)) NULL,
+ CONSTRAINT PK__test_dem__3213E83F176051C8 PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'绉熸埛id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'tenant_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'dept_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'user_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鎺掑簭鍙�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'order_num'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'key閿�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'test_key'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍊�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'value'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐗堟湰',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'version'
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'create_dept'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓浜�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'update_time'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊浜�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo',
+ 'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'娴嬭瘯鍗曡〃',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_demo'
+GO
+
+CREATE TABLE test_tree
+(
+ id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT ('000000') NULL,
+ parent_id bigint DEFAULT ((0)) NULL,
+ dept_id bigint NULL,
+ user_id bigint NULL,
+ tree_name nvarchar(255) NULL,
+ version int DEFAULT ((0)) NULL,
+ create_dept bigint NULL,
+ create_time datetime2(0) NULL,
+ create_by bigint NULL,
+ update_time datetime2(0) NULL,
+ update_by bigint NULL,
+ del_flag int DEFAULT ((0)) NULL,
+ CONSTRAINT PK__test_tre__3213E83FC75A1B63 PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'绉熸埛id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree',
+ 'COLUMN', N'tenant_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐖秈d',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree',
+ 'COLUMN', N'parent_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree',
+ 'COLUMN', N'dept_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree',
+ 'COLUMN', N'user_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍊�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree',
+ 'COLUMN', N'tree_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐗堟湰',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree',
+ 'COLUMN', N'version'
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree',
+ 'COLUMN', N'create_dept'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree',
+ 'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓浜�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree',
+ 'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree',
+ 'COLUMN', N'update_time'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊浜�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree',
+ 'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree',
+ 'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'娴嬭瘯鏍戣〃',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'test_tree'
+GO
+
+INSERT test_demo VALUES (1, N'000000', 102, 4, 1, N'娴嬭瘯鏁版嵁鏉冮檺', N'娴嬭瘯', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_demo VALUES (2, N'000000', 102, 3, 2, N'瀛愯妭鐐�1', N'111', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_demo VALUES (3, N'000000', 102, 3, 3, N'瀛愯妭鐐�2', N'222', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_demo VALUES (4, N'000000', 108, 4, 4, N'娴嬭瘯鏁版嵁', N'demo', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_demo VALUES (5, N'000000', 108, 3, 13, N'瀛愯妭鐐�11', N'1111', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_demo VALUES (6, N'000000', 108, 3, 12, N'瀛愯妭鐐�22', N'2222', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_demo VALUES (7, N'000000', 108, 3, 11, N'瀛愯妭鐐�33', N'3333', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_demo VALUES (8, N'000000', 108, 3, 10, N'瀛愯妭鐐�44', N'4444', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_demo VALUES (9, N'000000', 108, 3, 9, N'瀛愯妭鐐�55', N'5555', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_demo VALUES (10, N'000000', 108, 3, 8, N'瀛愯妭鐐�66', N'6666', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_demo VALUES (11, N'000000', 108, 3, 7, N'瀛愯妭鐐�77', N'7777', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_demo VALUES (12, N'000000', 108, 3, 6, N'瀛愯妭鐐�88', N'8888', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_demo VALUES (13, N'000000', 108, 3, 5, N'瀛愯妭鐐�99', N'9999', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+
+INSERT test_tree VALUES (1, N'000000', 0, 102, 4, N'娴嬭瘯鏁版嵁鏉冮檺', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_tree VALUES (2, N'000000', 1, 102, 3, N'瀛愯妭鐐�1', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_tree VALUES (3, N'000000', 2, 102, 3, N'瀛愯妭鐐�2', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_tree VALUES (4, N'000000', 0, 108, 4, N'娴嬭瘯鏍�1', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_tree VALUES (5, N'000000', 4, 108, 3, N'瀛愯妭鐐�11', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_tree VALUES (6, N'000000', 4, 108, 3, N'瀛愯妭鐐�22', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_tree VALUES (7, N'000000', 4, 108, 3, N'瀛愯妭鐐�33', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_tree VALUES (8, N'000000', 5, 108, 3, N'瀛愯妭鐐�44', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_tree VALUES (9, N'000000', 6, 108, 3, N'瀛愯妭鐐�55', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_tree VALUES (10, N'000000', 7, 108, 3, N'瀛愯妭鐐�66', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_tree VALUES (11, N'000000', 7, 108, 3, N'瀛愯妭鐐�77', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_tree VALUES (12, N'000000', 10, 108, 3, N'瀛愯妭鐐�88', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
+INSERT test_tree VALUES (13, N'000000', 10, 108, 3, N'瀛愯妭鐐�99', 0, 103, getdate(), 1, NULL, NULL, 0);
+GO
diff --git a/RuoYi-Vue-Plus/script/sql/sqlserver/sqlserver_ry_workflow.sql b/RuoYi-Vue-Plus/script/sql/sqlserver/sqlserver_ry_workflow.sql
new file mode 100755
index 0000000..09a661f
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/sqlserver/sqlserver_ry_workflow.sql
@@ -0,0 +1,1677 @@
+-- ----------------------------
+-- 0銆亀arm-flow-all.sql锛屽湴鍧�锛歨ttps://gitee.com/dromara/warm-flow/blob/master/sql/sqlserver/sqlserver.sql
+-- ----------------------------
+CREATE TABLE flow_definition (
+ id bigint NOT NULL,
+ flow_code nvarchar(40) NOT NULL,
+ flow_name nvarchar(100) NOT NULL,
+ model_value nvarchar(40) DEFAULT('CLASSICS') NOT NULL,
+ category nvarchar(100) NULL,
+ version nvarchar(20) NOT NULL,
+ is_publish tinyint DEFAULT('0') NULL,
+ form_custom nchar(1) DEFAULT('N') NULL,
+ form_path nvarchar(100) NULL,
+ activity_status tinyint DEFAULT('1') NULL,
+ listener_type nvarchar(100) NULL,
+ listener_path nvarchar(400) NULL,
+ ext nvarchar(500) NULL,
+ create_time datetime2(7) NULL,
+ create_by nvarchar(64) NULL,
+ update_time datetime2(7) NULL,
+ update_by nvarchar(64) NULL,
+ del_flag nchar(1) DEFAULT('0') NULL,
+ tenant_id nvarchar(40) NULL,
+ CONSTRAINT PK__flow_def__3213E83FEE39AE33 PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'涓婚敭id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼缂栫爜',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'flow_code'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鍚嶇О',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'flow_name'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'璁捐鍣ㄦā鍨嬶紙CLASSICS缁忓吀妯″瀷 MIMIC浠块拤閽夋ā鍨嬶級',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'model_value'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼绫诲埆',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'category'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鐗堟湰',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'version'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏄惁鍙戝竷锛�0鏈彂甯� 1宸插彂甯� 9澶辨晥锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'is_publish'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'form_custom'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹℃壒琛ㄥ崟璺緞',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'form_path'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼婵�娲荤姸鎬侊紙0鎸傝捣 1婵�娲伙級',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'activity_status'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鐩戝惉鍣ㄧ被鍨�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'listener_type'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鐩戝惉鍣ㄨ矾寰�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'listener_path'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'涓氬姟璇︽儏 瀛樹笟鍔¤〃瀵硅薄json瀛楃涓�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'ext'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'update_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒犻櫎鏍囧織',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'绉熸埛id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'tenant_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼瀹氫箟琛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition'
+GO
+
+CREATE TABLE flow_node (
+ id bigint NOT NULL,
+ node_type tinyint NOT NULL,
+ definition_id bigint NOT NULL,
+ node_code nvarchar(100) NOT NULL,
+ node_name nvarchar(100) NULL,
+ permission_flag nvarchar(200) NULL,
+ node_ratio nvarchar(200) NULL,
+ coordinate nvarchar(100) NULL,
+ any_node_skip nvarchar(100) NULL,
+ listener_type nvarchar(100) NULL,
+ listener_path nvarchar(400) NULL,
+ form_custom nchar(1) DEFAULT('N') NULL,
+ form_path nvarchar(100) NULL,
+ version nvarchar(20) NOT NULL,
+ create_time datetime2(7) NULL,
+ create_by nvarchar(64) NULL,
+ update_time datetime2(7) NULL,
+ update_by nvarchar(64) NULL,
+ ext nvarchar(max) NULL,
+ del_flag nchar(1) DEFAULT('0') NULL,
+ tenant_id nvarchar(40) NULL,
+ CONSTRAINT PK__flow_nod__3213E83F372470DE PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'涓婚敭id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'node_type'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼瀹氫箟id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'definition_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鑺傜偣缂栫爜',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'node_code'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鑺傜偣鍚嶇О',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'node_name'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏉冮檺鏍囪瘑锛堟潈闄愮被鍨�:鏉冮檺鏍囪瘑锛屽彲浠ュ涓紝鐢ˊ@闅斿紑)',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'permission_flag'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼绛剧讲姣斾緥鍊�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'node_ratio'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍧愭爣',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'coordinate'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'浠绘剰缁撶偣璺宠浆',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'any_node_skip'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鐩戝惉鍣ㄧ被鍨�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'listener_type'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鐩戝惉鍣ㄨ矾寰�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'listener_path'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'form_custom'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹℃壒琛ㄥ崟璺緞',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'form_path'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鐗堟湰',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'version'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'update_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鑺傜偣鎵╁睍灞炴��',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'ext'
+GO
+
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒犻櫎鏍囧織',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'绉熸埛id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'tenant_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鑺傜偣琛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node'
+GO
+
+CREATE TABLE flow_skip (
+ id bigint NOT NULL,
+ definition_id bigint NOT NULL,
+ now_node_code nvarchar(100) NOT NULL,
+ now_node_type tinyint NULL,
+ next_node_code nvarchar(100) NOT NULL,
+ next_node_type tinyint NULL,
+ skip_name nvarchar(100) NULL,
+ skip_type nvarchar(40) NULL,
+ skip_condition nvarchar(200) NULL,
+ coordinate nvarchar(100) NULL,
+ create_time datetime2(7) NULL,
+ create_by nvarchar(64) NULL,
+ update_time datetime2(7) NULL,
+ update_by nvarchar(64) NULL,
+ del_flag nchar(1) DEFAULT('0') NULL,
+ tenant_id nvarchar(40) NULL,
+ CONSTRAINT PK__flow_ski__3213E83F073FEE6E PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'涓婚敭id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼瀹氫箟id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'definition_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'褰撳墠娴佺▼鑺傜偣鐨勭紪鐮�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'now_node_code'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'褰撳墠鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'now_node_type'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'涓嬩竴涓祦绋嬭妭鐐圭殑缂栫爜',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'next_node_code'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'涓嬩竴涓妭鐐圭被鍨嬶紙0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'next_node_type'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'璺宠浆鍚嶇О',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'skip_name'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'璺宠浆绫诲瀷锛圥ASS瀹℃壒閫氳繃 REJECT閫�鍥烇級',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'skip_type'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'璺宠浆鏉′欢',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'skip_condition'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍧愭爣',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'coordinate'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'update_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒犻櫎鏍囧織',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'绉熸埛id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'tenant_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鑺傜偣璺宠浆鍏宠仈琛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip'
+GO
+
+CREATE TABLE flow_instance (
+ id bigint NOT NULL,
+ definition_id bigint NOT NULL,
+ business_id nvarchar(40) NOT NULL,
+ node_type tinyint NOT NULL,
+ node_code nvarchar(40) NOT NULL,
+ node_name nvarchar(100) NULL,
+ variable nvarchar(max) NULL,
+ flow_status nvarchar(20) NOT NULL,
+ activity_status tinyint DEFAULT('1') NULL,
+ def_json nvarchar(max) NULL,
+ create_time datetime2(7) NULL,
+ create_by nvarchar(64) NULL,
+ update_time datetime2(7) NULL,
+ update_by nvarchar(64) NULL,
+ ext nvarchar(500) NULL,
+ del_flag nchar(1) DEFAULT('0') NULL,
+ tenant_id nvarchar(40) NULL,
+ CONSTRAINT PK__flow_ins__3213E83F5190FEE1 PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+TEXTIMAGE_ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'涓婚敭id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀵瑰簲flow_definition琛ㄧ殑id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'definition_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'涓氬姟id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'business_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'node_type'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鑺傜偣缂栫爜',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'node_code'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鑺傜偣鍚嶇О',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'node_name'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'浠诲姟鍙橀噺',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'variable'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'flow_status'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼婵�娲荤姸鎬侊紙0鎸傝捣 1婵�娲伙級',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'activity_status'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼瀹氫箟json',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'def_json'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'update_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鎵╁睍瀛楁锛岄鐣欑粰涓氬姟绯荤粺浣跨敤',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'ext'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒犻櫎鏍囧織',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'绉熸埛id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'tenant_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼瀹炰緥琛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance'
+GO
+
+CREATE TABLE flow_task (
+ id bigint NOT NULL,
+ definition_id bigint NOT NULL,
+ instance_id bigint NOT NULL,
+ node_code nvarchar(100) NOT NULL,
+ node_name nvarchar(100) NULL,
+ node_type tinyint NOT NULL,
+ flow_status nvarchar(20) NOT NULL,
+ form_custom nchar(1) DEFAULT('N') NULL,
+ form_path nvarchar(100) NULL,
+ create_time datetime2(7) NULL,
+ create_by nvarchar(64) NULL,
+ update_time datetime2(7) NULL,
+ update_by nvarchar(64) NULL,
+ del_flag nchar(1) DEFAULT('0') NULL,
+ tenant_id nvarchar(40) NULL,
+ CONSTRAINT PK__flow_tas__3213E83F5AE1F1BA PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'涓婚敭id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀵瑰簲flow_definition琛ㄧ殑id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'definition_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀵瑰簲flow_instance琛ㄧ殑id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'instance_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鑺傜偣缂栫爜',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'node_code'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鑺傜偣鍚嶇О',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'node_name'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鑺傜偣绫诲瀷锛�0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'node_type'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'flow_status'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'form_custom'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹℃壒琛ㄥ崟璺緞',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'form_path'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'update_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒犻櫎鏍囧織',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'绉熸埛id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'tenant_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'寰呭姙浠诲姟琛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task'
+GO
+
+CREATE TABLE flow_his_task (
+ id bigint NOT NULL,
+ definition_id bigint NOT NULL,
+ instance_id bigint NOT NULL,
+ task_id bigint NOT NULL,
+ node_code nvarchar(200) NULL,
+ node_name nvarchar(200) NULL,
+ node_type tinyint NULL,
+ target_node_code nvarchar(100) NULL,
+ target_node_name nvarchar(100) NULL,
+ approver nvarchar(40) NULL,
+ cooperate_type tinyint DEFAULT('0') NULL,
+ collaborator nvarchar(500) NULL,
+ skip_type nvarchar(10) NOT NULL,
+ flow_status nvarchar(20) NOT NULL,
+ form_custom nchar(1) DEFAULT('N') NULL,
+ form_path nvarchar(100) NULL,
+ message nvarchar(500) NULL,
+ variable nvarchar(max) NULL,
+ ext nvarchar(max) NULL,
+ create_time datetime2(7) NULL,
+ update_time datetime2(7) NULL,
+ del_flag nchar(1) DEFAULT('0') NULL,
+ tenant_id nvarchar(40) NULL,
+ CONSTRAINT PK__flow_his__3213E83F67951564 PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'涓婚敭id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀵瑰簲flow_definition琛ㄧ殑id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'definition_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀵瑰簲flow_instance琛ㄧ殑id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'instance_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀵瑰簲flow_task琛ㄧ殑id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'task_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'寮�濮嬭妭鐐圭紪鐮�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'node_code'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'寮�濮嬭妭鐐瑰悕绉�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'node_name'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'寮�濮嬭妭鐐圭被鍨嬶紙0寮�濮嬭妭鐐� 1涓棿鑺傜偣 2缁撴潫鑺傜偣 3浜掓枼缃戝叧 4骞惰缃戝叧锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'node_type'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鐩爣鑺傜偣缂栫爜',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'target_node_code'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'缁撴潫鑺傜偣鍚嶇О',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'target_node_name'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹℃壒鑰�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'approver'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍗忎綔鏂瑰紡(1瀹℃壒 2杞姙 3濮旀淳 4浼氱 5绁ㄧ 6鍔犵 7鍑忕)',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'cooperate_type'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍗忎綔浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'collaborator'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佽浆绫诲瀷锛圥ASS閫氳繃 REJECT閫�鍥� NONE鏃犲姩浣滐級',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'skip_type'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'flow_status'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹℃壒琛ㄥ崟鏄惁鑷畾涔夛紙Y鏄� N鍚︼級',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'form_custom'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹℃壒琛ㄥ崟璺緞',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'form_path'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹℃壒鎰忚',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'message'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'浠诲姟鍙橀噺',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'variable'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'涓氬姟璇︽儏 瀛樹笟鍔¤〃瀵硅薄json瀛楃涓�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'ext'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'浠诲姟寮�濮嬫椂闂�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹℃壒瀹屾垚鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'update_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒犻櫎鏍囧織',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'绉熸埛id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'tenant_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍘嗗彶浠诲姟璁板綍琛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task'
+GO
+
+CREATE TABLE flow_user (
+ id bigint NOT NULL,
+ type nchar(1) NOT NULL,
+ processed_by nvarchar(80) NULL,
+ associated bigint NOT NULL,
+ create_time datetime2(7) NULL,
+ create_by nvarchar(64) NULL,
+ update_time datetime2(7) NULL,
+ update_by nvarchar(64) NULL,
+ del_flag nchar(1) DEFAULT('0') NULL,
+ tenant_id nvarchar(40) NULL,
+ CONSTRAINT PK__flow_use__3213E83FFA38CA8B PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+CREATE NONCLUSTERED INDEX user_processed_type ON flow_user (processed_by ASC, type ASC)
+GO
+CREATE NONCLUSTERED INDEX user_associated_idx ON flow_user (associated ASC)
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'涓婚敭id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_user',
+'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'浜哄憳绫诲瀷锛�1寰呭姙浠诲姟鐨勫鎵逛汉鏉冮檺 2寰呭姙浠诲姟鐨勮浆鍔炰汉鏉冮檺 3寰呭姙浠诲姟鐨勫鎵樹汉鏉冮檺锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_user',
+'COLUMN', N'type'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏉冮檺浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_user',
+'COLUMN', N'processed_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'浠诲姟琛╥d',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_user',
+'COLUMN', N'associated'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_user',
+'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_user',
+'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_user',
+'COLUMN', N'update_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_user',
+'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒犻櫎鏍囧織',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_user',
+'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'绉熸埛id',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_user',
+'COLUMN', N'tenant_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鐢ㄦ埛琛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_user'
+GO
+
+CREATE TABLE flow_category (
+ category_id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT('000000') NULL,
+ parent_id bigint DEFAULT(0) NULL,
+ ancestors nvarchar(500) DEFAULT('') NULL,
+ category_name nvarchar(30) NOT NULL,
+ order_num int DEFAULT(0) NULL,
+ del_flag nchar(1) DEFAULT('0') NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ CONSTRAINT PK__flow_cat__D54EE9B4AE98B9C1 PRIMARY KEY CLUSTERED (category_id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鍒嗙被ID',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_category',
+'COLUMN', N'category_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'绉熸埛缂栧彿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_category',
+'COLUMN', N'tenant_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鐖舵祦绋嬪垎绫籭d',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_category',
+'COLUMN', N'parent_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'绁栫骇鍒楄〃',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_category',
+'COLUMN', N'ancestors'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鍒嗙被鍚嶇О',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_category',
+'COLUMN', N'category_name'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏄剧ず椤哄簭',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_category',
+'COLUMN', N'order_num'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_category',
+'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓閮ㄩ棬',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_category',
+'COLUMN', N'create_dept'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓鑰�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_category',
+'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_category',
+'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊鑰�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_category',
+'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_category',
+'COLUMN', N'update_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鍒嗙被',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_category'
+GO
+
+INSERT flow_category VALUES (100, N'000000', 0, N'0', N'OA瀹℃壒', 0, N'0', 103, 1, getdate(), NULL, NULL);
+GO
+INSERT flow_category VALUES (101, N'000000', 100, N'0,100', N'鍋囧嫟绠$悊', 0, N'0', 103, 1, getdate(), NULL, NULL);
+GO
+INSERT flow_category VALUES (102, N'000000', 100, N'0,100', N'浜轰簨绠$悊', 1, N'0', 103, 1, getdate(), NULL, NULL);
+GO
+INSERT flow_category VALUES (103, N'000000', 101, N'0,100,101', N'璇峰亣', 0, N'0', 103, 1, getdate(), NULL, NULL);
+GO
+INSERT flow_category VALUES (104, N'000000', 101, N'0,100,101', N'鍑哄樊', 1, N'0', 103, 1, getdate(), NULL, NULL);
+GO
+INSERT flow_category VALUES (105, N'000000', 101, N'0,100,101', N'鍔犵彮', 2, N'0', 103, 1, getdate(), NULL, NULL);
+GO
+INSERT flow_category VALUES (106, N'000000', 101, N'0,100,101', N'鎹㈢彮', 3, N'0', 103, 1, getdate(), NULL, NULL);
+GO
+INSERT flow_category VALUES (107, N'000000', 101, N'0,100,101', N'澶栧嚭', 4, N'0', 103, 1, getdate(), NULL, NULL);
+GO
+INSERT flow_category VALUES (108, N'000000', 102, N'0,100,102', N'杞', 1, N'0', 103, 1, getdate(), NULL, NULL);
+GO
+INSERT flow_category VALUES (109, N'000000', 102, N'0,100,102', N'绂昏亴', 2, N'0', 103, 1, getdate(), NULL, NULL);
+GO
+
+CREATE TABLE flow_spel (
+ id BIGINT NOT NULL,
+ component_name VARCHAR(255),
+ method_name VARCHAR(255),
+ method_params VARCHAR(255),
+ view_spel VARCHAR(255),
+ remark VARCHAR(255),
+ status CHAR(1) DEFAULT ('0'),
+ del_flag CHAR(1) DEFAULT ('0'),
+ create_dept BIGINT,
+ create_by BIGINT,
+ create_time DATETIME,
+ update_by BIGINT,
+ update_time DATETIME,
+ CONSTRAINT PK__flow_spel__D54EE9B4AE98B9C1 PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+);
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'娴佺▼spel琛ㄨ揪寮忓畾涔夎〃',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勪欢鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'component_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏂规硶鍚�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'method_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍙傛暟',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'method_params'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'棰勮spel琛ㄨ揪寮�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'view_spel'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'澶囨敞',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'remark'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'create_dept'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'update_time'
+GO
+
+INSERT flow_spel VALUES (1, N'spelRuleComponent', N'selectDeptLeaderById', N'initiatorDeptId', N'#{@spelRuleComponent.selectDeptLeaderById(#initiatorDeptId)}', N'鏍规嵁閮ㄩ棬id鑾峰彇閮ㄩ棬璐熻矗浜�', N'0', N'0', 103, 1, GETDATE(), 1, GETDATE());
+GO
+INSERT flow_spel VALUES (2, NULL, NULL, N'initiator', N'${initiator}', N'娴佺▼鍙戣捣浜�', N'0', N'0', 103, 1, GETDATE(), 1, GETDATE());
+GO
+
+CREATE TABLE flow_instance_biz_ext (
+ id BIGINT NOT NULL,
+ tenant_id VARCHAR(20) DEFAULT ('000000'),
+ create_dept BIGINT,
+ create_by BIGINT,
+ create_time DATETIME,
+ update_by BIGINT,
+ update_time DATETIME,
+ business_code VARCHAR(255),
+ business_title VARCHAR(1000),
+ del_flag CHAR(1) DEFAULT ('0'),
+ instance_id BIGINT,
+ business_id VARCHAR(255),
+ CONSTRAINT PK__fi_biz_ext__D54EE9B4AE98B9C1 PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+);
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'娴佺▼瀹炰緥涓氬姟鎵╁睍琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'绉熸埛缂栧彿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'tenant_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'create_dept'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'update_time'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓氬姟缂栫爜',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'business_code'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓氬姟鏍囬',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'business_title'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'娴佺▼瀹炰緥Id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'instance_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓氬姟Id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'business_id'
+GO
+
+CREATE TABLE test_leave (
+ id bigint NOT NULL,
+ tenant_id nvarchar(20) DEFAULT('000000') NULL,
+ apply_code nvarchar(50) NOT NULL,
+ leave_type nvarchar(255) NOT NULL,
+ start_date datetime2(7) NOT NULL,
+ end_date datetime2(7) NOT NULL,
+ leave_days int NOT NULL,
+ remark nvarchar(255) NULL,
+ status nvarchar(255) NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL,
+ CONSTRAINT PK__test_lea__3213E83F348788FA PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'id',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'绉熸埛缂栧彿',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'tenant_id'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鐢宠缂栧彿',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'apply_code'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'璇峰亣绫诲瀷',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'leave_type'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'寮�濮嬫椂闂�',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'start_date'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'缁撴潫鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'end_date'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'璇峰亣澶╂暟',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'leave_days'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'璇峰亣鍘熷洜',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'remark'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鐘舵��',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'status'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓閮ㄩ棬',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'create_dept'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓鑰�',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊鑰�',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'update_time'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'璇峰亣鐢宠琛�',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave'
+GO
+
+INSERT sys_menu VALUES (11616, N'宸ヤ綔娴�', 0, 6, N'workflow', NULL, N'', 1, 0, N'M', N'0', N'0', N'', N'workflow', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11618, N'鎴戠殑浠诲姟', 0, 7, N'task', NULL, N'', 1, 0, N'M', N'0', N'0', N'', N'my-task', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11619, N'鎴戠殑寰呭姙', 11618, 2, N'taskWaiting', N'workflow/task/taskWaiting', N'', 1, 1, N'C', N'0', N'0', N'', N'waiting', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11632, N'鎴戠殑宸插姙', 11618, 3, N'taskFinish', N'workflow/task/taskFinish', N'', 1, 1, N'C', N'0', N'0', N'', N'finish', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11633, N'鎴戠殑鎶勯��', 11618, 4, N'taskCopyList', N'workflow/task/taskCopyList', N'', 1, 1, N'C', N'0', N'0', N'', N'my-copy', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11620, N'娴佺▼瀹氫箟', 11616, 3, N'processDefinition', N'workflow/processDefinition/index', N'', 1, 1, N'C', N'0', N'0', N'', N'process-definition', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11621, N'娴佺▼瀹炰緥', 11630, 1, N'processInstance', N'workflow/processInstance/index', N'', 1, 1, N'C', N'0', N'0', N'', N'tree-table', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11622, N'娴佺▼鍒嗙被', 11616, 1, N'category', N'workflow/category/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:category:list', N'category', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11629, N'鎴戝彂璧风殑', 11618, 1, N'myDocument', N'workflow/task/myDocument', N'', 1, 1, N'C', N'0', N'0', N'', N'guide', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11630, N'娴佺▼鐩戞帶', 11616, 4, N'monitor', NULL, N'', 1, 0, N'M', N'0', N'0', N'', N'monitor', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11631, N'寰呭姙浠诲姟', 11630, 2, N'allTaskWaiting', N'workflow/task/allTaskWaiting', N'', 1, 1, N'C', N'0', N'0', N'', N'waiting', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11700, N'娴佺▼璁捐', 11616, 5, N'design/index', N'workflow/processDefinition/design', N'', 1, 1, N'C', N'1', N'0', N'workflow:leave:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'/workflow/processDefinition');
+GO
+INSERT sys_menu VALUES (11701, N'璇峰亣鐢宠', 11616, 6, N'leaveEdit/index', N'workflow/leave/leaveEdit', N'', 1, 1, N'C', N'1', N'0', N'workflow:leave:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+
+-- 娴佺▼鍒嗙被绠$悊鐩稿叧鎸夐挳
+INSERT sys_menu VALUES (11623, N'娴佺▼鍒嗙被鏌ヨ', 11622, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:category:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11624, N'娴佺▼鍒嗙被鏂板', 11622, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:category:add', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11625, N'娴佺▼鍒嗙被淇敼', 11622, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:category:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11626, N'娴佺▼鍒嗙被鍒犻櫎', 11622, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:category:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11627, N'娴佺▼鍒嗙被瀵煎嚭', 11622, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:category:export', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+
+INSERT sys_menu VALUES (11801, N'娴佺▼琛ㄨ揪寮�', N'11616', 2, N'spel', N'workflow/spel/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:spel:list', N'input', 103, 1, GETDATE(), 1, GETDATE(), N'娴佺▼杈惧紡瀹氫箟鑿滃崟');
+GO
+INSERT sys_menu VALUES (11802, N'娴佺▼spel琛ㄨ揪寮忓畾涔夋煡璇�', N'11801', 1, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11803, N'娴佺▼spel琛ㄨ揪寮忓畾涔夋柊澧�', N'11801', 2, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:add', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11804, N'娴佺▼spel琛ㄨ揪寮忓畾涔変慨鏀�', N'11801', 3, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11805, N'娴佺▼spel琛ㄨ揪寮忓畾涔夊垹闄�', N'11801', 4, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11806, N'娴佺▼spel琛ㄨ揪寮忓畾涔夊鍑�', N'11801', 5, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:export', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+
+-- 璇峰亣娴嬭瘯鐩稿叧鎸夐挳
+INSERT sys_menu VALUES (11638, N'璇峰亣鐢宠', 5, 1, N'leave', N'workflow/leave/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:leave:list', N'#', 103, 1, GETDATE(), NULL, NULL, N'璇峰亣鐢宠鑿滃崟');
+GO
+INSERT sys_menu VALUES (11639, N'璇峰亣鐢宠鏌ヨ', 11638, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:leave:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11640, N'璇峰亣鐢宠鏂板', 11638, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:leave:add', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11641, N'璇峰亣鐢宠淇敼', 11638, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:leave:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11642, N'璇峰亣鐢宠鍒犻櫎', 11638, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:leave:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11643, N'璇峰亣鐢宠瀵煎嚭', 11638, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:leave:export', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+
+INSERT sys_dict_type VALUES (13, N'000000', N'涓氬姟鐘舵��', N'wf_business_status', 103, 1, GETDATE(), NULL, NULL, N'涓氬姟鐘舵�佸垪琛�');
+GO
+INSERT sys_dict_type VALUES (14, N'000000', N'琛ㄥ崟绫诲瀷', N'wf_form_type', 103, 1, GETDATE(), NULL, NULL, N'琛ㄥ崟绫诲瀷鍒楄〃');
+GO
+INSERT sys_dict_type VALUES (15, N'000000', N'浠诲姟鐘舵��', N'wf_task_status', 103, 1, GETDATE(), NULL, NULL, N'浠诲姟鐘舵��');
+GO
+
+INSERT sys_dict_data VALUES (39, N'000000', 1, N'宸叉挙閿�', N'cancel', N'wf_business_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'宸叉挙閿�');
+GO
+INSERT sys_dict_data VALUES (40, N'000000', 2, N'鑽夌', N'draft', N'wf_business_status', N'', N'info', N'N', 103, 1, GETDATE(), NULL, NULL, N'鑽夌');
+GO
+INSERT sys_dict_data VALUES (41, N'000000', 3, N'寰呭鏍�', N'waiting', N'wf_business_status', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'寰呭鏍�');
+GO
+INSERT sys_dict_data VALUES (42, N'000000', 4, N'宸插畬鎴�', N'finish', N'wf_business_status', N'', N'success', N'N', 103, 1, GETDATE(), NULL, NULL, N'宸插畬鎴�');
+GO
+INSERT sys_dict_data VALUES (43, N'000000', 5, N'宸蹭綔搴�', N'invalid', N'wf_business_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'宸蹭綔搴�');
+GO
+INSERT sys_dict_data VALUES (44, N'000000', 6, N'宸查��鍥�', N'back', N'wf_business_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'宸查��鍥�');
+GO
+INSERT sys_dict_data VALUES (45, N'000000', 7, N'宸茬粓姝�', N'termination', N'wf_business_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'宸茬粓姝�');
+GO
+INSERT sys_dict_data VALUES (46, N'000000', 1, N'鑷畾涔夎〃鍗�', N'static', N'wf_form_type', N'', N'success', N'N', 103, 1, GETDATE(), NULL, NULL, N'鑷畾涔夎〃鍗�');
+GO
+INSERT sys_dict_data VALUES (47, N'000000', 2, N'鍔ㄦ�佽〃鍗�', N'dynamic', N'wf_form_type', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'鍔ㄦ�佽〃鍗�');
+GO
+INSERT sys_dict_data VALUES (48, N'000000', 1, N'鎾ら攢', N'cancel', N'wf_task_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'鎾ら攢');
+GO
+INSERT sys_dict_data VALUES (49, N'000000', 2, N'閫氳繃', N'pass', N'wf_task_status', N'', N'success', N'N', 103, 1, GETDATE(), NULL, NULL, N'閫氳繃');
+GO
+INSERT sys_dict_data VALUES (50, N'000000', 3, N'寰呭鏍�', N'waiting', N'wf_task_status', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'寰呭鏍�');
+GO
+INSERT sys_dict_data VALUES (51, N'000000', 4, N'浣滃簾', N'invalid', N'wf_task_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'浣滃簾');
+GO
+INSERT sys_dict_data VALUES (52, N'000000', 5, N'閫�鍥�', N'back', N'wf_task_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'閫�鍥�');
+GO
+INSERT sys_dict_data VALUES (53, N'000000', 6, N'缁堟', N'termination', N'wf_task_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'缁堟');
+GO
+INSERT sys_dict_data VALUES (54, N'000000', 7, N'杞姙', N'transfer', N'wf_task_status', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'杞姙');
+GO
+INSERT sys_dict_data VALUES (55, N'000000', 8, N'濮旀墭', N'depute', N'wf_task_status', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'濮旀墭');
+GO
+INSERT sys_dict_data VALUES (56, N'000000', 9, N'鎶勯��', N'copy', N'wf_task_status', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'鎶勯��');
+GO
+INSERT sys_dict_data VALUES (57, N'000000', 10, N'鍔犵', N'sign', N'wf_task_status', N'', N'primary', N'N', 103, 1, GETDATE(), NULL, NULL, N'鍔犵');
+GO
+INSERT sys_dict_data VALUES (58, N'000000', 11, N'鍑忕', N'sign_off', N'wf_task_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'鍑忕');
+GO
+INSERT sys_dict_data VALUES (59, N'000000', 11, N'瓒呮椂', N'timeout', N'wf_task_status', N'', N'danger', N'N', 103, 1, GETDATE(), NULL, NULL, N'瓒呮椂');
+GO
diff --git a/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.0-5.1.sql b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.0-5.1.sql
new file mode 100755
index 0000000..09cfae8
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.0-5.1.sql
@@ -0,0 +1,151 @@
+ALTER TABLE gen_table ADD (data_name VARCHAR2(200) DEFAULT '');
+
+COMMENT ON COLUMN gen_table.data_name IS '鏁版嵁婧愬悕绉�';
+
+UPDATE sys_menu SET path = 'powerjob', component = 'monitor/powerjob/index', perms = 'monitor:powerjob:list', remark = 'powerjob鎺у埗鍙拌彍鍗�' WHERE menu_id = 120;
+
+-- ----------------------------
+-- 绗笁鏂瑰钩鍙版巿鏉冭〃
+-- ----------------------------
+create table sys_social
+(
+ id number(20) not null,
+ user_id number(20) not null,
+ tenant_id varchar2(20) default null,
+ auth_id varchar2(255) not null,
+ source varchar2(255) not null,
+ open_id varchar2(255) default null,
+ user_name varchar2(30) not null,
+ nick_name varchar2(30) default '',
+ email varchar2(255) default '',
+ avatar varchar2(500) default '',
+ access_token varchar2(255) not null,
+ expire_in number(20) default null,
+ refresh_token varchar2(255) default null,
+ access_code varchar2(255) default null,
+ union_id varchar2(255) default null,
+ scope varchar2(255) default null,
+ token_type varchar2(255) default null,
+ id_token varchar2(255) default null,
+ mac_algorithm varchar2(255) default null,
+ mac_key varchar2(255) default null,
+ code varchar2(255) default null,
+ oauth_token varchar2(255) default null,
+ oauth_token_secret varchar2(255) default null,
+ create_dept number(20),
+ create_by number(20),
+ create_time date,
+ update_by number(20),
+ update_time date,
+ del_flag char(1) default '0'
+);
+
+alter table sys_social add constraint pk_sys_social primary key (id);
+
+comment on table sys_social is '绀句細鍖栧叧绯昏〃';
+comment on column sys_social.id is '涓婚敭';
+comment on column sys_social.user_id is '鐢ㄦ埛ID';
+comment on column sys_social.tenant_id is '绉熸埛id';
+comment on column sys_social.auth_id is '骞冲彴+骞冲彴鍞竴id';
+comment on column sys_social.source is '鐢ㄦ埛鏉ユ簮';
+comment on column sys_social.open_id is '骞冲彴缂栧彿鍞竴id';
+comment on column sys_social.user_name is '鐧诲綍璐﹀彿';
+comment on column sys_social.nick_name is '鐢ㄦ埛鏄电О';
+comment on column sys_social.email is '鐢ㄦ埛閭';
+comment on column sys_social.avatar is '澶村儚鍦板潃';
+comment on column sys_social.access_token is '鐢ㄦ埛鐨勬巿鏉冧护鐗�';
+comment on column sys_social.expire_in is '鐢ㄦ埛鐨勬巿鏉冧护鐗岀殑鏈夋晥鏈燂紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.refresh_token is '鍒锋柊浠ょ墝锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�';
+comment on column sys_social.access_code is '骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.union_id is '鐢ㄦ埛鐨� unionid';
+comment on column sys_social.scope is '鎺堜簣鐨勬潈闄愶紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.token_type is '涓埆骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.id_token is 'id token锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�';
+comment on column sys_social.mac_algorithm is '灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.mac_key is '灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.code is '鐢ㄦ埛鐨勬巿鏉僣ode锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�';
+comment on column sys_social.oauth_token is 'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.oauth_token_secret is 'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_social.create_by is '鍒涘缓鑰�';
+comment on column sys_social.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_social.update_by is '鏇存柊鑰�';
+comment on column sys_social.update_time is '鏇存柊鏃堕棿';
+comment on column sys_social.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�';
+
+
+-- ----------------------------
+-- 绯荤粺鎺堟潈琛�
+-- ----------------------------
+create table sys_client (
+ id number(20) not null,
+ client_id varchar2(64) default null,
+ client_key varchar2(32) default null,
+ client_secret varchar2(255) default null,
+ grant_type varchar2(255) default null,
+ device_type varchar2(32) default null,
+ active_timeout number(11) default 1800,
+ timeout number(11) default 604800,
+ status char(1) default '0',
+ del_flag char(1) default '0',
+ create_dept number(20) default null,
+ create_by number(20) default null,
+ create_time date,
+ update_by number(20) default null,
+ update_time date
+);
+
+alter table sys_client add constraint pk_sys_client primary key (id);
+
+comment on table sys_client is '绯荤粺鎺堟潈琛�';
+comment on column sys_client.id is '涓婚敭';
+comment on column sys_client.client_id is '瀹㈡埛绔痠d';
+comment on column sys_client.client_key is '瀹㈡埛绔痥ey';
+comment on column sys_client.client_secret is '瀹㈡埛绔閽�';
+comment on column sys_client.grant_type is '鎺堟潈绫诲瀷';
+comment on column sys_client.device_type is '璁惧绫诲瀷';
+comment on column sys_client.active_timeout is 'token娲昏穬瓒呮椂鏃堕棿';
+comment on column sys_client.timeout is 'token鍥哄畾瓒呮椂';
+comment on column sys_client.status is '鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_client.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�';
+comment on column sys_client.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_client.create_by is '鍒涘缓鑰�';
+comment on column sys_client.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_client.update_by is '鏇存柊鑰�';
+comment on column sys_client.update_time is '鏇存柊鏃堕棿';
+
+insert into sys_client values (1, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 103, 1, sysdate, 1, sysdate);
+insert into sys_client values (2, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'android', 1800, 604800, 0, 0, 103, 1, sysdate, 1, sysdate);
+
+insert into sys_dict_type values(11, '000000', '鎺堟潈绫诲瀷', 'sys_grant_type', '0', 103, 1, sysdate, null, null, '璁よ瘉鎺堟潈绫诲瀷');
+insert into sys_dict_type values(12, '000000', '璁惧绫诲瀷', 'sys_device_type', '0', 103, 1, sysdate, null, null, '瀹㈡埛绔澶囩被鍨�');
+
+insert into sys_dict_data values(30, '000000', 0, '瀵嗙爜璁よ瘉', 'password', 'sys_grant_type', '', 'default', 'N', '0', 103, 1, sysdate, null, null, '瀵嗙爜璁よ瘉');
+insert into sys_dict_data values(31, '000000', 0, '鐭俊璁よ瘉', 'sms', 'sys_grant_type', '', 'default', 'N', '0', 103, 1, sysdate, null, null, '鐭俊璁よ瘉');
+insert into sys_dict_data values(32, '000000', 0, '閭欢璁よ瘉', 'email', 'sys_grant_type', '', 'default', 'N', '0', 103, 1, sysdate, null, null, '閭欢璁よ瘉');
+insert into sys_dict_data values(33, '000000', 0, '灏忕▼搴忚璇�', 'xcx', 'sys_grant_type', '', 'default', 'N', '0', 103, 1, sysdate, null, null, '灏忕▼搴忚璇�');
+insert into sys_dict_data values(34, '000000', 0, '涓夋柟鐧诲綍璁よ瘉', 'social', 'sys_grant_type', '', 'default', 'N', '0', 103, 1, sysdate, null, null, '涓夋柟鐧诲綍璁よ瘉');
+insert into sys_dict_data values(35, '000000', 0, 'PC', 'pc', 'sys_device_type', '', 'default', 'N', '0', 103, 1, sysdate, null, null, 'PC');
+insert into sys_dict_data values(36, '000000', 0, '瀹夊崜', 'android', 'sys_device_type', '', 'default', 'N', '0', 103, 1, sysdate, null, null, '瀹夊崜');
+insert into sys_dict_data values(37, '000000', 0, 'iOS', 'ios', 'sys_device_type', '', 'default', 'N', '0', 103, 1, sysdate, null, null, 'iOS');
+insert into sys_dict_data values(38, '000000', 0, '灏忕▼搴�', 'xcx', 'sys_device_type', '', 'default', 'N', '0', 103, 1, sysdate, null, null, '灏忕▼搴�');
+
+-- 浜岀骇鑿滃崟
+insert into sys_menu values('123', '瀹㈡埛绔鐞�', '1', '11', 'client', 'system/client/index', '', 1, 0, 'C', '0', '0', 'system:client:list', 'international', 103, 1, sysdate, null, null, '瀹㈡埛绔鐞嗚彍鍗�');
+-- 瀹㈡埛绔鐞嗘寜閽�
+insert into sys_menu values('1061', '瀹㈡埛绔鐞嗘煡璇�', '123', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1062', '瀹㈡埛绔鐞嗘柊澧�', '123', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1063', '瀹㈡埛绔鐞嗕慨鏀�', '123', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1064', '瀹㈡埛绔鐞嗗垹闄�', '123', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:remove', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1065', '瀹㈡埛绔鐞嗗鍑�', '123', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:export', '#', 103, 1, sysdate, null, null, '');
+
+-- 瑙掕壊鑿滃崟鏉冮檺
+insert into sys_role_menu values ('2', '1061');
+insert into sys_role_menu values ('2', '1062');
+insert into sys_role_menu values ('2', '1063');
+insert into sys_role_menu values ('2', '1064');
+insert into sys_role_menu values ('2', '1065');
+
+
+update sys_dept set leader = null;
+ALTER TABLE sys_dept MODIFY (leader NUMBER(20))
diff --git a/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.1.0-5.1.1.sql b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.1.0-5.1.1.sql
new file mode 100755
index 0000000..979a4bd
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.1.0-5.1.1.sql
@@ -0,0 +1,5 @@
+ALTER TABLE sys_logininfor ADD (client_key varchar2(32) DEFAULT '');
+COMMENT ON COLUMN sys_logininfor.client_key IS '瀹㈡埛绔�';
+
+ALTER TABLE sys_logininfor ADD (device_type varchar2(32) DEFAULT '');
+COMMENT ON COLUMN sys_logininfor.device_type IS '璁惧绫诲瀷';
diff --git a/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.1.1-5.1.2.sql b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.1.1-5.1.2.sql
new file mode 100755
index 0000000..d7c030c
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.1.1-5.1.2.sql
@@ -0,0 +1,6 @@
+delete from sys_menu where menu_id in (1604, 1605);
+insert into sys_menu values('1620', '閰嶇疆鍒楄〃', '118', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:list', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1621', '閰嶇疆娣诲姞', '118', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:add', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1622', '閰嶇疆缂栬緫', '118', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:edit', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1623', '閰嶇疆鍒犻櫎', '118', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:remove', '#', 103, 1, sysdate, null, null, '');
+
diff --git a/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.1.2-5.2.0.sql b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.1.2-5.2.0.sql
new file mode 100755
index 0000000..1aa585a
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.1.2-5.2.0.sql
@@ -0,0 +1,9 @@
+ALTER TABLE sys_dept ADD (dept_category varchar2(100) DEFAULT NULL) COMMENT '閮ㄩ棬绫诲埆缂栫爜';
+COMMENT ON COLUMN sys_dept.dept_category IS '閮ㄩ棬绫诲埆缂栫爜';
+ALTER TABLE sys_post ADD (dept_id number(20) NOT NULL) COMMENT '閮ㄩ棬id';
+COMMENT ON COLUMN sys_post.dept_id IS '閮ㄩ棬id';
+ALTER TABLE sys_post ADD (post_category VARCHAR2(100) DEFAULT NULL) COMMENT '宀椾綅绫诲埆缂栫爜';
+COMMENT ON COLUMN sys_post.post_category IS '宀椾綅绫诲埆缂栫爜';
+UPDATE sys_post SET dept_id = 100;
+UPDATE sys_post SET dept_id = 103 where post_id = 1;
+UPDATE sys_menu SET menu_name = 'SnailJob鎺у埗鍙�', path = 'snailjob', component = 'monitor/snailjob/index', perms = 'monitor:snailjob:list', remark = 'SnailJob鎺у埗鍙拌彍鍗�' WHERE menu_id = 120;
diff --git a/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.3.0-5.3.1.sql b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.3.0-5.3.1.sql
new file mode 100755
index 0000000..09000fe
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.3.0-5.3.1.sql
@@ -0,0 +1,7 @@
+ALTER TABLE flow_node DROP COLUMN skip_any_node;
+ALTER TABLE flow_node ADD (ext VARCHAR2(500));
+COMMENT ON COLUMN flow_node.ext IS '鎵╁睍灞炴��';
+create index USER_ASSOCIATED_IDX on FLOW_USER (ASSOCIATED);
+
+ALTER TABLE sys_oss ADD (ext1 VARCHAR2(500));
+COMMENT ON COLUMN sys_oss.ext1 IS '鎵╁睍灞炴��';
diff --git a/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.3.1-5.4.0.sql b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.3.1-5.4.0.sql
new file mode 100755
index 0000000..3561f4d
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.3.1-5.4.0.sql
@@ -0,0 +1,18 @@
+ALTER TABLE flow_task ADD (flow_status VARCHAR2(20));
+COMMENT ON COLUMN flow_task.flow_status IS '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�';
+
+COMMENT ON COLUMN flow_instance.flow_status IS '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�';
+
+COMMENT ON COLUMN flow_his_task.flow_status IS '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�';
+
+ALTER TABLE sys_social
+ MODIFY (access_token VARCHAR2(2000 BYTE))
+ MODIFY (refresh_token VARCHAR2(2000 BYTE));
+
+INSERT INTO sys_menu VALUES ('116', '淇敼鐢熸垚閰嶇疆', '3', '2', 'gen-edit/index/:tableId', 'tool/gen/editTable', '', 1, 1, 'C', '1', '0', 'tool:gen:edit', '#', 103, 1, sysdate, null, null, '');
+INSERT INTO sys_menu VALUES ('130', '鍒嗛厤鐢ㄦ埛', '1', '2', 'role-auth/user/:roleId', 'system/role/authUser', '', 1, 1, 'C', '1', '0', 'system:role:edit', '#', 103, 1, sysdate, null, null, '');
+INSERT INTO sys_menu VALUES ('131', '鍒嗛厤瑙掕壊', '1', '1', 'user-auth/role/:userId', 'system/user/authRole', '', 1, 1, 'C', '1', '0', 'system:user:edit', '#', 103, 1, sysdate, null, null, '');
+INSERT INTO sys_menu VALUES ('132', '瀛楀吀鏁版嵁', '1', '6', 'dict-data/index/:dictId', 'system/dict/data', '', 1, 1, 'C', '1', '0', 'system:dict:list', '#', 103, 1, sysdate, null, null, '');
+INSERT INTO sys_menu VALUES ('133', '鏂囦欢閰嶇疆绠$悊', '1', '10', 'oss-config/index', 'system/oss/config', '', 1, 1, 'C', '1', '0', 'system:ossConfig:list', '#', 103, 1, sysdate, null, null, '');
+INSERT INTO sys_menu VALUES ('11700', '娴佺▼璁捐', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11701', '璇峰亣鐢宠', '11616', '6', 'leaveEdit/index', 'workflow/leave/leaveEdit', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
diff --git a/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.4.1-5.5.0.sql b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.4.1-5.5.0.sql
new file mode 100755
index 0000000..455501f
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.4.1-5.5.0.sql
@@ -0,0 +1,97 @@
+-- ----------------------------
+-- 娴佺▼spel琛ㄨ揪寮忓畾涔夎〃
+-- ----------------------------
+CREATE TABLE flow_spel (
+ id NUMBER(20) NOT NULL,
+ component_name VARCHAR2(255),
+ method_name VARCHAR2(255),
+ method_params VARCHAR2(255),
+ view_spel VARCHAR2(255),
+ remark VARCHAR2(255),
+ status CHAR(1) DEFAULT '0',
+ del_flag CHAR(1) DEFAULT '0',
+ create_dept NUMBER(20),
+ create_by NUMBER(20),
+ create_time DATE,
+ update_by NUMBER(20),
+ update_time DATE
+);
+
+alter table flow_spel add constraint pk_flow_spel primary key (id);
+
+COMMENT ON TABLE flow_spel IS '娴佺▼spel琛ㄨ揪寮忓畾涔夎〃';
+COMMENT ON COLUMN flow_spel.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_spel.component_name IS '缁勪欢鍚嶇О';
+COMMENT ON COLUMN flow_spel.method_name IS '鏂规硶鍚�';
+COMMENT ON COLUMN flow_spel.method_params IS '鍙傛暟';
+COMMENT ON COLUMN flow_spel.view_spel IS '棰勮spel琛ㄨ揪寮�';
+COMMENT ON COLUMN flow_spel.remark IS '澶囨敞';
+COMMENT ON COLUMN flow_spel.status IS '鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+COMMENT ON COLUMN flow_spel.del_flag IS '鍒犻櫎鏍囧織';
+COMMENT ON COLUMN flow_spel.create_dept IS '鍒涘缓閮ㄩ棬';
+COMMENT ON COLUMN flow_spel.create_by IS '鍒涘缓鑰�';
+COMMENT ON COLUMN flow_spel.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_spel.update_by IS '鏇存柊鑰�';
+COMMENT ON COLUMN flow_spel.update_time IS '鏇存柊鏃堕棿';
+
+INSERT INTO flow_spel VALUES (1, 'spelRuleComponent', 'selectDeptLeaderById', 'initiatorDeptId', '#{@spelRuleComponent.selectDeptLeaderById(#initiatorDeptId)}', '鏍规嵁閮ㄩ棬id鑾峰彇閮ㄩ棬璐熻矗浜�', '0', '0', 103, 1, SYSDATE, 1, SYSDATE);
+INSERT INTO flow_spel VALUES (2, NULL, NULL, 'initiator', '${initiator}', '娴佺▼鍙戣捣浜�', '0', '0', 103, 1, SYSDATE, 1, SYSDATE);
+
+INSERT INTO sys_menu VALUES ('11801', '娴佺▼琛ㄨ揪寮�', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, SYSDATE, 1, SYSDATE, '娴佺▼杈惧紡瀹氫箟鑿滃崟');
+INSERT INTO sys_menu VALUES ('11802', '娴佺▼spel琛ㄨ揪寮忓畾涔夋煡璇�', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11803', '娴佺▼spel琛ㄨ揪寮忓畾涔夋柊澧�', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11804', '娴佺▼spel琛ㄨ揪寮忓畾涔変慨鏀�', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11805', '娴佺▼spel琛ㄨ揪寮忓畾涔夊垹闄�', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11806', '娴佺▼spel琛ㄨ揪寮忓畾涔夊鍑�', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
+
+ALTER TABLE flow_definition ADD model_value VARCHAR2(40) DEFAULT 'CLASSICS' NOT NULL;
+COMMENT ON COLUMN flow_definition.model_value IS '璁捐鍣ㄦā寮忥紙CLASSICS缁忓吀妯″紡 MIMIC浠块拤閽夋ā寮忥級';
+
+UPDATE flow_skip SET skip_condition = REPLACE(skip_condition, 'notNike', 'notLike');
+
+ALTER TABLE flow_his_task MODIFY (collaborator VARCHAR2(500) DEFAULT NULL NULL);
+COMMENT ON COLUMN flow_his_task.collaborator IS '鍗忎綔浜�';
+
+-- ----------------------------
+-- 娴佺▼瀹炰緥涓氬姟鎵╁睍琛�
+-- ----------------------------
+CREATE TABLE flow_instance_biz_ext (
+ id NUMBER(20),
+ tenant_id VARCHAR2(20) DEFAULT '000000',
+ create_dept NUMBER(20),
+ create_by NUMBER(20),
+ create_time TIMESTAMP,
+ update_by NUMBER(20),
+ update_time TIMESTAMP,
+ business_code VARCHAR2(255),
+ business_title VARCHAR2(1000),
+ del_flag CHAR(1) DEFAULT '0',
+ instance_id NUMBER(20),
+ business_id VARCHAR2(255)
+);
+
+alter table flow_instance_biz_ext add constraint pk_fi_biz_ext primary key (id);
+
+COMMENT ON TABLE flow_instance_biz_ext IS '娴佺▼瀹炰緥涓氬姟鎵╁睍琛�';
+COMMENT ON COLUMN flow_instance_biz_ext.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_instance_biz_ext.tenant_id IS '绉熸埛缂栧彿';
+COMMENT ON COLUMN flow_instance_biz_ext.create_dept IS '鍒涘缓閮ㄩ棬';
+COMMENT ON COLUMN flow_instance_biz_ext.create_by IS '鍒涘缓鑰�';
+COMMENT ON COLUMN flow_instance_biz_ext.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_instance_biz_ext.update_by IS '鏇存柊鑰�';
+COMMENT ON COLUMN flow_instance_biz_ext.update_time IS '鏇存柊鏃堕棿';
+COMMENT ON COLUMN flow_instance_biz_ext.business_code IS '涓氬姟缂栫爜';
+COMMENT ON COLUMN flow_instance_biz_ext.business_title IS '涓氬姟鏍囬';
+COMMENT ON COLUMN flow_instance_biz_ext.del_flag IS '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+COMMENT ON COLUMN flow_instance_biz_ext.instance_id IS '娴佺▼瀹炰緥Id';
+COMMENT ON COLUMN flow_instance_biz_ext.business_id IS '涓氬姟Id';
+
+ALTER TABLE test_leave ADD COLUMN apply_code VARCHAR2(50) NOT NULL;
+COMMENT ON COLUMN test_leave.apply_code IS '鐢宠缂栧彿';
+
+update sys_menu set remark = '/tool/gen' where menu_id = 116;
+update sys_menu set remark = '/system/role' where menu_id = 130;
+update sys_menu set remark = '/system/user' where menu_id = 131;
+update sys_menu set remark = '/system/dict' where menu_id = 132;
+update sys_menu set remark = '/system/oss' where menu_id = 133;
+update sys_menu set remark = '/workflow/processDefinition' where menu_id = 11700;
diff --git a/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.5.0-5.5.1.sql b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.5.0-5.5.1.sql
new file mode 100755
index 0000000..8fa28c0
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.5.0-5.5.1.sql
@@ -0,0 +1,25 @@
+ALTER TABLE flow_definition ADD create_by VARCHAR2(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_definition ADD update_by VARCHAR2(64) DEFAULT '' NOT NULL;
+COMMENT ON COLUMN flow_definition.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_definition.update_by IS '鏇存柊浜�';
+
+ALTER TABLE flow_node ADD create_by VARCHAR2(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_node ADD update_by VARCHAR2(64) DEFAULT '' NOT NULL;
+COMMENT ON COLUMN flow_node.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_node.update_by IS '鏇存柊浜�';
+
+ALTER TABLE flow_skip ADD create_by VARCHAR2(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_skip ADD update_by VARCHAR2(64) DEFAULT '' NOT NULL;
+COMMENT ON COLUMN flow_skip.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_skip.update_by IS '鏇存柊浜�';
+
+ALTER TABLE flow_instance ADD update_by VARCHAR2(64) DEFAULT '' NOT NULL;
+COMMENT ON COLUMN flow_instance.update_by IS '鏇存柊浜�';
+
+ALTER TABLE flow_task ADD create_by VARCHAR2(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_task ADD update_by VARCHAR2(64) DEFAULT '' NOT NULL;
+COMMENT ON COLUMN flow_task.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_task.update_by IS '鏇存柊浜�';
+
+ALTER TABLE flow_user ADD update_by VARCHAR2(64) DEFAULT '' NOT NULL;
+COMMENT ON COLUMN flow_user.update_by IS '鏇存柊浜�';
diff --git a/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.5.1-5.5.2.sql b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.5.1-5.5.2.sql
new file mode 100755
index 0000000..17600d6
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/oracle/update_5.5.1-5.5.2.sql
@@ -0,0 +1,4 @@
+ALTER TABLE flow_node MODIFY (node_ratio VARCHAR2(200) DEFAULT NULL NULL);
+COMMENT ON COLUMN flow_node.node_ratio IS '娴佺▼绛剧讲姣斾緥鍊�';
+ALTER TABLE flow_node DROP COLUMN handler_type;
+ALTER TABLE flow_node DROP COLUMN handler_path;
diff --git a/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.0-5.1.sql b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.0-5.1.sql
new file mode 100755
index 0000000..f5f0a5c
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.0-5.1.sql
@@ -0,0 +1,150 @@
+ALTER TABLE gen_table ADD data_name varchar(200) default ''::varchar;
+
+COMMENT ON COLUMN gen_table.data_name IS '鏁版嵁婧愬悕绉�';
+
+UPDATE sys_menu SET path = 'powerjob', component = 'monitor/powerjob/index', perms = 'monitor:powerjob:list', remark = 'powerjob鎺у埗鍙拌彍鍗�' WHERE menu_id = 120;
+
+-- ----------------------------
+-- 绗笁鏂瑰钩鍙版巿鏉冭〃
+-- ----------------------------
+create table sys_social
+(
+ id int8 not null,
+ user_id int8 not null,
+ tenant_id varchar(20) default null::varchar,
+ auth_id varchar(255) not null,
+ source varchar(255) not null,
+ open_id varchar(255) default null::varchar,
+ user_name varchar(30) not null,
+ nick_name varchar(30) default ''::varchar,
+ email varchar(255) default ''::varchar,
+ avatar varchar(500) default ''::varchar,
+ access_token varchar(255) not null,
+ expire_in int8 default null,
+ refresh_token varchar(255) default null::varchar,
+ access_code varchar(255) default null::varchar,
+ union_id varchar(255) default null::varchar,
+ scope varchar(255) default null::varchar,
+ token_type varchar(255) default null::varchar,
+ id_token varchar(255) default null::varchar,
+ mac_algorithm varchar(255) default null::varchar,
+ mac_key varchar(255) default null::varchar,
+ code varchar(255) default null::varchar,
+ oauth_token varchar(255) default null::varchar,
+ oauth_token_secret varchar(255) default null::varchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ del_flag char default '0'::bpchar,
+ constraint "pk_sys_social" primary key (id)
+);
+
+comment on table sys_social is '绀句細鍖栧叧绯昏〃';
+comment on column sys_social.id is '涓婚敭';
+comment on column sys_social.user_id is '鐢ㄦ埛ID';
+comment on column sys_social.tenant_id is '绉熸埛id';
+comment on column sys_social.auth_id is '骞冲彴+骞冲彴鍞竴id';
+comment on column sys_social.source is '鐢ㄦ埛鏉ユ簮';
+comment on column sys_social.open_id is '骞冲彴缂栧彿鍞竴id';
+comment on column sys_social.user_name is '鐧诲綍璐﹀彿';
+comment on column sys_social.nick_name is '鐢ㄦ埛鏄电О';
+comment on column sys_social.email is '鐢ㄦ埛閭';
+comment on column sys_social.avatar is '澶村儚鍦板潃';
+comment on column sys_social.access_token is '鐢ㄦ埛鐨勬巿鏉冧护鐗�';
+comment on column sys_social.expire_in is '鐢ㄦ埛鐨勬巿鏉冧护鐗岀殑鏈夋晥鏈燂紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.refresh_token is '鍒锋柊浠ょ墝锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�';
+comment on column sys_social.access_code is '骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.union_id is '鐢ㄦ埛鐨� unionid';
+comment on column sys_social.scope is '鎺堜簣鐨勬潈闄愶紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.token_type is '涓埆骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.id_token is 'id token锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�';
+comment on column sys_social.mac_algorithm is '灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.mac_key is '灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.code is '鐢ㄦ埛鐨勬巿鏉僣ode锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�';
+comment on column sys_social.oauth_token is 'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.oauth_token_secret is 'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁';
+comment on column sys_social.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_social.create_by is '鍒涘缓鑰�';
+comment on column sys_social.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_social.update_by is '鏇存柊鑰�';
+comment on column sys_social.update_time is '鏇存柊鏃堕棿';
+comment on column sys_social.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�';
+
+
+-- ----------------------------
+-- 绯荤粺鎺堟潈琛�
+-- ----------------------------
+drop table if exists sys_client;
+create table sys_client (
+ id int8,
+ client_id varchar(64) default ''::varchar,
+ client_key varchar(32) default ''::varchar,
+ client_secret varchar(255) default ''::varchar,
+ grant_type varchar(255) default ''::varchar,
+ device_type varchar(32) default ''::varchar,
+ active_timeout int4 default 1800,
+ timeout int4 default 604800,
+ status char(1) default '0'::bpchar,
+ del_flag char(1) default '0'::bpchar,
+ create_dept int8,
+ create_by int8,
+ create_time timestamp,
+ update_by int8,
+ update_time timestamp,
+ constraint sys_client_pk primary key (id)
+);
+
+comment on table sys_client is '绯荤粺鎺堟潈琛�';
+comment on column sys_client.id is '涓婚敭';
+comment on column sys_client.client_id is '瀹㈡埛绔痠d';
+comment on column sys_client.client_key is '瀹㈡埛绔痥ey';
+comment on column sys_client.client_secret is '瀹㈡埛绔閽�';
+comment on column sys_client.grant_type is '鎺堟潈绫诲瀷';
+comment on column sys_client.device_type is '璁惧绫诲瀷';
+comment on column sys_client.active_timeout is 'token娲昏穬瓒呮椂鏃堕棿';
+comment on column sys_client.timeout is 'token鍥哄畾瓒呮椂';
+comment on column sys_client.status is '鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+comment on column sys_client.del_flag is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�';
+comment on column sys_client.create_dept is '鍒涘缓閮ㄩ棬';
+comment on column sys_client.create_by is '鍒涘缓鑰�';
+comment on column sys_client.create_time is '鍒涘缓鏃堕棿';
+comment on column sys_client.update_by is '鏇存柊鑰�';
+comment on column sys_client.update_time is '鏇存柊鏃堕棿';
+
+insert into sys_client values (1, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 103, 1, now(), 1, now());
+insert into sys_client values (2, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'android', 1800, 604800, 0, 0, 103, 1, now(), 1, now());
+
+insert into sys_dict_type values(11, '000000', '鎺堟潈绫诲瀷', 'sys_grant_type', '0', 103, 1, now(), null, null, '璁よ瘉鎺堟潈绫诲瀷');
+insert into sys_dict_type values(12, '000000', '璁惧绫诲瀷', 'sys_device_type', '0', 103, 1, now(), null, null, '瀹㈡埛绔澶囩被鍨�');
+
+insert into sys_dict_data values(30, '000000', 0, '瀵嗙爜璁よ瘉', 'password', 'sys_grant_type', '', 'default', 'N', '0', 103, 1, now(), null, null, '瀵嗙爜璁よ瘉');
+insert into sys_dict_data values(31, '000000', 0, '鐭俊璁よ瘉', 'sms', 'sys_grant_type', '', 'default', 'N', '0', 103, 1, now(), null, null, '鐭俊璁よ瘉');
+insert into sys_dict_data values(32, '000000', 0, '閭欢璁よ瘉', 'email', 'sys_grant_type', '', 'default', 'N', '0', 103, 1, now(), null, null, '閭欢璁よ瘉');
+insert into sys_dict_data values(33, '000000', 0, '灏忕▼搴忚璇�', 'xcx', 'sys_grant_type', '', 'default', 'N', '0', 103, 1, now(), null, null, '灏忕▼搴忚璇�');
+insert into sys_dict_data values(34, '000000', 0, '涓夋柟鐧诲綍璁よ瘉', 'social', 'sys_grant_type', '', 'default', 'N', '0', 103, 1, now(), null, null, '涓夋柟鐧诲綍璁よ瘉');
+insert into sys_dict_data values(35, '000000', 0, 'PC', 'pc', 'sys_device_type', '', 'default', 'N', '0', 103, 1, now(), null, null, 'PC');
+insert into sys_dict_data values(36, '000000', 0, '瀹夊崜', 'android', 'sys_device_type', '', 'default', 'N', '0', 103, 1, now(), null, null, '瀹夊崜');
+insert into sys_dict_data values(37, '000000', 0, 'iOS', 'ios', 'sys_device_type', '', 'default', 'N', '0', 103, 1, now(), null, null, 'iOS');
+insert into sys_dict_data values(38, '000000', 0, '灏忕▼搴�', 'xcx', 'sys_device_type', '', 'default', 'N', '0', 103, 1, now(), null, null, '灏忕▼搴�');
+
+-- 浜岀骇鑿滃崟
+insert into sys_menu values('123', '瀹㈡埛绔鐞�', '1', '11', 'client', 'system/client/index', '', '1', '0', 'C', '0', '0', 'system:client:list', 'international', 103, 1, now(), null, null, '瀹㈡埛绔鐞嗚彍鍗�');
+-- 瀹㈡埛绔鐞嗘寜閽�
+insert into sys_menu values('1061', '瀹㈡埛绔鐞嗘煡璇�', '123', '1', '#', '', '', '1', '0', 'F', '0', '0', 'system:client:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1062', '瀹㈡埛绔鐞嗘柊澧�', '123', '2', '#', '', '', '1', '0', 'F', '0', '0', 'system:client:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1063', '瀹㈡埛绔鐞嗕慨鏀�', '123', '3', '#', '', '', '1', '0', 'F', '0', '0', 'system:client:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1064', '瀹㈡埛绔鐞嗗垹闄�', '123', '4', '#', '', '', '1', '0', 'F', '0', '0', 'system:client:remove', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1065', '瀹㈡埛绔鐞嗗鍑�', '123', '5', '#', '', '', '1', '0', 'F', '0', '0', 'system:client:export', '#', 103, 1, now(), null, null, '');
+
+-- 瑙掕壊鑿滃崟鏉冮檺
+insert into sys_role_menu values ('2', '1061');
+insert into sys_role_menu values ('2', '1062');
+insert into sys_role_menu values ('2', '1063');
+insert into sys_role_menu values ('2', '1064');
+insert into sys_role_menu values ('2', '1065');
+
+
+update sys_dept set leader = null;
+ALTER TABLE sys_dept ALTER COLUMN leader TYPE int8;
diff --git a/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.1.0-5.1.1.sql b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.1.0-5.1.1.sql
new file mode 100755
index 0000000..29f5507
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.1.0-5.1.1.sql
@@ -0,0 +1,5 @@
+ALTER TABLE sys_logininfor ADD client_key varchar(32) default ''::varchar;
+COMMENT ON COLUMN sys_logininfor.client_key IS '瀹㈡埛绔�';
+
+ALTER TABLE sys_logininfor ADD device_type varchar(32) default ''::varchar;
+COMMENT ON COLUMN sys_logininfor.device_type IS '璁惧绫诲瀷';
diff --git a/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.1.1-5.1.2.sql b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.1.1-5.1.2.sql
new file mode 100755
index 0000000..62eb836
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.1.1-5.1.2.sql
@@ -0,0 +1,5 @@
+delete from sys_menu where menu_id in (1604, 1605);
+insert into sys_menu values('1620', '閰嶇疆鍒楄〃', '118', '5', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:list', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1621', '閰嶇疆娣诲姞', '118', '6', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:add', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1622', '閰嶇疆缂栬緫', '118', '6', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:edit', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1623', '閰嶇疆鍒犻櫎', '118', '6', '#', '', '', '1', '0', 'F', '0', '0', 'system:ossConfig:remove', '#', 103, 1, now(), null, null, '');
diff --git a/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.1.2-5.2.0.sql b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.1.2-5.2.0.sql
new file mode 100755
index 0000000..5089a09
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.1.2-5.2.0.sql
@@ -0,0 +1,9 @@
+ALTER TABLE sys_dept ADD COLUMN dept_category varchar(100) default null::varchar;
+COMMENT ON COLUMN sys_dept.dept_category IS '瀹㈡埛绔�';
+ALTER TABLE sys_post ADD COLUMN dept_id int8 NOT NULL;
+COMMENT ON COLUMN sys_post.dept_id IS '閮ㄩ棬id';
+ALTER TABLE sys_post ADD COLUMN post_category varchar(100) default null::varchar;
+COMMENT ON COLUMN sys_post.post_category IS '宀椾綅绫诲埆缂栫爜';
+UPDATE sys_post SET dept_id = 100;
+UPDATE sys_post SET dept_id = 103 where post_id = 1;
+UPDATE sys_menu SET menu_name = 'SnailJob鎺у埗鍙�', path = 'snailjob', component = 'monitor/snailjob/index', perms = 'monitor:snailjob:list', remark = 'SnailJob鎺у埗鍙拌彍鍗�' WHERE menu_id = 120;
diff --git a/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.3.0-5.3.1.sql b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.3.0-5.3.1.sql
new file mode 100755
index 0000000..de5dd56
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.3.0-5.3.1.sql
@@ -0,0 +1,7 @@
+ALTER TABLE flow_node DROP COLUMN skip_any_node;
+ALTER TABLE flow_node ADD COLUMN ext varchar(500);
+COMMENT ON COLUMN flow_node.ext IS '鎵╁睍灞炴��';
+CREATE INDEX user_associated_idx ON FLOW_USER USING btree (associated);
+
+ALTER TABLE sys_oss ADD COLUMN ext1 varchar(500));
+COMMENT ON COLUMN sys_oss.ext1 IS '鎵╁睍灞炴��';
diff --git a/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.3.1-5.4.0.sql b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.3.1-5.4.0.sql
new file mode 100755
index 0000000..e0b67e2
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.3.1-5.4.0.sql
@@ -0,0 +1,18 @@
+ALTER TABLE flow_task ADD COLUMN flow_status varchar(20);
+COMMENT ON COLUMN flow_task.flow_status IS '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�';
+
+COMMENT ON COLUMN flow_instance.flow_status IS '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�';
+
+COMMENT ON COLUMN flow_his_task.flow_status IS '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�';
+
+ALTER TABLE sys_social
+ALTER COLUMN access_token TYPE varchar(2000),
+ALTER COLUMN refresh_token TYPE varchar(2000);
+
+INSERT INTO sys_menu VALUES ('116', '淇敼鐢熸垚閰嶇疆', '3', '2', 'gen-edit/index/:tableId', 'tool/gen/editTable', '', '1', '1', 'C', '1', '0', 'tool:gen:edit', '#', 103, 1, now(), null, null, '');
+INSERT INTO sys_menu VALUES ('130', '鍒嗛厤鐢ㄦ埛', '1', '2', 'role-auth/user/:roleId', 'system/role/authUser', '', '1', '1', 'C', '1', '0', 'system:role:edit', '#', 103, 1, now(), null, null, '');
+INSERT INTO sys_menu VALUES ('131', '鍒嗛厤瑙掕壊', '1', '1', 'user-auth/role/:userId', 'system/user/authRole', '', '1', '1', 'C', '1', '0', 'system:user:edit', '#', 103, 1, now(), null, null, '');
+INSERT INTO sys_menu VALUES ('132', '瀛楀吀鏁版嵁', '1', '6', 'dict-data/index/:dictId', 'system/dict/data', '', '1', '1', 'C', '1', '0', 'system:dict:list', '#', 103, 1, now(), null, null, '');
+INSERT INTO sys_menu VALUES ('133', '鏂囦欢閰嶇疆绠$悊', '1', '10', 'oss-config/index', 'system/oss/config', '', '1', '1', 'C', '1', '0', 'system:ossConfig:list', '#', 103, 1, now(), null, null, '');
+INSERT INTO sys_menu VALUES ('11700', '娴佺▼璁捐', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11701', '璇峰亣鐢宠', '11616', '6', 'leaveEdit/index', 'workflow/leave/leaveEdit', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, now(), NULL, NULL, '');
diff --git a/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.4.1-5.5.0.sql b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.4.1-5.5.0.sql
new file mode 100755
index 0000000..0bc80b4
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.4.1-5.5.0.sql
@@ -0,0 +1,99 @@
+-- ----------------------------
+-- 娴佺▼spel琛ㄨ揪寮忓畾涔夎〃
+-- ----------------------------
+CREATE TABLE flow_spel (
+ id BIGINT NOT NULL,
+ component_name VARCHAR(255),
+ method_name VARCHAR(255),
+ method_params VARCHAR(255),
+ view_spel VARCHAR(255),
+ remark VARCHAR(255),
+ status CHAR(1) DEFAULT '0',
+ del_flag CHAR(1) DEFAULT '0',
+ create_dept BIGINT,
+ create_by BIGINT,
+ create_time TIMESTAMP,
+ update_by BIGINT,
+ update_time TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+COMMENT ON TABLE flow_spel IS '娴佺▼spel琛ㄨ揪寮忓畾涔夎〃';
+COMMENT ON COLUMN flow_spel.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_spel.component_name IS '缁勪欢鍚嶇О';
+COMMENT ON COLUMN flow_spel.method_name IS '鏂规硶鍚�';
+COMMENT ON COLUMN flow_spel.method_params IS '鍙傛暟';
+COMMENT ON COLUMN flow_spel.view_spel IS '棰勮spel琛ㄨ揪寮�';
+COMMENT ON COLUMN flow_spel.remark IS '澶囨敞';
+COMMENT ON COLUMN flow_spel.status IS '鐘舵�侊紙0姝e父 1鍋滅敤锛�';
+COMMENT ON COLUMN flow_spel.del_flag IS '鍒犻櫎鏍囧織';
+COMMENT ON COLUMN flow_spel.create_dept IS '鍒涘缓閮ㄩ棬';
+COMMENT ON COLUMN flow_spel.create_by IS '鍒涘缓鑰�';
+COMMENT ON COLUMN flow_spel.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_spel.update_by IS '鏇存柊鑰�';
+COMMENT ON COLUMN flow_spel.update_time IS '鏇存柊鏃堕棿';
+
+INSERT INTO flow_spel VALUES (1, 'spelRuleComponent', 'selectDeptLeaderById', 'initiatorDeptId', '#{@spelRuleComponent.selectDeptLeaderById(#initiatorDeptId)}', '鏍规嵁閮ㄩ棬id鑾峰彇閮ㄩ棬璐熻矗浜�', '0', '0', 103, 1, now(), 1, now());
+INSERT INTO flow_spel VALUES (2, NULL, NULL, 'initiator', '${initiator}', '娴佺▼鍙戣捣浜�', '0', '0', 103, 1, now(), 1, now());
+
+INSERT INTO sys_menu VALUES ('11801', '娴佺▼琛ㄨ揪寮�', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, now(), 1, now(), '娴佺▼杈惧紡瀹氫箟鑿滃崟');
+INSERT INTO sys_menu VALUES ('11802', '娴佺▼spel琛ㄨ揪寮忓畾涔夋煡璇�', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11803', '娴佺▼spel琛ㄨ揪寮忓畾涔夋柊澧�', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11804', '娴佺▼spel琛ㄨ揪寮忓畾涔変慨鏀�', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11805', '娴佺▼spel琛ㄨ揪寮忓畾涔夊垹闄�', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11806', '娴佺▼spel琛ㄨ揪寮忓畾涔夊鍑�', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, now(), NULL, NULL, '');
+
+ALTER TABLE flow_definition ADD COLUMN model_value VARCHAR(40) NOT NULL DEFAULT 'CLASSICS';
+COMMENT ON COLUMN flow_definition.model_value IS '璁捐鍣ㄦā寮忥紙CLASSICS缁忓吀妯″紡 MIMIC浠块拤閽夋ā寮忥級';
+
+UPDATE flow_skip SET skip_condition = REPLACE(skip_condition, 'notNike', 'notLike');
+
+ALTER TABLE flow_his_task
+ ALTER COLUMN collaborator DROP NOT NULL,
+ ALTER COLUMN collaborator SET DEFAULT NULL,
+ ALTER COLUMN collaborator TYPE VARCHAR(500);
+COMMENT ON COLUMN flow_his_task.collaborator IS '鍗忎綔浜�';
+
+
+-- ----------------------------
+-- 娴佺▼瀹炰緥涓氬姟鎵╁睍琛�
+-- ----------------------------
+CREATE TABLE flow_instance_biz_ext (
+ id int8,
+ tenant_id VARCHAR(20) DEFAULT '000000',
+ create_dept int8,
+ create_by int8,
+ create_time TIMESTAMP,
+ update_by int8,
+ update_time TIMESTAMP,
+ business_code VARCHAR(255),
+ business_title VARCHAR(1000),
+ del_flag CHAR(1) DEFAULT '0',
+ instance_id int8,
+ business_id VARCHAR(255),
+ PRIMARY KEY (id)
+);
+
+COMMENT ON TABLE flow_instance_biz_ext IS '娴佺▼瀹炰緥涓氬姟鎵╁睍琛�';
+COMMENT ON COLUMN flow_instance_biz_ext.id IS '涓婚敭id';
+COMMENT ON COLUMN flow_instance_biz_ext.tenant_id IS '绉熸埛缂栧彿';
+COMMENT ON COLUMN flow_instance_biz_ext.create_dept IS '鍒涘缓閮ㄩ棬';
+COMMENT ON COLUMN flow_instance_biz_ext.create_by IS '鍒涘缓鑰�';
+COMMENT ON COLUMN flow_instance_biz_ext.create_time IS '鍒涘缓鏃堕棿';
+COMMENT ON COLUMN flow_instance_biz_ext.update_by IS '鏇存柊鑰�';
+COMMENT ON COLUMN flow_instance_biz_ext.update_time IS '鏇存柊鏃堕棿';
+COMMENT ON COLUMN flow_instance_biz_ext.business_code IS '涓氬姟缂栫爜';
+COMMENT ON COLUMN flow_instance_biz_ext.business_title IS '涓氬姟鏍囬';
+COMMENT ON COLUMN flow_instance_biz_ext.del_flag IS '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�';
+COMMENT ON COLUMN flow_instance_biz_ext.instance_id IS '娴佺▼瀹炰緥Id';
+COMMENT ON COLUMN flow_instance_biz_ext.business_id IS '涓氬姟Id';
+
+ALTER TABLE test_leave ADD COLUMN apply_code VARCHAR(50) NOT NULL;
+COMMENT ON COLUMN test_leave.apply_code IS '鐢宠缂栧彿';
+
+update sys_menu set remark = '/tool/gen' where menu_id = 116;
+update sys_menu set remark = '/system/role' where menu_id = 130;
+update sys_menu set remark = '/system/user' where menu_id = 131;
+update sys_menu set remark = '/system/dict' where menu_id = 132;
+update sys_menu set remark = '/system/oss' where menu_id = 133;
+update sys_menu set remark = '/workflow/processDefinition' where menu_id = 11700;
diff --git a/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.5.0-5.5.1.sql b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.5.0-5.5.1.sql
new file mode 100755
index 0000000..f1449f0
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.5.0-5.5.1.sql
@@ -0,0 +1,25 @@
+ALTER TABLE flow_definition ADD create_by VARCHAR(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_definition ADD update_by VARCHAR(64) DEFAULT '' NOT NULL;
+COMMENT ON COLUMN flow_definition.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_definition.update_by IS '鏇存柊浜�';
+
+ALTER TABLE flow_node ADD create_by VARCHAR(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_node ADD update_by VARCHAR(64) DEFAULT '' NOT NULL;
+COMMENT ON COLUMN flow_node.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_node.update_by IS '鏇存柊浜�';
+
+ALTER TABLE flow_skip ADD create_by VARCHAR(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_skip ADD update_by VARCHAR(64) DEFAULT '' NOT NULL;
+COMMENT ON COLUMN flow_skip.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_skip.update_by IS '鏇存柊浜�';
+
+ALTER TABLE flow_instance ADD update_by VARCHAR(64) DEFAULT '' NOT NULL;
+COMMENT ON COLUMN flow_instance.update_by IS '鏇存柊浜�';
+
+ALTER TABLE flow_task ADD create_by VARCHAR(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_task ADD update_by VARCHAR(64) DEFAULT '' NOT NULL;
+COMMENT ON COLUMN flow_task.create_by IS '鍒涘缓浜�';
+COMMENT ON COLUMN flow_task.update_by IS '鏇存柊浜�';
+
+ALTER TABLE flow_user ADD update_by VARCHAR(64) DEFAULT '' NOT NULL;
+COMMENT ON COLUMN flow_user.update_by IS '鏇存柊浜�';
diff --git a/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.5.1-5.5.2.sql b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.5.1-5.5.2.sql
new file mode 100755
index 0000000..bf2d69f
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/postgres/update_5.5.1-5.5.2.sql
@@ -0,0 +1,6 @@
+ALTER TABLE flow_node
+ ALTER COLUMN node_ratio TYPE VARCHAR(200),
+ ALTER COLUMN node_ratio DROP NOT NULL;
+COMMENT ON COLUMN flow_node.node_ratio IS '娴佺▼绛剧讲姣斾緥鍊�';
+ALTER TABLE flow_node DROP COLUMN handler_type;
+ALTER TABLE flow_node DROP COLUMN handler_path;
diff --git a/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.0-5.1.sql b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.0-5.1.sql
new file mode 100755
index 0000000..6ea54a5
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.0-5.1.sql
@@ -0,0 +1,409 @@
+ALTER TABLE gen_table ADD data_name nvarchar(200) DEFAULT '' NULL
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏁版嵁婧愬悕绉�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'gen_table',
+ 'COLUMN', N'data_name'
+GO
+
+UPDATE sys_menu SET path = 'powerjob', component = 'monitor/powerjob/index', perms = 'monitor:powerjob:list', remark = 'powerjob鎺у埗鍙拌彍鍗�' WHERE menu_id = 120
+GO
+
+create table sys_social
+(
+ id bigint NOT NULL,
+ user_id bigint NOT NULL,
+ tenant_id nvarchar(20) NULL,
+ auth_id nvarchar(255) NOT NULL,
+ source nvarchar(255) NOT NULL,
+ open_id nvarchar(255) NULL,
+ user_name nvarchar(30) NOT NULL,
+ nick_name nvarchar(30) DEFAULT ('') NULL,
+ email nvarchar(255) DEFAULT ('') NULL,
+ avatar nvarchar(500) DEFAULT ('') NULL,
+ access_token nvarchar(255) NOT NULL,
+ expire_in bigint NULL,
+ refresh_token nvarchar(255) NULL,
+ access_code nvarchar(255) NULL,
+ union_id nvarchar(255) NULL,
+ scope nvarchar(255) NULL,
+ token_type nvarchar(255) NULL,
+ id_token nvarchar(255) NULL,
+ mac_algorithm nvarchar(255) NULL,
+ mac_key nvarchar(255) NULL,
+ code nvarchar(255) NULL,
+ oauth_token nvarchar(255) NULL,
+ oauth_token_secret nvarchar(255) NULL,
+ create_dept bigint,
+ create_by bigint,
+ create_time datetime2(7),
+ update_by bigint,
+ update_time datetime2(7),
+ del_flag nchar DEFAULT ('0') NULL,
+ CONSTRAINT PK__sys_social__B21E8F2427725F8A PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'id' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛ID' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'user_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'绉熸埛id' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'tenant_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'骞冲彴+骞冲彴鍞竴id' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'auth_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鏉ユ簮' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'source'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'骞冲彴缂栧彿鍞竴id' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'open_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐧诲綍璐﹀彿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'user_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鏄电О' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'nick_name'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛閭' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'email'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'澶村儚鍦板潃' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'avatar'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鐨勬巿鏉冧护鐗�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'access_token'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鐨勬巿鏉冧护鐗岀殑鏈夋晥鏈燂紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'expire_in'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒锋柊浠ょ墝锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'refresh_token'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'access_code'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鐨� unionid' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'union_id'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鎺堜簣鐨勬潈闄愶紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'scope'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'涓埆骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'token_type'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'id token锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'id_token'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'mac_algorithm'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'mac_key'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鐢ㄦ埛鐨勬巿鏉僣ode锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'code'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'oauth_token'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'oauth_token_secret'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'del_flag'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'create_dept'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'create_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'create_time'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'update_by'
+GO
+EXEC sys.sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿' ,
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_social',
+ 'COLUMN', N'update_time'
+GO
+
+
+CREATE TABLE sys_client
+(
+ id bigint NOT NULL,
+ client_id nvarchar(64) DEFAULT '' NULL,
+ client_key nvarchar(32) DEFAULT '' NULL,
+ client_secret nvarchar(255) DEFAULT '' NULL,
+ grant_type nvarchar(255) DEFAULT '' NULL,
+ device_type nvarchar(32) DEFAULT '' NULL,
+ active_timeout int DEFAULT ((1800)) NULL,
+ timeout int DEFAULT ((604800)) NULL,
+ status nchar(1) DEFAULT ('0') NULL,
+ del_flag nchar(1) DEFAULT ('0') NULL,
+ create_dept bigint NULL,
+ create_by bigint NULL,
+ create_time datetime2(7) NULL,
+ update_by bigint NULL,
+ update_time datetime2(7) NULL
+ CONSTRAINT PK__sys_client___BFBDE87009ED2882 PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'涓婚敭',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'id'
+GO
+EXEC sys.sp_addextendedproperty
+'MS_Description', N'瀹㈡埛绔痠d' ,
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'client_id'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹㈡埛绔痥ey',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'client_key'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹㈡埛绔閽�',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'client_secret'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'鎺堟潈绫诲瀷',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'grant_type'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'璁惧绫诲瀷',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'device_type'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'token娲昏穬瓒呮椂鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'active_timeout'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'token鍥哄畾瓒呮椂',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'timeout'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'status'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'del_flag'
+GO
+EXEC sys.sp_addextendedproperty
+'MS_Description', N'鍒涘缓閮ㄩ棬' ,
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'create_dept'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓鑰�',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'create_by'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'create_time'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊鑰�',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'update_by'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊鏃堕棿',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'update_time'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'绯荤粺鎺堟潈琛�',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client'
+GO
+
+INSERT INTO sys_client VALUES (N'1', N'e5cd7e4891bf95d1d19206ce24a7b32e', N'pc', N'pc123', N'password,social', N'pc', 1800, 604800, N'0', N'0', 103, 1, getdate(), 1, getdate())
+GO
+INSERT INTO sys_client VALUES (N'2', N'428a8310cd442757ae699df5d894f051', N'app', N'app123', N'password,sms,social', N'android', 1800, 604800, N'0', N'0', 103, 1, getdate(), 1, getdate())
+GO
+
+INSERT sys_dict_type VALUES (11, N'000000', N'鎺堟潈绫诲瀷', N'sys_grant_type', N'0', 103, 1, getdate(), NULL, NULL, N'璁よ瘉鎺堟潈绫诲瀷')
+GO
+INSERT sys_dict_type VALUES (12, N'000000', N'璁惧绫诲瀷', N'sys_device_type', N'0', 103, 1, getdate(), NULL, NULL, N'瀹㈡埛绔澶囩被鍨�')
+GO
+
+INSERT sys_dict_data VALUES (30, N'000000', 0, N'瀵嗙爜璁よ瘉', N'password', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'瀵嗙爜璁よ瘉');
+GO
+INSERT sys_dict_data VALUES (31, N'000000', 0, N'鐭俊璁よ瘉', N'sms', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'鐭俊璁よ瘉')
+GO
+INSERT sys_dict_data VALUES (32, N'000000', 0, N'閭欢璁よ瘉', N'email', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'閭欢璁よ瘉')
+GO
+INSERT sys_dict_data VALUES (33, N'000000', 0, N'灏忕▼搴忚璇�', N'xcx', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'灏忕▼搴忚璇�')
+GO
+INSERT sys_dict_data VALUES (34, N'000000', 0, N'涓夋柟鐧诲綍璁よ瘉', N'social', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'涓夋柟鐧诲綍璁よ瘉')
+GO
+INSERT sys_dict_data VALUES (35, N'000000', 0, N'PC', N'`pc`', N'sys_device_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'PC')
+GO
+INSERT sys_dict_data VALUES (36, N'000000', 0, N'瀹夊崜', N'`android`', N'sys_device_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'瀹夊崜')
+GO
+INSERT sys_dict_data VALUES (37, N'000000', 0, N'iOS', N'`ios`', N'sys_device_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'iOS')
+GO
+INSERT sys_dict_data VALUES (38, N'000000', 0, N'灏忕▼搴�', N'`xcx`', N'sys_device_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'灏忕▼搴�')
+GO
+
+-- 浜岀骇鑿滃崟
+INSERT sys_menu VALUES (123, N'瀹㈡埛绔鐞�', 1, 11, N'client', N'system/client/index', N'', 1, 0, N'C', N'0', N'0', N'system:client:list', N'international', 103, 1, getdate(), NULL, NULL, N'瀹㈡埛绔鐞嗚彍鍗�')
+GO
+-- 瀹㈡埛绔鐞嗘寜閽�
+INSERT sys_menu VALUES (1061, N'瀹㈡埛绔鐞嗘煡璇�', 123, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:query', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1062, N'瀹㈡埛绔鐞嗘柊澧�', 123, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:add', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1063, N'瀹㈡埛绔鐞嗕慨鏀�', 123, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:edit', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1064, N'瀹㈡埛绔鐞嗗垹闄�', 123, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:remove', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1065, N'瀹㈡埛绔鐞嗗鍑�', 123, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:export', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+
+
+-- 瑙掕壊鑿滃崟鏉冮檺
+INSERT sys_role_menu VALUES (2, 1061)
+GO
+INSERT sys_role_menu VALUES (2, 1062)
+GO
+INSERT sys_role_menu VALUES (2, 1063)
+GO
+INSERT sys_role_menu VALUES (2, 1064)
+GO
+INSERT sys_role_menu VALUES (2, 1065)
+GO
+
+
+UPDATE sys_dept SET leader = null
+GO
+ALTER TABLE sys_dept ALTER COLUMN leader bigint NULL
+GO
diff --git a/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.1.0-5.1.1.sql b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.1.0-5.1.1.sql
new file mode 100755
index 0000000..2238536
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.1.0-5.1.1.sql
@@ -0,0 +1,19 @@
+ALTER TABLE sys_logininfor ADD client_key nvarchar(32) DEFAULT '' NULL
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'瀹㈡埛绔�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'client_key'
+GO
+
+ALTER TABLE sys_logininfor ADD device_type nvarchar(32) DEFAULT '' NULL
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'璁惧绫诲瀷',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_logininfor',
+ 'COLUMN', N'device_type'
+GO
diff --git a/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.1.1-5.1.2.sql b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.1.1-5.1.2.sql
new file mode 100755
index 0000000..9133772
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.1.1-5.1.2.sql
@@ -0,0 +1,10 @@
+DELETE FROM sys_menu WHERE menu_id IN (1604, 1605);
+GO
+INSERT sys_menu VALUES (1620, N'閰嶇疆鍒楄〃', 118, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:ossConfig:list', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1621, N'閰嶇疆娣诲姞', 118, 6, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:ossConfig:add', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1622, N'閰嶇疆缂栬緫', 118, 6, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:ossConfig:edit', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1623, N'閰嶇疆鍒犻櫎', 118, 6, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:ossConfig:remove', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
diff --git a/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.1.2-5.2.0.sql b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.1.2-5.2.0.sql
new file mode 100755
index 0000000..18daca4
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.1.2-5.2.0.sql
@@ -0,0 +1,29 @@
+ALTER TABLE sys_dept ADD dept_category nvarchar(100) DEFAULT NULL
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬绫诲埆缂栫爜',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_dept',
+ 'COLUMN', N'dept_category'
+GO
+ALTER TABLE sys_post ADD dept_id bigint NOT NULL
+GO
+ALTER TABLE sys_post ADD post_category nvarchar(100) DEFAULT NULL
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'閮ㄩ棬id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'dept_id'
+GO
+EXEC sp_addextendedproperty
+ 'MS_Description', N'宀椾綅绫诲埆缂栫爜',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'sys_post',
+ 'COLUMN', N'post_category'
+GO
+UPDATE sys_post SET dept_id = 100
+GO
+UPDATE sys_post SET dept_id = 103 where post_id = 1
+GO
+UPDATE sys_menu SET menu_name = N'SnailJob鎺у埗鍙�', path = N'snailjob', component = N'monitor/snailjob/index', perms = N'monitor:snailjob:list', remark = N'SnailJob鎺у埗鍙拌彍鍗�' WHERE menu_id = 120
+GO
diff --git a/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.3.0-5.3.1.sql b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.3.0-5.3.1.sql
new file mode 100755
index 0000000..50859de
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.3.0-5.3.1.sql
@@ -0,0 +1,21 @@
+ALTER TABLE flow_node DROP COLUMN skip_any_node;
+ALTER TABLE flow_node ADD ext nvarchar(500) NULL;
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鎵╁睍灞炴��',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'ext'
+GO
+
+CREATE NONCLUSTERED INDEX user_associated_idx ON flow_user (associated ASC)
+GO
+
+ALTER TABLE sys_oss ADD ext1 nvarchar(500) NULL;
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鎵╁睍灞炴��',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_oss',
+'COLUMN', N'ext1'
+GO
diff --git a/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.3.1-5.4.0.sql b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.3.1-5.4.0.sql
new file mode 100755
index 0000000..1535566
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.3.1-5.4.0.sql
@@ -0,0 +1,63 @@
+ALTER TABLE flow_task ADD flow_status nvarchar(20) NULL;
+
+EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'flow_status'
+GO
+
+IF ((SELECT COUNT(*) FROM ::fn_listextendedproperty('MS_Description',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'flow_status')) > 0)
+ EXEC sp_updateextendedproperty
+'MS_Description', N'娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'flow_status'
+ELSE
+ EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'flow_status'
+GO
+
+IF ((SELECT COUNT(*) FROM ::fn_listextendedproperty('MS_Description',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'flow_status')) > 0)
+ EXEC sp_updateextendedproperty
+'MS_Description', N'娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'flow_status'
+ELSE
+ EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'flow_status'
+GO
+
+-- sys_social 琛ㄤ慨鏀瑰垪
+ALTER TABLE sys_social ALTER COLUMN access_token VARCHAR(2000) NOT NULL
+GO
+ALTER TABLE sys_social ALTER COLUMN refresh_token VARCHAR(2000) NULL
+GO
+
+INSERT sys_menu VALUES (116, N'淇敼鐢熸垚閰嶇疆', 3, 2, N'gen-edit/index/:tableId', N'tool/gen/editTable', N'', 1, 1, N'C', N'1', N'0', N'tool:gen:edit', N'#', 103, 1, getdate(), null, null, N'');
+GO
+INSERT sys_menu VALUES (130, N'鍒嗛厤鐢ㄦ埛', 1, 2, N'role-auth/user/:roleId', N'system/role/authUser', N'', 1, 1, N'C', N'1', N'0', N'system:role:edit', N'#', 103, 1, getdate(), null, null, N'');
+GO
+INSERT sys_menu VALUES (131, N'鍒嗛厤瑙掕壊', 1, 1, N'user-auth/role/:userId', N'system/user/authRole', N'', 1, 1, N'C', N'1', N'0', N'system:user:edit', N'#', 103, 1, getdate(), null, null, N'');
+GO
+INSERT sys_menu VALUES (132, N'瀛楀吀鏁版嵁', 1, 6, N'dict-data/index/:dictId', N'system/dict/data', N'', 1, 1, N'C', N'1', N'0', N'system:dict:list', N'#', 103, 1, getdate(), null, null, N'');
+GO
+INSERT sys_menu VALUES (133, N'鏂囦欢閰嶇疆绠$悊', 1, 10, N'oss-config/index', N'system/oss/config', N'', 1, 1, N'C', N'1', N'0', N'system:ossConfig:list', N'#', 103, 1, getdate(), null, null, N'');
+GO
+INSERT sys_menu VALUES (11700, N'娴佺▼璁捐', 11616, 5, N'design/index', N'workflow/processDefinition/design', N'', 1, 1, N'C', N'1', N'0', N'workflow:leave:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11701, N'璇峰亣鐢宠', 11616, 6, N'leaveEdit/index', N'workflow/leave/leaveEdit', N'', 1, 1, N'C', N'1', N'0', N'workflow:leave:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
diff --git a/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.4.1-5.5.0.sql b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.4.1-5.5.0.sql
new file mode 100755
index 0000000..ce6b6dd
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.4.1-5.5.0.sql
@@ -0,0 +1,294 @@
+-- ----------------------------
+-- 娴佺▼spel琛ㄨ揪寮忓畾涔夎〃
+-- ----------------------------
+CREATE TABLE flow_spel (
+ id BIGINT NOT NULL,
+ component_name VARCHAR(255),
+ method_name VARCHAR(255),
+ method_params VARCHAR(255),
+ view_spel VARCHAR(255),
+ remark VARCHAR(255),
+ status CHAR(1) DEFAULT ('0'),
+ del_flag CHAR(1) DEFAULT ('0'),
+ create_dept BIGINT,
+ create_by BIGINT,
+ create_time DATETIME,
+ update_by BIGINT,
+ update_time DATETIME,
+ CONSTRAINT PK_flow_spel PRIMARY KEY (id)
+);
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'娴佺▼spel琛ㄨ揪寮忓畾涔夎〃',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'缁勪欢鍚嶇О',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'component_name'
+GO
+
+-- method_name 瀛楁娉ㄩ噴
+ 'MS_Description', N'鏂规硶鍚�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'method_name'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍙傛暟',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'method_params'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'棰勮spel琛ㄨ揪寮�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'view_spel'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'澶囨敞',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'remark'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'status'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'create_dept'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_spel',
+ 'COLUMN', N'update_time'
+GO
+
+INSERT flow_spel VALUES (1, N'spelRuleComponent', N'selectDeptLeaderById', N'initiatorDeptId', N'#{@spelRuleComponent.selectDeptLeaderById(#initiatorDeptId)}', N'鏍规嵁閮ㄩ棬id鑾峰彇閮ㄩ棬璐熻矗浜�', N'0', N'0', 103, 1, GETDATE(), 1, GETDATE());
+GO
+INSERT flow_spel VALUES (2, NULL, NULL, N'initiator', N'${initiator}', N'娴佺▼鍙戣捣浜�', N'0', N'0', 103, 1, GETDATE(), 1, GETDATE());
+GO
+
+INSERT sys_menu VALUES (11801, N'娴佺▼琛ㄨ揪寮�', N'11616', 2, N'spel', N'workflow/spel/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:spel:list', N'input', 103, 1, GETDATE(), 1, GETDATE(), N'娴佺▼杈惧紡瀹氫箟鑿滃崟');
+GO
+INSERT sys_menu VALUES (11802, N'娴佺▼spel琛ㄨ揪寮忓畾涔夋煡璇�', N'11801', 1, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11803, N'娴佺▼spel琛ㄨ揪寮忓畾涔夋柊澧�', N'11801', 2, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:add', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11804, N'娴佺▼spel琛ㄨ揪寮忓畾涔変慨鏀�', N'11801', 3, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11805, N'娴佺▼spel琛ㄨ揪寮忓畾涔夊垹闄�', N'11801', 4, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (11806, N'娴佺▼spel琛ㄨ揪寮忓畾涔夊鍑�', N'11801', 5, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:export', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+GO
+
+ALTER TABLE flow_definition ADD model_value VARCHAR(40) NOT NULL CONSTRAINT DF_flow_definition_model_value DEFAULT 'CLASSICS';
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'璁捐鍣ㄦā鍨嬶紙CLASSICS缁忓吀妯″瀷 MIMIC浠块拤閽夋ā鍨嬶級',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'model_value'
+GO
+UPDATE flow_skip SET skip_condition = REPLACE(skip_condition, 'notNike', 'notLike');
+GO
+ALTER TABLE flow_his_task ALTER COLUMN collaborator VARCHAR(500) NULL;
+GO
+IF ((SELECT COUNT(*) FROM ::fn_listextendedproperty('MS_Description',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'collaborator')) > 0)
+ EXEC sp_updateextendedproperty
+'MS_Description', N'鍗忎綔浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'collaborator'
+ELSE
+ EXEC sp_addextendedproperty
+'MS_Description', N'鍗忎綔浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'collaborator'
+GO
+
+CREATE TABLE flow_instance_biz_ext (
+ id BIGINT NOT NULL,
+ tenant_id VARCHAR(20) DEFAULT ('000000'),
+ create_dept BIGINT,
+ create_by BIGINT,
+ create_time DATETIME,
+ update_by BIGINT,
+ update_time DATETIME,
+ business_code VARCHAR(255),
+ business_title VARCHAR(1000),
+ del_flag CHAR(1) DEFAULT ('0'),
+ instance_id BIGINT,
+ business_id VARCHAR(255),
+ CONSTRAINT PK__fi_biz_ext__D54EE9B4AE98B9C1 PRIMARY KEY CLUSTERED (id)
+ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+ ON [PRIMARY]
+);
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'娴佺▼瀹炰緥涓氬姟鎵╁睍琛�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓婚敭id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'绉熸埛缂栧彿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'tenant_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓閮ㄩ棬',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'create_dept'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鑰�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒涘缓鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'create_time'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鑰�',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'update_by'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鏇存柊鏃堕棿',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'update_time'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'鍒犻櫎鏍囧織',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'del_flag'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓氬姟缂栫爜',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'business_code'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓氬姟鏍囬',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'business_title'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'娴佺▼瀹炰緥Id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'instance_id'
+GO
+
+EXEC sp_addextendedproperty
+ 'MS_Description', N'涓氬姟Id',
+ 'SCHEMA', N'dbo',
+ 'TABLE', N'flow_instance_biz_ext',
+ 'COLUMN', N'business_id'
+GO
+
+ALTER TABLE test_leave ADD apply_code nvarchar(50) NOT NULL;
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'鐢宠缂栧彿',
+'SCHEMA', N'dbo',
+'TABLE', N'test_leave',
+'COLUMN', N'apply_code'
+GO
+
+update sys_menu set remark = N'/tool/gen' where menu_id = 116;
+GO
+update sys_menu set remark = N'/system/role' where menu_id = 130;
+GO
+update sys_menu set remark = N'/system/user' where menu_id = 131;
+GO
+update sys_menu set remark = N'/system/dict' where menu_id = 132;
+GO
+update sys_menu set remark = N'/system/oss' where menu_id = 133;
+GO
+update sys_menu set remark = N'/workflow/processDefinition' where menu_id = 11700;
+GO
diff --git a/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.5.0-5.5.1.sql b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.5.0-5.5.1.sql
new file mode 100755
index 0000000..8388f33
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.5.0-5.5.1.sql
@@ -0,0 +1,99 @@
+ALTER TABLE flow_definition ADD create_by nvarchar(64) NOT NULL CONSTRAINT DF_flow_definition_create_by DEFAULT '';
+GO
+
+ALTER TABLE flow_definition ADD update_by nvarchar(64) NOT NULL CONSTRAINT DF_flow_definition_update_by DEFAULT '';
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_definition',
+'COLUMN', N'update_by'
+GO
+
+ALTER TABLE flow_node ADD create_by nvarchar(64) NOT NULL CONSTRAINT DF_flow_node_create_by DEFAULT '';
+GO
+
+ALTER TABLE flow_node ADD update_by nvarchar(64) NOT NULL CONSTRAINT DF_flow_node_update_by DEFAULT '';
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'update_by'
+GO
+
+ALTER TABLE flow_skip ADD create_by nvarchar(64) NOT NULL CONSTRAINT DF_flow_skip_create_by DEFAULT '';
+GO
+
+ALTER TABLE flow_skip ADD update_by nvarchar(64) NOT NULL CONSTRAINT DF_flow_skip_update_by DEFAULT '';
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_skip',
+'COLUMN', N'update_by'
+GO
+
+ALTER TABLE flow_instance ADD update_by nvarchar(64) NOT NULL CONSTRAINT DF_flow_instance_update_by DEFAULT '';
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_instance',
+'COLUMN', N'update_by'
+GO
+
+ALTER TABLE flow_task ADD create_by nvarchar(64) NOT NULL CONSTRAINT DF_flow_task_create_by DEFAULT '';
+GO
+
+ALTER TABLE flow_task ADD update_by nvarchar(64) NOT NULL CONSTRAINT DF_flow_task_update_by DEFAULT '';
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒涘缓浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'create_by'
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_task',
+'COLUMN', N'update_by'
+GO
+
+ALTER TABLE flow_user ADD update_by nvarchar(64) NOT NULL CONSTRAINT DF_flow_user_update_by DEFAULT '';
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'鏇存柊浜�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_user',
+'COLUMN', N'update_by'
+GO
diff --git a/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.5.1-5.5.2.sql b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.5.1-5.5.2.sql
new file mode 100755
index 0000000..716abb5
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/sqlserver/update_5.5.1-5.5.2.sql
@@ -0,0 +1,22 @@
+ALTER TABLE flow_node ALTER COLUMN node_ratio nvarchar(200) NULL;
+GO
+IF ((SELECT COUNT(*) FROM ::fn_listextendedproperty('MS_Description',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'collaborator')) > 0)
+ EXEC sp_updateextendedproperty
+'MS_Description', N'娴佺▼绛剧讲姣斾緥鍊�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'node_ratio'
+ELSE
+ EXEC sp_addextendedproperty
+'MS_Description', N'娴佺▼绛剧讲姣斾緥鍊�',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'node_ratio'
+GO
+ALTER TABLE flow_node DROP COLUMN handler_type;
+GO
+ALTER TABLE flow_node DROP COLUMN handler_path;
+GO
diff --git a/RuoYi-Vue-Plus/script/sql/update/update_5.0-5.1.sql b/RuoYi-Vue-Plus/script/sql/update/update_5.0-5.1.sql
new file mode 100755
index 0000000..871bda3
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/update_5.0-5.1.sql
@@ -0,0 +1,101 @@
+ALTER TABLE gen_table ADD COLUMN data_name varchar(200) NULL DEFAULT '' COMMENT '鏁版嵁婧愬悕绉�' AFTER table_id;
+
+UPDATE sys_menu SET path = 'powerjob', component = 'monitor/powerjob/index', perms = 'monitor:powerjob:list', remark = 'powerjob鎺у埗鍙拌彍鍗�' WHERE menu_id = 120;
+
+-- ----------------------------
+-- 绗笁鏂瑰钩鍙版巿鏉冭〃
+-- ----------------------------
+drop table if exists sys_social;
+create table sys_social
+(
+ id bigint not null comment '涓婚敭',
+ user_id bigint not null comment '鐢ㄦ埛ID',
+ tenant_id varchar(20) default null comment '绉熸埛id',
+ auth_id varchar(255) not null comment '骞冲彴+骞冲彴鍞竴id',
+ source varchar(255) not null comment '鐢ㄦ埛鏉ユ簮',
+ open_id varchar(255) default null comment '骞冲彴缂栧彿鍞竴id',
+ user_name varchar(30) not null comment '鐧诲綍璐﹀彿',
+ nick_name varchar(30) default '' comment '鐢ㄦ埛鏄电О',
+ email varchar(255) default '' comment '鐢ㄦ埛閭',
+ avatar varchar(500) default '' comment '澶村儚鍦板潃',
+ access_token varchar(255) not null comment '鐢ㄦ埛鐨勬巿鏉冧护鐗�',
+ expire_in int default null comment '鐢ㄦ埛鐨勬巿鏉冧护鐗岀殑鏈夋晥鏈燂紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ refresh_token varchar(255) default null comment '鍒锋柊浠ょ墝锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�',
+ access_code varchar(255) default null comment '骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ union_id varchar(255) default null comment '鐢ㄦ埛鐨� unionid',
+ scope varchar(255) default null comment '鎺堜簣鐨勬潈闄愶紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ token_type varchar(255) default null comment '涓埆骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ id_token varchar(255) default null comment 'id token锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�',
+ mac_algorithm varchar(255) default null comment '灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ mac_key varchar(255) default null comment '灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ code varchar(255) default null comment '鐢ㄦ埛鐨勬巿鏉僣ode锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�',
+ oauth_token varchar(255) default null comment 'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ oauth_token_secret varchar(255) default null comment 'Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁',
+ create_dept bigint(20) comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) comment '鍒涘缓鑰�',
+ create_time datetime comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) comment '鏇存柊鑰�',
+ update_time datetime comment '鏇存柊鏃堕棿',
+ del_flag char(1) default '0' comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�',
+ PRIMARY KEY (id)
+) engine=innodb comment = '绀句細鍖栧叧绯昏〃';
+
+
+-- ----------------------------
+-- 绯荤粺鎺堟潈琛�
+-- ----------------------------
+drop table if exists sys_client;
+create table sys_client (
+ id bigint(20) not null comment 'id',
+ client_id varchar(64) default null comment '瀹㈡埛绔痠d',
+ client_key varchar(32) default null comment '瀹㈡埛绔痥ey',
+ client_secret varchar(255) default null comment '瀹㈡埛绔閽�',
+ grant_type varchar(255) default null comment '鎺堟潈绫诲瀷',
+ device_type varchar(32) default null comment '璁惧绫诲瀷',
+ active_timeout int(11) default 1800 comment 'token娲昏穬瓒呮椂鏃堕棿',
+ timeout int(11) default 604800 comment 'token鍥哄畾瓒呮椂',
+ status char(1) default '0' comment '鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ del_flag char(1) default '0' comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�',
+ create_dept bigint(20) default null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) default null comment '鍒涘缓鑰�',
+ create_time datetime default null comment '鍒涘缓鏃堕棿',
+ update_by bigint(20) default null comment '鏇存柊鑰�',
+ update_time datetime default null comment '鏇存柊鏃堕棿',
+ primary key (id)
+) engine=innodb comment='绯荤粺鎺堟潈琛�';
+
+insert into sys_client values (1, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 103, 1, sysdate(), 1, sysdate());
+insert into sys_client values (2, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'android', 1800, 604800, 0, 0, 103, 1, sysdate(), 1, sysdate());
+
+insert into sys_dict_type values(11, '000000', '鎺堟潈绫诲瀷', 'sys_grant_type', 103, 1, sysdate(), null, null, '璁よ瘉鎺堟潈绫诲瀷');
+insert into sys_dict_type values(12, '000000', '璁惧绫诲瀷', 'sys_device_type', 103, 1, sysdate(), null, null, '瀹㈡埛绔澶囩被鍨�');
+
+insert into sys_dict_data values(30, '000000', 0, '瀵嗙爜璁よ瘉', 'password', 'sys_grant_type', '', 'default', 'N', 103, 1, sysdate(), null, null, '瀵嗙爜璁よ瘉');
+insert into sys_dict_data values(31, '000000', 0, '鐭俊璁よ瘉', 'sms', 'sys_grant_type', '', 'default', 'N', 103, 1, sysdate(), null, null, '鐭俊璁よ瘉');
+insert into sys_dict_data values(32, '000000', 0, '閭欢璁よ瘉', 'email', 'sys_grant_type', '', 'default', 'N', 103, 1, sysdate(), null, null, '閭欢璁よ瘉');
+insert into sys_dict_data values(33, '000000', 0, '灏忕▼搴忚璇�', 'xcx', 'sys_grant_type', '', 'default', 'N', 103, 1, sysdate(), null, null, '灏忕▼搴忚璇�');
+insert into sys_dict_data values(34, '000000', 0, '涓夋柟鐧诲綍璁よ瘉', 'social', 'sys_grant_type', '', 'default', 'N', 103, 1, sysdate(), null, null, '涓夋柟鐧诲綍璁よ瘉');
+insert into sys_dict_data values(35, '000000', 0, 'PC', 'pc', 'sys_device_type', '', 'default', 'N', 103, 1, sysdate(), null, null, 'PC');
+insert into sys_dict_data values(36, '000000', 0, '瀹夊崜', 'android', 'sys_device_type', '', 'default', 'N', 103, 1, sysdate(), null, null, '瀹夊崜');
+insert into sys_dict_data values(37, '000000', 0, 'iOS', 'ios', 'sys_device_type', '', 'default', 'N', 103, 1, sysdate(), null, null, 'iOS');
+insert into sys_dict_data values(38, '000000', 0, '灏忕▼搴�', 'xcx', 'sys_device_type', '', 'default', 'N', 103, 1, sysdate(), null, null, '灏忕▼搴�');
+
+-- 浜岀骇鑿滃崟
+insert into sys_menu values('123', '瀹㈡埛绔鐞�', '1', '11', 'client', 'system/client/index', '', 1, 0, 'C', '0', '0', 'system:client:list', 'international', 103, 1, sysdate(), null, null, '瀹㈡埛绔鐞嗚彍鍗�');
+-- 瀹㈡埛绔鐞嗘寜閽�
+insert into sys_menu values('1061', '瀹㈡埛绔鐞嗘煡璇�', '123', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1062', '瀹㈡埛绔鐞嗘柊澧�', '123', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1063', '瀹㈡埛绔鐞嗕慨鏀�', '123', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1064', '瀹㈡埛绔鐞嗗垹闄�', '123', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:remove', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1065', '瀹㈡埛绔鐞嗗鍑�', '123', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:client:export', '#', 103, 1, sysdate(), null, null, '');
+
+-- 瑙掕壊鑿滃崟鏉冮檺
+insert into sys_role_menu values ('2', '1061');
+insert into sys_role_menu values ('2', '1062');
+insert into sys_role_menu values ('2', '1063');
+insert into sys_role_menu values ('2', '1064');
+insert into sys_role_menu values ('2', '1065');
+
+
+update sys_dept set leader = null;
+alter table sys_dept modify column leader bigint null default null comment '璐熻矗浜�' after order_num;
diff --git a/RuoYi-Vue-Plus/script/sql/update/update_5.1.0-5.1.1.sql b/RuoYi-Vue-Plus/script/sql/update/update_5.1.0-5.1.1.sql
new file mode 100755
index 0000000..1dea49b
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/update_5.1.0-5.1.1.sql
@@ -0,0 +1,3 @@
+ALTER TABLE sys_logininfor
+ ADD COLUMN client_key VARCHAR(32) NULL DEFAULT NULL COMMENT '瀹㈡埛绔�' AFTER `user_name`,
+ ADD COLUMN device_type VARCHAR(32) NULL DEFAULT NULL COMMENT '璁惧绫诲瀷' AFTER `client_key`;
diff --git a/RuoYi-Vue-Plus/script/sql/update/update_5.1.1-5.1.2.sql b/RuoYi-Vue-Plus/script/sql/update/update_5.1.1-5.1.2.sql
new file mode 100755
index 0000000..314743f
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/update_5.1.1-5.1.2.sql
@@ -0,0 +1,5 @@
+delete from sys_menu where menu_id in (1604, 1605);
+insert into sys_menu values('1620', '閰嶇疆鍒楄〃', '118', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:list', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1621', '閰嶇疆娣诲姞', '118', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:add', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1622', '閰嶇疆缂栬緫', '118', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1623', '閰嶇疆鍒犻櫎', '118', '6', '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:remove', '#', 103, 1, sysdate(), null, null, '');
diff --git a/RuoYi-Vue-Plus/script/sql/update/update_5.1.2-5.2.0.sql b/RuoYi-Vue-Plus/script/sql/update/update_5.1.2-5.2.0.sql
new file mode 100755
index 0000000..33384e7
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/update_5.1.2-5.2.0.sql
@@ -0,0 +1,5 @@
+ALTER TABLE sys_dept ADD dept_category VARCHAR(100) DEFAULT NULL COMMENT '閮ㄩ棬绫诲埆缂栫爜';
+ALTER TABLE sys_post ADD dept_id BIGINT(20) NOT NULL COMMENT '閮ㄩ棬id', ADD post_category VARCHAR(100) DEFAULT NULL COMMENT '宀椾綅绫诲埆缂栫爜';
+UPDATE sys_post SET dept_id = 100;
+UPDATE sys_post SET dept_id = 103 where post_id = 1;
+UPDATE sys_menu SET menu_name = 'SnailJob鎺у埗鍙�', path = 'snailjob', component = 'monitor/snailjob/index', perms = 'monitor:snailjob:list', remark = 'SnailJob鎺у埗鍙拌彍鍗�' WHERE menu_id = 120;
diff --git a/RuoYi-Vue-Plus/script/sql/update/update_5.3.0-5.3.1.sql b/RuoYi-Vue-Plus/script/sql/update/update_5.3.0-5.3.1.sql
new file mode 100755
index 0000000..f45cc40
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/update_5.3.0-5.3.1.sql
@@ -0,0 +1,7 @@
+ALTER TABLE `flow_node` DROP COLUMN `skip_any_node`;
+ALTER TABLE `flow_node`
+ ADD COLUMN `ext` text NULL COMMENT '鎵╁睍灞炴��' AFTER `update_time`;
+ALTER TABLE `flow_user` ADD INDEX `user_associated`(`associated`) USING BTREE
+
+ALTER TABLE `sys_oss`
+ ADD COLUMN `ext1` text NULL COMMENT '鎵╁睍灞炴��' AFTER `url`;
diff --git a/RuoYi-Vue-Plus/script/sql/update/update_5.3.1-5.4.0.sql b/RuoYi-Vue-Plus/script/sql/update/update_5.3.1-5.4.0.sql
new file mode 100755
index 0000000..058f6fc
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/update_5.3.1-5.4.0.sql
@@ -0,0 +1,21 @@
+ALTER TABLE `flow_task`
+ ADD COLUMN `flow_status` varchar(20) NOT NULL COMMENT '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�' AFTER `node_type`;
+
+ALTER TABLE `flow_instance`
+ MODIFY COLUMN `flow_status` varchar(20) NOT NULL COMMENT '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�' AFTER `variable`;
+
+ALTER TABLE `flow_his_task`
+ MODIFY COLUMN `flow_status` varchar(20) NOT NULL COMMENT '娴佺▼鐘舵�侊紙0寰呮彁浜� 1瀹℃壒涓� 2瀹℃壒閫氳繃 4缁堟 5浣滃簾 6鎾ら攢 8宸插畬鎴� 9宸查��鍥� 10澶辨晥 11鎷垮洖锛�' AFTER `skip_type`;
+
+ALTER TABLE `sys_social`
+ MODIFY COLUMN `access_token` varchar(2000) NOT NULL COMMENT '鐢ㄦ埛鐨勬巿鏉冧护鐗�' AFTER `avatar`;
+ALTER TABLE `sys_social`
+ MODIFY COLUMN `refresh_token` varchar(2000) DEFAULT NULL COMMENT '鍒锋柊浠ょ墝锛岄儴鍒嗗钩鍙板彲鑳芥病鏈�' AFTER `expire_in`;
+
+insert into sys_menu values('116', '淇敼鐢熸垚閰嶇疆', '3', '2', 'gen-edit/index/:tableId', 'tool/gen/editTable', '', 1, 1, 'C', '1', '0', 'tool:gen:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('130', '鍒嗛厤鐢ㄦ埛', '1', '2', 'role-auth/user/:roleId', 'system/role/authUser', '', 1, 1, 'C', '1', '0', 'system:role:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('131', '鍒嗛厤瑙掕壊', '1', '1', 'user-auth/role/:userId', 'system/user/authRole', '', 1, 1, 'C', '1', '0', 'system:user:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('132', '瀛楀吀鏁版嵁', '1', '6', 'dict-data/index/:dictId', 'system/dict/data', '', 1, 1, 'C', '1', '0', 'system:dict:list', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('133', '鏂囦欢閰嶇疆绠$悊', '1', '10', 'oss-config/index', 'system/oss/config', '', 1, 1, 'C', '1', '0', 'system:ossConfig:list', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('11700', '娴佺▼璁捐', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', 1, 1, 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('11701', '璇峰亣鐢宠', '11616', '6', 'leaveEdit/index', 'workflow/leave/leaveEdit', '', 1, 1, 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), null, null, '');
diff --git a/RuoYi-Vue-Plus/script/sql/update/update_5.4.1-5.5.0.sql b/RuoYi-Vue-Plus/script/sql/update/update_5.4.1-5.5.0.sql
new file mode 100755
index 0000000..a0d29fe
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/update_5.4.1-5.5.0.sql
@@ -0,0 +1,67 @@
+-- ----------------------------
+-- 娴佺▼spel琛ㄨ揪寮忓畾涔夎〃
+-- ----------------------------
+CREATE TABLE flow_spel (
+ id bigint(20) NOT NULL COMMENT '涓婚敭id',
+ component_name varchar(255) DEFAULT NULL COMMENT '缁勪欢鍚嶇О',
+ method_name varchar(255) DEFAULT NULL COMMENT '鏂规硶鍚�',
+ method_params varchar(255) DEFAULT NULL COMMENT '鍙傛暟',
+ view_spel varchar(255) DEFAULT NULL COMMENT '棰勮spel琛ㄨ揪寮�',
+ remark varchar(255) DEFAULT NULL COMMENT '澶囨敞',
+ status char(1) DEFAULT '0' COMMENT '鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ del_flag char(1) DEFAULT '0' COMMENT '鍒犻櫎鏍囧織',
+ create_dept bigint(20) DEFAULT NULL COMMENT '鍒涘缓閮ㄩ棬',
+ create_by bigint(20) DEFAULT NULL COMMENT '鍒涘缓鑰�',
+ create_time datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+ update_by bigint(20) DEFAULT NULL COMMENT '鏇存柊鑰�',
+ update_time datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+ PRIMARY KEY (id)
+) ENGINE = InnoDB COMMENT='娴佺▼spel琛ㄨ揪寮忓畾涔夎〃';
+
+INSERT INTO flow_spel VALUES (1, 'spelRuleComponent', 'selectDeptLeaderById', 'initiatorDeptId', '#{@spelRuleComponent.selectDeptLeaderById(#initiatorDeptId)}', '鏍规嵁閮ㄩ棬id鑾峰彇閮ㄩ棬璐熻矗浜�', '0', '0', 103, 1, sysdate(), 1, sysdate());
+INSERT INTO flow_spel VALUES (2, NULL, NULL, 'initiator', '${initiator}', '娴佺▼鍙戣捣浜�', '0', '0', 103, 1, sysdate(), 1, sysdate());
+
+INSERT INTO sys_menu VALUES ('11801', '娴佺▼琛ㄨ揪寮�', '11616', '2', 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, sysdate(), 1, sysdate(), '娴佺▼杈惧紡瀹氫箟鑿滃崟');
+INSERT INTO sys_menu VALUES ('11802', '娴佺▼spel琛ㄨ揪寮忓畾涔夋煡璇�', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11803', '娴佺▼spel琛ㄨ揪寮忓畾涔夋柊澧�', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11804', '娴佺▼spel琛ㄨ揪寮忓畾涔変慨鏀�', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11805', '娴佺▼spel琛ㄨ揪寮忓畾涔夊垹闄�', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11806', '娴佺▼spel琛ㄨ揪寮忓畾涔夊鍑�', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, sysdate(), NULL, NULL, '');
+
+ALTER TABLE `flow_definition`
+ ADD COLUMN `model_value` varchar(40) NOT NULL DEFAULT 'CLASSICS' COMMENT '璁捐鍣ㄦā寮忥紙CLASSICS缁忓吀妯″紡 MIMIC浠块拤閽夋ā寮忥級' AFTER `flow_name`;
+
+update flow_skip set skip_condition = REPLACE(skip_condition,'notNike','notLike');
+
+ALTER TABLE `flow_his_task`
+ MODIFY COLUMN `collaborator` varchar(500) NULL DEFAULT NULL COMMENT '鍗忎綔浜�' AFTER `cooperate_type`;
+
+-- ----------------------------
+-- 娴佺▼瀹炰緥涓氬姟鎵╁睍琛�
+-- ----------------------------
+
+create table flow_instance_biz_ext (
+ id bigint not null comment '涓婚敭id',
+ tenant_id varchar(20) default '000000' null comment '绉熸埛缂栧彿',
+ create_dept bigint null comment '鍒涘缓閮ㄩ棬',
+ create_by bigint null comment '鍒涘缓鑰�',
+ create_time datetime null comment '鍒涘缓鏃堕棿',
+ update_by bigint null comment '鏇存柊鑰�',
+ update_time datetime null comment '鏇存柊鏃堕棿',
+ business_code varchar(255) null comment '涓氬姟缂栫爜',
+ business_title varchar(1000) null comment '涓氬姟鏍囬',
+ del_flag char default '0' null comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛�',
+ instance_id bigint null comment '娴佺▼瀹炰緥Id',
+ business_id varchar(255) null comment '涓氬姟Id',
+ PRIMARY KEY (id)
+) ENGINE = InnoDB COMMENT '娴佺▼瀹炰緥涓氬姟鎵╁睍琛�';
+
+ALTER TABLE `test_leave`
+ ADD COLUMN `apply_code` varchar(50) NOT NULL COMMENT '鐢宠缂栧彿' AFTER `tenant_id`;
+
+update sys_menu set remark = '/tool/gen' where menu_id = 116;
+update sys_menu set remark = '/system/role' where menu_id = 130;
+update sys_menu set remark = '/system/user' where menu_id = 131;
+update sys_menu set remark = '/system/dict' where menu_id = 132;
+update sys_menu set remark = '/system/oss' where menu_id = 133;
+update sys_menu set remark = '/workflow/processDefinition' where menu_id = 11700;
diff --git a/RuoYi-Vue-Plus/script/sql/update/update_5.5.0-5.5.1.sql b/RuoYi-Vue-Plus/script/sql/update/update_5.5.0-5.5.1.sql
new file mode 100755
index 0000000..17a60af
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/update_5.5.0-5.5.1.sql
@@ -0,0 +1,21 @@
+ALTER TABLE `flow_definition`
+ ADD COLUMN `create_by` varchar(64) NULL DEFAULT NULL COMMENT '鍒涘缓浜�' AFTER `create_time`,
+ ADD COLUMN `update_by` varchar(64) NULL DEFAULT NULL COMMENT '鏇存柊浜�' AFTER `update_time`;
+
+ALTER TABLE `flow_node`
+ ADD COLUMN `create_by` varchar(64) NULL DEFAULT NULL COMMENT '鍒涘缓浜�' AFTER `create_time`,
+ ADD COLUMN `update_by` varchar(64) NULL DEFAULT NULL COMMENT '鏇存柊浜�' AFTER `update_time`;
+
+ALTER TABLE `flow_skip`
+ ADD COLUMN `create_by` varchar(64) NULL DEFAULT NULL COMMENT '鍒涘缓浜�' AFTER `create_time`,
+ ADD COLUMN `update_by` varchar(64) NULL DEFAULT NULL COMMENT '鏇存柊浜�' AFTER `update_time`;
+
+ALTER TABLE `flow_instance`
+ ADD COLUMN `update_by` varchar(64) NULL DEFAULT NULL COMMENT '鏇存柊浜�' AFTER `update_time`;
+
+ALTER TABLE `flow_task`
+ ADD COLUMN `create_by` varchar(64) NULL DEFAULT NULL COMMENT '鍒涘缓浜�' AFTER `create_time`,
+ ADD COLUMN `update_by` varchar(64) NULL DEFAULT NULL COMMENT '鏇存柊浜�' AFTER `update_time`;
+
+ALTER TABLE `flow_user`
+ ADD COLUMN `update_by` varchar(64) NULL DEFAULT NULL COMMENT '鏇存柊浜�' AFTER `update_time`;
diff --git a/RuoYi-Vue-Plus/script/sql/update/update_5.5.1-5.5.2.sql b/RuoYi-Vue-Plus/script/sql/update/update_5.5.1-5.5.2.sql
new file mode 100755
index 0000000..d1e4cef
--- /dev/null
+++ b/RuoYi-Vue-Plus/script/sql/update/update_5.5.1-5.5.2.sql
@@ -0,0 +1,3 @@
+ALTER TABLE flow_node MODIFY COLUMN node_ratio varchar(200) NULL COMMENT '娴佺▼绛剧讲姣斾緥鍊�';
+ALTER TABLE flow_node DROP COLUMN handler_type;
+ALTER TABLE flow_node DROP COLUMN handler_path;
diff --git "a/RuoYi-Vue-Plus/\345\274\200\345\217\221\346\227\245\345\277\227.md" "b/RuoYi-Vue-Plus/\345\274\200\345\217\221\346\227\245\345\277\227.md"
new file mode 100644
index 0000000..1de3019
--- /dev/null
+++ "b/RuoYi-Vue-Plus/\345\274\200\345\217\221\346\227\245\345\277\227.md"
@@ -0,0 +1,119 @@
+### 娉ㄦ剰浜嬮」
+- 1.postgrest鏁版嵁搴撴椂搴忔暟鎹〃瀛楁绫诲瀷鎸夌収骞夸赴宸插彂甯冧负鍑�(琛ㄥ悕甯time鐨勮〃)锛屾湰鍦版暟鎹簱琛ㄩ儴鍒嗗瓧娈电被鍨嬩笉瀵�
+
+
+### 寮�鍙戞棩蹇�
+
+#### 20260226
+- 1.鏂板鎴愬搧妫� 妫�楠屾壒娆� 绠$悊椤甸潰
+- 2.寤鸿〃sql
+```
+-- 鍒涘缓妫�楠屾壒娆¤〃
+CREATE TABLE QM_BATCH (
+ ID VARCHAR(50) NOT NULL,
+ BATCH_CODE VARCHAR(50),
+ BATCH_NAME VARCHAR(50),
+ TYP VARCHAR(20),
+ EQP_CODE VARCHAR(50),
+ MAT_CODE VARCHAR(50),
+ JUDGE_CODE VARCHAR(50),
+ BATCH_DATE DATE,
+ ISFLAG VARCHAR(2),
+ ENABLED VARCHAR(2),
+ TOTAL_NUM NUMERIC(22),
+ RESULTS VARCHAR(50),
+ APPROVER VARCHAR(50),
+ AUDITOR VARCHAR(50),
+ CREATER VARCHAR(50),
+ TAB_DATE DATE,
+ VER_NAME VARCHAR(50),
+ VER_CODE VARCHAR(50),
+ ARCH_DATE VARCHAR(50),
+ FLAG VARCHAR(20),
+ TO_MES_DATE DATE,
+ FROM_MES_DATE DATE,
+ DELETED NUMERIC(22),
+ BATCH_DES VARCHAR(200),
+ CATEGORY VARCHAR(2),
+ MAKENO VARCHAR(60),
+ SHIFTEQPNO VARCHAR(32),
+ BOXNO VARCHAR(60),
+ PID VARCHAR(50),
+ REVIEWER VARCHAR(50),
+ RVCOUNT NUMERIC(22),
+ STATE VARCHAR(2),
+ REVIEW_TIME DATE,
+ AUDIT_TIME DATE,
+ SPEC VARCHAR(50),
+ APPROVE_TIME DATE,
+ UNIT VARCHAR(50),
+ ARRIVAL_TIME DATE,
+ STORAGE_PLACE VARCHAR(50),
+ CHECKER VARCHAR(20),
+ RECEIVE_TIME DATE,
+ INSP_TIME DATE,
+ STORER VARCHAR(50),
+ ISVERIFY VARCHAR(10),
+ ISCHK VARCHAR(10),
+ BAK1 VARCHAR(100),
+ BAK2 VARCHAR(100),
+ CONSTRAINT PK_QM_BATCH PRIMARY KEY (ID)
+);
+
+-- 娣诲姞娉ㄩ噴锛堝彲閫夛級
+COMMENT ON TABLE QM_BATCH IS '妫�楠屾壒娆¤〃';
+
+COMMENT ON COLUMN QM_BATCH.ID IS '缂栫爜';
+COMMENT ON COLUMN QM_BATCH.BATCH_CODE IS '鎵规浠g爜';
+COMMENT ON COLUMN QM_BATCH.BATCH_NAME IS '鎵规鍚嶇О';
+COMMENT ON COLUMN QM_BATCH.TYP IS 'A-鍒朵笣 B-鎴愬瀷 C-鍗峰寘 D-灏佺 E-绯栭鏂�';
+COMMENT ON COLUMN QM_BATCH.EQP_CODE IS '鏈哄彴浠g爜';
+COMMENT ON COLUMN QM_BATCH.MAT_CODE IS '鐗屽彿';
+COMMENT ON COLUMN QM_BATCH.JUDGE_CODE IS '鍒ゅ畾渚濇嵁浠g爜';
+COMMENT ON COLUMN QM_BATCH.BATCH_DATE IS '鎵规鐢熸垚鏃ユ湡';
+COMMENT ON COLUMN QM_BATCH.ISFLAG IS '浣跨敤鏍囧織';
+COMMENT ON COLUMN QM_BATCH.ENABLED IS '鍚敤鏍囧織';
+COMMENT ON COLUMN QM_BATCH.TOTAL_NUM IS '鍒拌揣鎬婚噺';
+COMMENT ON COLUMN QM_BATCH.RESULTS IS '缁煎悎鍒ゅ畾';
+COMMENT ON COLUMN QM_BATCH.APPROVER IS '鎵瑰噯浜�';
+COMMENT ON COLUMN QM_BATCH.AUDITOR IS '瀹℃牳浜�';
+COMMENT ON COLUMN QM_BATCH.CREATER IS '鍒涘缓浜�';
+COMMENT ON COLUMN QM_BATCH.TAB_DATE IS '鍒惰〃鏃ユ湡';
+COMMENT ON COLUMN QM_BATCH.VER_NAME IS '鐗堟湰鍚嶇О';
+COMMENT ON COLUMN QM_BATCH.VER_CODE IS '鐗堟湰缂栧彿';
+COMMENT ON COLUMN QM_BATCH.ARCH_DATE IS '淇濆瓨鏈�';
+COMMENT ON COLUMN QM_BATCH.FLAG IS '0-鏈笂浼爉es,1-宸蹭笂浼�, 3-浠嶮ES涓嬭浇';
+COMMENT ON COLUMN QM_BATCH.TO_MES_DATE IS '涓婁紶MES鏃堕棿';
+COMMENT ON COLUMN QM_BATCH.FROM_MES_DATE IS '浠嶮ES鏃堕棿涓嬭浇';
+COMMENT ON COLUMN QM_BATCH.DELETED IS '鍒犻櫎鏍囧織';
+COMMENT ON COLUMN QM_BATCH.BATCH_DES IS '鎵规鎻忚堪';
+COMMENT ON COLUMN QM_BATCH.CATEGORY IS '绫诲埆 0锛氭垚鍝� 1锛氳緟鏉�';
+COMMENT ON COLUMN QM_BATCH.MAKENO IS '鍗峰埗宸ュ彿';
+COMMENT ON COLUMN QM_BATCH.SHIFTEQPNO IS '鐝鏈哄彿';
+COMMENT ON COLUMN QM_BATCH.BOXNO IS '瑁呯鍙�';
+COMMENT ON COLUMN QM_BATCH.PID IS '鐖舵壒娆″彿';
+COMMENT ON COLUMN QM_BATCH.REVIEWER IS '澶嶆牳浜�';
+COMMENT ON COLUMN QM_BATCH.RVCOUNT IS '澶嶆娆℃暟';
+COMMENT ON COLUMN QM_BATCH.STATE IS '鎵规鐘舵��';
+COMMENT ON COLUMN QM_BATCH.REVIEW_TIME IS '澶嶆牳鏃ユ湡';
+COMMENT ON COLUMN QM_BATCH.AUDIT_TIME IS '瀹℃牳鏃ユ湡';
+COMMENT ON COLUMN QM_BATCH.SPEC IS '瑙勬牸';
+COMMENT ON COLUMN QM_BATCH.APPROVE_TIME IS '鎵瑰噯鏃堕棿';
+COMMENT ON COLUMN QM_BATCH.UNIT IS '鍒拌揣鍗曚綅';
+COMMENT ON COLUMN QM_BATCH.ARRIVAL_TIME IS '鍒拌揣鏃ユ湡';
+COMMENT ON COLUMN QM_BATCH.STORAGE_PLACE IS '瀛樻斁鍦扮偣';
+COMMENT ON COLUMN QM_BATCH.CHECKER IS '妫�楠屽憳';
+COMMENT ON COLUMN QM_BATCH.RECEIVE_TIME IS '鎺ュ崟鏃ユ湡';
+COMMENT ON COLUMN QM_BATCH.INSP_TIME IS '鎶ユ鏃ユ湡';
+COMMENT ON COLUMN QM_BATCH.STORER IS '浠撳簱淇濈鍛�';
+COMMENT ON COLUMN QM_BATCH.ISVERIFY IS '鏄惁楠岃瘉';
+COMMENT ON COLUMN QM_BATCH.ISCHK IS '鏄惁妫�楠�';
+COMMENT ON COLUMN QM_BATCH.BAK1 IS '澶囩敤1';
+COMMENT ON COLUMN QM_BATCH.BAK2 IS '澶囩敤2';
+
+-- 鍒涘缓绱㈠紩锛堟牴鎹父鐢ㄦ煡璇㈡潯浠跺垱寤猴級
+CREATE INDEX IDX_QM_BATCH_BATCH_CODE ON QM_BATCH(BATCH_CODE);
+CREATE INDEX IDX_QM_BATCH_BATCH_DATE ON QM_BATCH(BATCH_DATE);
+CREATE INDEX IDX_QM_BATCH_TYP ON QM_BATCH(TYP);
+CREATE INDEX IDX_QM_BATCH_MAT_CODE ON QM_BATCH(MAT_CODE);
+```
diff --git a/ruoyi-plus-soybean/.drone.yml b/ruoyi-plus-soybean/.drone.yml
new file mode 100755
index 0000000..786f8d9
--- /dev/null
+++ b/ruoyi-plus-soybean/.drone.yml
@@ -0,0 +1,73 @@
+kind: pipeline
+type: docker
+name: Build and Deploy
+
+clone:
+ depth: 10
+
+volumes:
+ - name: go_cache
+ host:
+ path: /data/drone_cache/go_cache
+
+steps:
+ - name: restore-cache
+ image: drillster/drone-volume-cache
+ volumes:
+ - name: go_cache
+ path: /cache
+ settings:
+ restore: true
+ mount:
+ - ./.npm-cache
+ - ./node_modules
+
+ - name: build
+ image: node:alpine
+ pull: if-not-exists
+ commands:
+ - export NODE_OPTIONS=--max_old_space_size=6144
+ - echo ${DRONE_BRANCH}
+ - echo ${DRONE_TAG}
+ - echo ${DRONE_COMMIT}
+ - echo ${DRONE_COMMIT:0-7}
+ - npm config set registry https://registry.npmmirror.com
+ - npm install -g pnpm
+ - pnpm config set registry https://registry.npmmirror.com
+ - pnpm i
+ - pnpm build
+
+ - name: rebuild-cache
+ image: drillster/drone-volume-cache
+ volumes:
+ - name: go_cache
+ path: /cache
+ settings:
+ rebuild: true
+ mount:
+ - ./.npm-cache
+ - ./node_modules
+
+ - name: scp files
+ image: appleboy/drone-scp
+ pull: if-not-exists
+ settings:
+ host:
+ from_secret: HOST
+ username:
+ from_secret: USERNAME
+ password:
+ from_secret: PASSWORD
+ port:
+ from_secret: PORT
+ target:
+ from_secret: TARGET_PATH
+ source: dist/*
+ overwrite: true
+ rm: true
+
+trigger:
+ branch:
+ - master
+ event:
+ - push
diff --git a/ruoyi-plus-soybean/.editorconfig b/ruoyi-plus-soybean/.editorconfig
new file mode 100755
index 0000000..0552777
--- /dev/null
+++ b/ruoyi-plus-soybean/.editorconfig
@@ -0,0 +1,11 @@
+# Editor configuration, see http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/ruoyi-plus-soybean/.env b/ruoyi-plus-soybean/.env
new file mode 100755
index 0000000..3f2f9f8
--- /dev/null
+++ b/ruoyi-plus-soybean/.env
@@ -0,0 +1,63 @@
+# the base url of the application, the default is "/"
+# if use a sub directory, it must be end with "/", like "/admin/" but not "/admin"
+VITE_BASE_URL=/
+
+VITE_APP_TITLE=骞夸赴鍗风儫鍘�
+
+VITE_APP_DESC=骞夸赴鍗风儫鍘傛暟閲囪川閲忓垎鏋愮郴缁�
+
+# the prefix of the icon name
+VITE_ICON_PREFIX=icon
+
+# the prefix of the local svg icon component, must include VITE_ICON_PREFIX
+# format {VITE_ICON_PREFIX}-{local icon name}
+VITE_ICON_LOCAL_PREFIX=icon-local
+
+# auth route mode: static 锝� dynamic
+VITE_AUTH_ROUTE_MODE=dynamic
+
+# static auth route home
+VITE_ROUTE_HOME=home
+
+# default menu icon
+VITE_MENU_ICON=mdi:menu
+
+# whether to enable http proxy when is dev mode
+VITE_HTTP_PROXY=Y
+
+# vue-router mode: hash | history | memory
+VITE_ROUTER_HISTORY_MODE=history
+
+# success code of backend service, when the code is received, the request is successful
+VITE_SERVICE_SUCCESS_CODE=200
+
+# logout codes of backend service, when the code is received, the user will be logged out and redirected to login page
+VITE_SERVICE_LOGOUT_CODES=401
+
+# modal logout codes of backend service, when the code is received, the user will be logged out by displaying a modal
+VITE_SERVICE_MODAL_LOGOUT_CODES=401
+
+# token expired codes of backend service, when the code is received, it will refresh the token and resend the request
+VITE_SERVICE_EXPIRED_TOKEN_CODES=9999,9998,3333
+
+# when the route mode is static, the defined super role
+VITE_STATIC_SUPER_ROLE=R_SUPER
+
+# sourcemap
+VITE_SOURCE_MAP=N
+
+# Used to differentiate storage across different domains
+VITE_STORAGE_PREFIX=RY_
+
+# used to control whether the program automatically detects updates
+VITE_AUTOMATICALLY_DETECT_UPDATE=Y
+
+# watermark
+VITE_WATERMARK=N
+
+# show proxy url log in terminal
+VITE_PROXY_LOG=Y
+
+# used to control whether to launch editor
+# by the way, this plugin is only available in dev mode, not in build mode
+VITE_DEVTOOLS_LAUNCH_EDITOR=code
diff --git a/ruoyi-plus-soybean/.env.dev b/ruoyi-plus-soybean/.env.dev
new file mode 100755
index 0000000..6aa4ce7
--- /dev/null
+++ b/ruoyi-plus-soybean/.env.dev
@@ -0,0 +1,26 @@
+# backend service base url, test environment
+VITE_SERVICE_BASE_URL=http://localhost:8080
+
+VITE_APP_BASE_API=/dev-api
+
+# watermark
+VITE_WATERMARK=N
+# 鏄惁寮�鍚� SSE 鍔熻兘
+VITE_APP_SSE=Y
+# 鏄惁寮�鍚� websocket 鍔熻兘
+VITE_APP_WEBSOCKET=N
+
+# app client id
+VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
+
+# 璁颁綇瀵嗙爜 AES 鍔犲瘑瀵嗛挜
+VITE_REMEMBER_ME_AES_KEY=pC4aO6cD2uU7hA0bK6iD4vE1mV8sU8xG
+
+# 鎺ュ彛鍔犲瘑鍔熻兘寮�鍏�(濡傞渶鍏抽棴 鍚庣涔熷繀椤诲搴斿叧闂�)
+VITE_APP_ENCRYPT=N
+# AES 鍔犲瘑澶存爣璇�
+VITE_HEADER_FLAG=encrypt-key
+# 鎺ュ彛鍔犲瘑浼犺緭 RSA 鍏挜涓庡悗绔В瀵嗙閽ュ搴� 濡傛洿鎹㈤渶鍓嶅悗绔竴鍚屾洿鎹�
+VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
+# 鎺ュ彛鍝嶅簲瑙e瘑 RSA 绉侀挜涓庡悗绔姞瀵嗗叕閽ュ搴� 濡傛洿鎹㈤渶鍓嶅悗绔竴鍚屾洿鎹�
+VITE_APP_RSA_PRIVATE_KEY='MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE='
diff --git a/ruoyi-plus-soybean/.env.prod b/ruoyi-plus-soybean/.env.prod
new file mode 100755
index 0000000..aa1e0f3
--- /dev/null
+++ b/ruoyi-plus-soybean/.env.prod
@@ -0,0 +1,23 @@
+VITE_APP_BASE_API=/prod-api
+
+# watermark
+VITE_WATERMARK=Y
+# 鏄惁寮�鍚� SSE 鍔熻兘
+VITE_APP_SSE=Y
+# 鏄惁寮�鍚� websocket 鍔熻兘
+VITE_APP_WEBSOCKET=N
+
+# app client id
+VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
+
+# 璁颁綇瀵嗙爜 AES 鍔犲瘑瀵嗛挜
+VITE_REMEMBER_ME_AES_KEY=pC4aO6cD2uU7hA0b6iD4vE1mV8sU8xG
+
+# 鎺ュ彛鍔犲瘑鍔熻兘寮�鍏�(濡傞渶鍏抽棴 鍚庣涔熷繀椤诲搴斿叧闂�)
+VITE_APP_ENCRYPT=N
+# AES 鍔犲瘑澶存爣璇�
+VITE_HEADER_FLAG=encrypt-key
+# 鎺ュ彛鍔犲瘑浼犺緭 RSA 鍏挜涓庡悗绔В瀵嗙閽ュ搴� 濡傛洿鎹㈤渶鍓嶅悗绔竴鍚屾洿鎹�
+VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
+# 鎺ュ彛鍝嶅簲瑙e瘑 RSA 绉侀挜涓庡悗绔姞瀵嗗叕閽ュ搴� 濡傛洿鎹㈤渶鍓嶅悗绔竴鍚屾洿鎹�
+VITE_APP_RSA_PRIVATE_KEY='MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE='
diff --git a/ruoyi-plus-soybean/.env.test b/ruoyi-plus-soybean/.env.test
new file mode 100755
index 0000000..e45ccdc
--- /dev/null
+++ b/ruoyi-plus-soybean/.env.test
@@ -0,0 +1,23 @@
+VITE_APP_BASE_API=/test-api
+
+# watermark
+VITE_WATERMARK=Y
+# 鏄惁寮�鍚� SSE 鍔熻兘
+VITE_APP_SSE=Y
+# 鏄惁寮�鍚� websocket 鍔熻兘
+VITE_APP_WEBSOCKET=N
+
+# app client id
+VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
+
+# 璁颁綇瀵嗙爜 AES 鍔犲瘑瀵嗛挜
+VITE_REMEMBER_ME_AES_KEY=pC4aO6cD2uU7hA0bK6iD4vE1mV8sU8xG
+
+# 鎺ュ彛鍔犲瘑鍔熻兘寮�鍏�(濡傞渶鍏抽棴 鍚庣涔熷繀椤诲搴斿叧闂�)
+VITE_APP_ENCRYPT=Y
+# AES 鍔犲瘑澶存爣璇�
+VITE_HEADER_FLAG=encrypt-key
+# 鎺ュ彛鍔犲瘑浼犺緭 RSA 鍏挜涓庡悗绔В瀵嗙閽ュ搴� 濡傛洿鎹㈤渶鍓嶅悗绔竴鍚屾洿鎹�
+VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
+# 鎺ュ彛鍝嶅簲瑙e瘑 RSA 绉侀挜涓庡悗绔姞瀵嗗叕閽ュ搴� 濡傛洿鎹㈤渶鍓嶅悗绔竴鍚屾洿鎹�
+VITE_APP_RSA_PRIVATE_KEY='MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE='
diff --git a/ruoyi-plus-soybean/.gitattributes b/ruoyi-plus-soybean/.gitattributes
new file mode 100755
index 0000000..9553ccb
--- /dev/null
+++ b/ruoyi-plus-soybean/.gitattributes
@@ -0,0 +1,13 @@
+"*.vue" eol=lf
+"*.js" eol=lf
+"*.ts" eol=lf
+"*.jsx" eol=lf
+"*.tsx" eol=lf
+"*.mjs" eol=lf
+"*.json" eol=lf
+"*.html" eol=lf
+"*.css" eol=lf
+"*.scss" eol=lf
+"*.md" eol=lf
+"*.yaml" eol=lf
+"*.yml" eol=lf
diff --git a/ruoyi-plus-soybean/.gitignore b/ruoyi-plus-soybean/.gitignore
new file mode 100755
index 0000000..fdd159a
--- /dev/null
+++ b/ruoyi-plus-soybean/.gitignore
@@ -0,0 +1,35 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+!.vscode/settings.json
+!.vscode/launch.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+package-lock.json
+yarn.lock
+
+.VSCodeCounter
diff --git a/ruoyi-plus-soybean/.npmrc b/ruoyi-plus-soybean/.npmrc
new file mode 100755
index 0000000..dfc2d68
--- /dev/null
+++ b/ruoyi-plus-soybean/.npmrc
@@ -0,0 +1,4 @@
+registry=https://registry.npmmirror.com/
+shamefully-hoist=true
+ignore-workspace-root-check=true
+link-workspace-packages=true
diff --git a/ruoyi-plus-soybean/CHANGELOG.md b/ruoyi-plus-soybean/CHANGELOG.md
new file mode 100755
index 0000000..abe3e14
--- /dev/null
+++ b/ruoyi-plus-soybean/CHANGELOG.md
@@ -0,0 +1,501 @@
+# 鏇存柊鏃ュ織
+
+## [v2.0.0](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v1.2.1...v2.0.0) (2025-12-25)
+
+### 馃殌 鏂板姛鑳�
+
+- **components**:
+ - 鍒楄缃柊澧炴粴鍔ㄦ潯澶勭悊 - by @m-xlsea [<samp>(6696d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/6696da52)
+ - 浼樺寲鏂囦欢涓婁紶缁勪欢鎻愮ず鍐呭 - by @m-xlsea [<samp>(7bd11)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7bd115bf)
+ - 鏂板棰勮涓婚鏀寔 - by @m-xlsea [<samp>(c1063)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/c1063e3e)
+- **docs**:
+ - 鏂板 GitCode star 寰界珷 - by @m-xlsea [<samp>(5310d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/5310d352)
+- **hooks**:
+ - 浼樺寲琛ㄦ牸鍝嶅簲鏁版嵁澶勭悊 - by @m-xlsea [<samp>(7d7f2)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7d7f28c4)
+ - 瀹屾垚琛ㄦ牸 Hooks 鏀归�� - by @m-xlsea [<samp>(46996)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4699654f)
+ - 浼樺寲鏍戝舰琛ㄦ牸 hooks 灏佽 - by @m-xlsea [<samp>(ccbb7)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ccbb72c0)
+- **project**:
+ - 浼樺寲涓氬姟浠g爜璇硶鏍煎紡 - by @m-xlsea [<samp>(7f04b)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7f04b119)
+- **projects**:
+ - 椤圭洰閫傞厤 Soybean 2.0 - by @m-xlsea
+ - 瀹㈡埛绔鐞嗘柊澧炵姸鎬佷慨鏀瑰紑鍏� - by @m-xlsea [<samp>(ea6a9)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ea6a92cd)
+ - Iframe 绫诲瀷鑿滃崟浼犲弬鏇存敼 - by @m-xlsea [<samp>(bf3d5)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/bf3d5cb3)
+ - 鏂板鍚屾绉熸埛鍙傛暟閰嶇疆鍔熻兘 - by @m-xlsea [<samp>(901a6)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/901a65ad)
+ - 鏂板鍏充簬椤甸潰 - by @m-xlsea [<samp>(7d851)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7d85127c)
+ - 鑿滃崟鏂板甯冨眬閫夋嫨鏀寔 - by @m-xlsea [<samp>(13de6)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/13de6fbb)
+ - 浼樺寲鎺у埗鍙拌緭鍑哄拰 sql 瀵煎叆鏂囦欢 - by @m-xlsea [<samp>(bfb71)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/bfb7169e)
+ - 鐧诲綍璁颁綇瀵嗙爜鍔犲瘑淇濆瓨 - by @m-xlsea [<samp>(90c52)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/90c52d97)
+ - 浣跨敤 highlight.js 鏇挎崲 monaco-editor - by @m-xlsea [<samp>(7dd7a)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7dd7a936)
+ - 婕旂ず椤甸潰鏂板瀛楁鎺掑簭 demo - by @m-xlsea [<samp>(41c25)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/41c25dcd)
+ - support pinning and unpinning of tabs - by @PChening [<samp>(b8a76)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/b8a767d7)
+ - hybrid layout mode auto select first deepest child menu - by @paynezhuang [<samp>(94019)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/9401925f)
+
+### 馃悶 Bug 淇
+
+- **hooks**:
+ - 淇 useTable 鑾峰彇瀛楁鍒楄〃闂 - by @m-xlsea [<samp>(0f83c)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/0f83cf5f)
+ - update pagination pageSize after data fetch. - by **Azir-11** [<samp>(64226)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/64226d9b)
+- **projects**:
+ - 淇琛ㄥ崟鏍¢獙闂 - by @m-xlsea [<samp>(62fb9)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/62fb9d90)
+ - 淇鐧诲綍椤甸潰 logo 棰滆壊闂 - by @m-xlsea [<samp>(27cae)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/27cae756)
+ - 淇璺敱 name 涓� path 涓嶄竴鑷存縺娲昏彍鍗曞紓甯搁棶棰� - by @m-xlsea [<samp>(789a6)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/789a6bb9)
+ - fix the incorrect judgment of home by pin tab. - by **Azir-11** [<samp>(62a43)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/62a43c39)
+- **project**:
+ - 淇瀵煎嚭鏃舵煡璇㈠弬鏁伴敊璇棶棰� - by @m-xlsea [<samp>(52ad9)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/52ad93b2)
+- **table**:
+ - 淇鍒嗛〉鏁版嵁澶勭悊閫昏緫 - by @imtzc [<samp>(a59fd)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/a59fdc58)
+- **template**:
+ - 璋冩暣鎼滅储妯″潡鐨勫睘鎬у畾涔変綅缃� - by @imtzc [<samp>(bb039)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/bb039eff)
+
+### 馃洜 浼樺寲
+
+- **projects**:
+ - 淇鑿滃崟浠g爜璐ㄩ噺闂 - by @m-xlsea [<samp>(6f349)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/6f34956e)
+ - 浼樺寲娉ㄩ噴瑙勮寖 - by @m-xlsea [<samp>(4139a)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4139a729)
+- **styles**:
+ - 浼樺寲灞炴�ц〃鏍煎睍寮�鍒楁牱寮� - by @m-xlsea [<samp>(e40c3)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e40c37a0)
+
+### 馃摉 鏂囨。
+
+- **other**:
+ - 浼樺寲 sql 鎻掑叆璇彞 - by @m-xlsea [<samp>(f7d8d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f7d8d189)
+ - 浼樺寲宸ヤ綔娴佺浉鍏宠彍鍗� - by @m-xlsea [<samp>(33155)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/3315552d)
+ - 绉婚櫎 cursor 鏂囦欢澶� - by @m-xlsea [<samp>(5f950)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/5f950b46)
+ - 淇妯℃澘澶勭悊宸ュ叿绫诲唴瀹归敊璇� - by @m-xlsea [<samp>(f6dcd)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f6dcded8)
+- **projects**:
+ - 鏇存柊 cursor 瑙勫垯 - by @m-xlsea [<samp>(e63fe)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e63fee59)
+
+### 馃彙 鏉傞」
+
+- **deps**:
+ - update deps - by @soybeanjs [<samp>(ec9f9)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ec9f9af9)
+ - update umo-editor deps - by @m-xlsea [<samp>(39f8d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/39f8d13b)
+- **styles**:
+ - format code - by @soybeanjs [<samp>(098cd)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/098cd50e)
+
+### 鉂わ笍 璐$尞鑰�
+
+[](https://github.com/m-xlsea) [](https://github.com/soybeanjs) [](https://github.com/imtzc) [](https://github.com/paynezhuang) [](https://github.com/PChening) [](https://github.com/Azir-11) [](https://github.com/wenyuanw) [](https://github.com/CyberShen) [](https://github.com/Lruihao)
+[鍒樼拹](mailto:hi.alue@qq.com), [CyberShen123](mailto:s.lijun@qq.com), [whyang](mailto:whyang9701@gmail.com), [HongxuanG](mailto:1359774872@qq.com), [NicholasLD](mailto:878639947@qq.com),
+
+## [v2.0.0-beta.2](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2025-12-17)
+
+### 馃殌 鏂板姛鑳�
+
+- **components**:
+ - 鍒楄缃柊澧炴粴鍔ㄦ潯澶勭悊 - by @m-xlsea [<samp>(6696d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/6696da52)
+- **docs**:
+ - 鏂板 GitCode star 寰界珷 - by @m-xlsea [<samp>(5310d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/5310d352)
+- **hooks**:
+ - 浼樺寲琛ㄦ牸鍝嶅簲鏁版嵁澶勭悊 - by @m-xlsea [<samp>(7d7f2)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7d7f28c4)
+- **project**:
+ - 浼樺寲涓氬姟浠g爜璇硶鏍煎紡 - by @m-xlsea [<samp>(7f04b)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7f04b119)
+- **projects**:
+ - 瀹㈡埛绔鐞嗘柊澧炵姸鎬佷慨鏀瑰紑鍏� - by @m-xlsea [<samp>(ea6a9)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ea6a92cd)
+ - Iframe 绫诲瀷鑿滃崟浼犲弬鏇存敼 - by @m-xlsea [<samp>(bf3d5)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/bf3d5cb3)
+ - 鏂板鍚屾绉熸埛鍙傛暟閰嶇疆鍔熻兘 - by @m-xlsea [<samp>(901a6)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/901a65ad)
+ - support pinning and unpinning of tabs - by @PChening [<samp>(b8a76)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/b8a767d7)
+ - hybrid layout mode auto select first deepest child menu - by @paynezhuang [<samp>(94019)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/9401925f)
+ - 鏂板鍏充簬椤甸潰 - by @m-xlsea [<samp>(7d851)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7d85127c)
+ - 鑿滃崟鏂板甯冨眬閫夋嫨鏀寔 - by @m-xlsea [<samp>(13de6)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/13de6fbb)
+ - 浼樺寲鎺у埗鍙拌緭鍑哄拰 sql 瀵煎叆鏂囦欢 - by @m-xlsea [<samp>(bfb71)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/bfb7169e)
+ - 鐧诲綍璁颁綇瀵嗙爜鍔犲瘑淇濆瓨 - by @m-xlsea [<samp>(90c52)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/90c52d97)
+ - 浣跨敤 highlight.js 鏇挎崲 monaco-editor - by @m-xlsea [<samp>(7dd7a)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7dd7a936)
+ - 婕旂ず椤甸潰鏂板瀛楁鎺掑簭 demo - by @m-xlsea [<samp>(41c25)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/41c25dcd)
+
+### 馃悶 Bug 淇
+
+- **hooks**:
+ - update pagination pageSize after data fetch. - by **Azir-11** [<samp>(64226)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/64226d9b)
+- **project**:
+ - 淇瀵煎嚭鏃舵煡璇㈠弬鏁伴敊璇棶棰� - by @m-xlsea [<samp>(52ad9)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/52ad93b2)
+- **projects**:
+ - fix the incorrect judgment of home by pin tab. - by **Azir-11** [<samp>(62a43)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/62a43c39)
+- **table**:
+ - 淇鍒嗛〉鏁版嵁澶勭悊閫昏緫 - by @imtzc [<samp>(a59fd)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/a59fdc58)
+- **template**:
+ - 璋冩暣鎼滅储妯″潡鐨勫睘鎬у畾涔変綅缃� - by @imtzc [<samp>(bb039)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/bb039eff)
+
+### 馃洜 浼樺寲
+
+- **projects**:
+ - 淇鑿滃崟浠g爜璐ㄩ噺闂 - by @m-xlsea [<samp>(6f349)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/6f34956e)
+ - 浼樺寲娉ㄩ噴瑙勮寖 - by @m-xlsea [<samp>(4139a)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4139a729)
+
+### 馃摉 鏂囨。
+
+- **other**:
+ - 鏇存柊 cursor 瑙勫垯 - by @m-xlsea [<samp>(1d6af)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/1d6af984)
+ - 浼樺寲 sql 鎻掑叆璇彞 - by @m-xlsea [<samp>(f7d8d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f7d8d189)
+ - 浼樺寲宸ヤ綔娴佺浉鍏宠彍鍗� - by @m-xlsea [<samp>(33155)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/3315552d)
+ - 绉婚櫎 cursor 鏂囦欢澶� - by @m-xlsea [<samp>(5f950)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/5f950b46)
+ - 淇妯℃澘澶勭悊宸ュ叿绫诲唴瀹归敊璇� - by @m-xlsea [<samp>(f6dcd)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f6dcded8)
+
+### 馃彙 閲嶆瀯
+
+- **deps**:
+ - update deps - by @soybeanjs [<samp>(7cf40)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7cf4083b)
+ - update umo-editor deps - by @m-xlsea [<samp>(39f8d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/39f8d13b)
+- **styles**:
+ - format code - by @soybeanjs [<samp>(098cd)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/098cd50e)
+
+### 鉂わ笍 璐$尞鑰�
+
+[](https://github.com/m-xlsea) [](https://github.com/imtzc) [](https://github.com/soybeanjs) [](https://github.com/paynezhuang) [](https://github.com/PChening) [](https://github.com/Azir-11)
+
+## [v2.0.0-beta.1](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v1.2.1...v2.0.0-beta.1) (2025-12-04)
+
+### 馃殌 鏂板姛鑳�
+
+- **projects**:
+ - 椤圭洰閫傞厤 Soybean 2.0 - by @m-xlsea
+- **components**:
+ - 鏂板棰勮涓婚鏀寔 - by @m-xlsea [<samp>(c1063)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/c1063e3e)
+- **hooks**:
+ - 瀹屾垚琛ㄦ牸 Hooks 鏀归�� - by @m-xlsea [<samp>(46996)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4699654f)
+ - 浼樺寲鏍戝舰琛ㄦ牸 hooks 灏佽 - by @m-xlsea [<samp>(ccbb7)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ccbb72c0)
+
+### 馃悶 Bug 淇
+
+- **projects**:
+ - 淇鐧诲綍椤甸潰 logo 棰滆壊闂 - by @m-xlsea [<samp>(27cae)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/27cae756)
+ - 淇璺敱 name 涓� path 涓嶄竴鑷存縺娲昏彍鍗曞紓甯搁棶棰� - by @m-xlsea [<samp>(789a6)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/789a6bb9)
+
+### 鉂わ笍 璐$尞鑰�
+
+[](https://github.com/m-xlsea) [](https://github.com/soybeanjs) [](https://github.com/Azir-11) [](https://github.com/wenyuanw) [](https://github.com/CyberShen) [](https://github.com/Lruihao)
+[鍒樼拹](mailto:hi.alue@qq.com), [CyberShen123](mailto:s.lijun@qq.com), [whyang](mailto:whyang9701@gmail.com), [HongxuanG](mailto:1359774872@qq.com), [NicholasLD](mailto:878639947@qq.com),
+
+## [v1.2.1](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v1.2.0...v1.2.1) (2025-10-29)
+
+### 馃殌 鏂板姛鑳�
+
+- **components**:
+ - 鑿滃崟鏍戦�夋嫨缁勪欢鏂板闅愯棌绂佺敤鏍囪瘑 - by @m-xlsea [<samp>(08cfa)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/08cfa167)
+ - 鍒楄缃柊澧炴粴鍔ㄦ潯澶勭悊 - by @m-xlsea [<samp>(6696d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/6696da52)
+- **docs**:
+ - 鏂板 GitCode star 寰界珷 - by @m-xlsea [<samp>(5310d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/5310d352)
+- **projects**:
+ - 浼樺寲瀛楀吀鎿嶄綔 - by @m-xlsea [<samp>(2400b)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/2400bf8c)
+ - 瀹㈡埛绔鐞嗘柊澧炵姸鎬佷慨鏀瑰紑鍏� - by @m-xlsea [<samp>(ea6a9)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ea6a92cd)
+ - Iframe 绫诲瀷鑿滃崟浼犲弬鏇存敼 - by @m-xlsea [<samp>(bf3d5)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/bf3d5cb3)
+ - 鏂板鍚屾绉熸埛鍙傛暟閰嶇疆鍔熻兘 - by @m-xlsea [<samp>(901a6)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/901a65ad)
+
+### 馃悶 Bug 淇
+
+- **projects**: 淇浠g爜鐢熸垚鏍戞ā鏉块棶棰� - by **AN** [<samp>(fa7bc)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/fa7bc434)
+
+### 馃洜 浼樺寲
+
+- **projects**:
+ - 浼樺寲浠g爜鍐呭 - by @m-xlsea [<samp>(9edbd)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/9edbd8e6)
+ - 淇鑿滃崟浠g爜璐ㄩ噺闂 - by @m-xlsea [<samp>(6f349)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/6f34956e)
+
+### 馃摉 鏂囨。
+
+- **other**: 鏇存柊 cursor 瑙勫垯 - by @m-xlsea [<samp>(1d6af)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/1d6af984)
+- **projects**: 鏇存柊 cursor 瑙勫垯 - by @m-xlsea [<samp>(e63fe)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e63fee59)
+
+### 馃帹 鏍峰紡
+
+- **projects**: 浼樺寲娉ㄩ噴瑙勮寖 - by @m-xlsea [<samp>(4139a)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4139a729)
+
+### 鉂わ笍 璐$尞鍊�
+
+[](https://gitee.com/xlsea) [](https://gitee.com/elio-an)
+
+## [v1.2.0](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v1.1.3...v1.2.0) (2025-09-26)
+
+### 馃殌 鏂板姛鑳�
+
+- **components**:
+ - 鏂板 umodoc 缂栬緫鍣ㄩ泦鎴� - by @m-xlsea [<samp>(f182d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f182def5)
+- **projects**:
+ - 閲嶆瀯鐧诲綍椤甸潰鏍峰紡 - by @m-xlsea [<samp>(8412a)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/8412a8db)
+ - 璺敱鍏煎 activeMenu 閫夐」 - by @m-xlsea [<samp>(25ee3)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/25ee3207)
+ - 鐢ㄦ埛鍒楄〃鏂板澶村儚灞曠ず - by @m-xlsea [<samp>(3146c)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/3146c039)
+ - 鏂板宀椾綅閮ㄩ棬鏍戞帴鍙� - by **AN** [<samp>(28101)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/28101cb2)
+- **styles**:
+ - 浼樺寲宸︿晶鏍戝舰缁撴瀯鏍峰紡 - by @m-xlsea [<samp>(513dc)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/513dc31e)
+- **utils**:
+ - 鏂板鏈湴 Excel 瀵煎嚭宸ュ叿绫� - by @m-xlsea [<samp>(7f2f3)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7f2f3bd0)
+
+### 馃悶 Bug 淇
+
+- **components**:
+ - 淇瀛楀吀鏍囩浼氫慨鏀瑰瓧鍏告暟鎹�奸棶棰� - by @m-xlsea [<samp>(90a14)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/90a14e33)
+- **hooks**:
+ - 淇涓嬭浇 hooks 閿欒鏈鐞� - by @m-xlsea [<samp>(5ef1c)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/5ef1c5de)
+- **packages**:
+ - axios: fix json response. fixed #815 - by @soybeanjs in https://gitee.com/xlsea/ruoyi-plus-soybean/issues/815 [<samp>(fd087)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/fd087f59)
+ - 淇tinymce灞傜骇闂 - by **AN** [<samp>(2c248)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/2c248d82)
+- **projects**:
+ - 淇敼浠g爜鐢熸垚鍔熻兘妯″潡鍚嶄负椹煎嘲鏃讹紝璺敱閿欒闂 - by **AN** [<samp>(2f794)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/2f794c4b)
+ - 淇鏂板閮ㄩ棬鏃朵笉鏄剧ず涓婄骇閮ㄩ棬闂 - by **AN** [<samp>(d5bbc)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/d5bbc37d)
+ - 淇鑿滃崟寮圭獥鎵撳紑鏈竻绌洪粯璁ゅ�奸棶棰� - by @m-xlsea [<samp>(ad207)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ad207255)
+ - 淇閫�鍑虹櫥褰曟湭娓呯┖娑堟伅鍒楄〃闂 - by @m-xlsea [<samp>(dc2fb)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/dc2fbbd5)
+ - 淇鑿滃崟榛樿鍥炬爣闂 - by @m-xlsea [<samp>(34ab7)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/34ab7d5d)
+ - 淇娑堟伅閫氱煡瀛楀吀鍊兼湭澶勭悊闂 - by @m-xlsea [<samp>(3f148)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/3f148a4e)
+ - 淇鐧诲綍椤甸潰璺宠浆闂 - by @m-xlsea [<samp>(8aeb7)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/8aeb7362)
+ - 淇鐧诲綍椤甸潰鏍峰紡闂 - by @m-xlsea [<samp>(4e27f)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4e27f3b5)
+- **types**:
+ - fix proxy types - by @soybeanjs [<samp>(12b25)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/12b25e0d)
+- **utils**:
+ - 淇璇锋眰宸ュ叿鍝嶅簲瑙e瘑闂 - by @m-xlsea [<samp>(9ef0b)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/9ef0bd41)
+
+### 馃洜 浼樺寲
+
+- **components**: 琛ュ厖鍥介檯鍖� - by **AN** [<samp>(ecad1)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ecad1c3e)
+- **projects**: 瀛楀吀鐘舵�佷娇鐢ㄦ灇涓惧�� - by @m-xlsea [<samp>(56fd5)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/56fd5434)
+
+### 馃摉 鏂囨。
+
+- **other**: 鏇存柊 cursor 瑙勫垯 - by @m-xlsea [<samp>(e623b)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e623b560)
+
+### 馃彙 閲嶆瀯
+
+- **deps**:
+ - update deps - by @soybeanjs [<samp>(e33f9)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e33f944a)
+ - update deps - by @soybeanjs [<samp>(9fa95)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/9fa951aa)
+
+### 馃帹 鏍峰紡
+
+- **components**: 淇敼json棰勮缁勪欢鏍峰紡闂 - by **AN** [<samp>(378aa)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/378aa869)
+- **styles**: 淇瀛椾綋鏍峰紡瀵艰嚧涓嬪垝绾夸笉鍙闂 - by **AN** [<samp>(4a424)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4a4244b5)
+
+### 鉂わ笍 璐$尞鑰�
+
+[](https://github.com/m-xlsea) [](https://github.com/soybeanjs) [](https://gitee.com/elio-an)
+
+## [v1.1.3](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v1.1.2...v1.1.3) (2025-08-16)
+
+### 馃悶 Bug 淇
+
+- **hooks**:
+ - 闈炲畨鍏ㄧ幆澧冧笅涓嶄娇鐢ㄦ祦寮忎笅杞� - by @m-xlsea [<samp>(f8983)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f8983557)
+ - 淇oss涓嬭浇鏃舵湭杞爜闂 - by **AN** [<samp>(2d31d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/2d31d7dc)
+- **project**:
+ - 鍏抽棴澶氱鎴峰姛鑳藉悗浠嶇劧閬嶅巻绉熸埛鍒楄〃瀵艰嚧鎺у埗鍙版姤閿欑殑闂 - by **wang_rui** [<samp>(b96c4)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/b96c46ba)
+ - 鍏抽棴澶氱鎴峰姛鑳藉悗浠嶇劧閬嶅巻绉熸埛鍒楄〃瀵艰嚧鎺у埗鍙版姤閿欑殑闂 Merge pull request !25 from littleghost2016/dev - by **涓嶅淇�** [<samp>(90276)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/9027632b)
+- **projects**:
+ - 淇涓�绾ц彍鍗曢殣钘忓け鏁堥棶棰� - by **AN** [<samp>(8fcc7)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/8fcc70d7)
+ - 淇鏃ユ湡鎼滅储鏉′欢娓呴櫎闂 - by **AN** [<samp>(52318)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/52318c10)
+ - 淇鐧诲綍杩囨湡浜嬩欢鐩戝惉鏈閲嶇疆 - by @m-xlsea [<samp>(71037)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/71037439)
+ - 淇鐢ㄦ埛鏂板鏃惰鑹蹭笅鎷夊寘鍚秴绾х鐞嗗憳闂 - by **AN** [<samp>(a15b6)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/a15b683b)
+ - 淇鐢ㄦ埛瀵煎叆鍔熻兘鏃犳硶鏇存柊闂 - by **AN** [<samp>(4e983)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4e9839bd)
+ - Fix the icon size in the image preview toolbar - by @m-xlsea [<samp>(4539f)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4539fe01)
+ - 淇鏂板鐢ㄦ埛鏈煡璇㈣鑹插垪琛ㄩ棶棰� - by **AN** [<samp>(d6ae8)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/d6ae85d2)
+- **readme**:
+ - update GitHub stars and forks links for gitee - by @soybeanjs [<samp>(923eb)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/923eb98a)
+
+### 馃拝 閲嶆瀯
+
+- **menu**:
+ - 鑿滃崟绠$悊涓殣钘忕殑鑿滃崟鏄剧ず鐏拌壊 - by **NicholasLD** [<samp>(adca2)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/adca2e26)
+ - 鑿滃崟绠$悊涓殣钘忕殑鑿滃崟鏄剧ず鐏拌壊 Merge pull request !24 from NicholasLD/N/A - by **涓嶅淇�** [<samp>(4eb77)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4eb77eac)
+- **projects**:
+ - 鑿滃崟鍒楄〃鏂板绂佺敤鑿滃崟鏍峰紡 - by @m-xlsea [<samp>(e5383)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e538355f)
+
+### 馃彙 鏉傞」
+
+- **other**: update the ESLint validation configuration to support more file types. - by **Azir-11** [<samp>(8d7f9)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/8d7f91dc)
+- **readme**: remove DartNode sponsorship badge from README files - by @soybeanjs [<samp>(33ade)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/33ade539)
+
+### 鉂わ笍 璐$尞鑰�
+
+[](https://github.com/soybeanjs) [](https://github.com/m-xlsea) [](https://gitee.com/elio-an) [](https://github.com/Azir-11) [](https://github.com/NicholasLD)
+[wang_rui](mailto:wrr1996@163.com)
+
+## [v1.1.2](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v1.1.1...v1.1.2) (2025-07-24)
+
+### 馃悶 Bug 淇
+
+- 淇 api.d.ts.vm 浠g爜鐢熸垚妯℃澘bug - by **zygalaxy** [<samp>(4e8c8)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4e8c8715)
+- **projects**:
+ - 淇鍒锋柊鏃惰烦杞嚦鐧诲綍椤甸棶棰� - by **AN** [<samp>(2587f)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/2587f8cb)
+ - 淇鐧诲綍杩囨湡涓嶅脊绐楅棶棰� - by **AN** [<samp>(e485f)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e485f680)
+ - 淇鑿滃崟缁撴瀯鍙樺姩鍚庤矾鐢辨棤娉曡繘鍏ラ棶棰� - by @m-xlsea [<samp>(f4038)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f4038a2d)
+
+### 馃洜 浼樺寲
+
+- **projects**: 浼樺寲鎼滅储妗咶ormItem - by **AN** [<samp>(a1336)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/a1336d15)
+
+### 馃彙 鏉傞」
+
+- **deps**: update deps - by @soybeanjs [<samp>(e89b8)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e89b86ce)
+
+### 馃帹 鏍峰紡
+
+- **projects**: 鎼滅储FormItem鍗犳瘮璋冩暣 - by **AN** [<samp>(cc29e)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/cc29ea85)
+
+### 鉂わ笍 璐$尞鑰�
+
+[](https://github.com/m-xlsea) [](https://gitee.com/elio-an) [](https://github.com/soybeanjs)
+[zygalaxy](mailto:zygalaxy@qq.com)
+
+## [v1.1.1](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v1.1.0...v1.1.1) (2025-07-11)
+
+### 馃殌 鏂板姛鑳�
+
+- **hooks**:
+ - 閲嶆瀯涓嬭浇鏂规硶锛屾敮鎸佹祦寮忎笅杞� - by @m-xlsea [<samp>(65067)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/650673e2)
+- **projects**:
+ - 瑙掕壊鍒嗛厤鐢ㄦ埛鏂板閮ㄩ棬涓庢椂闂存煡璇㈡潯浠� - by @m-xlsea [<samp>(ad48d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ad48d8e8)
+ - 淇敼鎿嶄綔鍚庡垪琛ㄦ煡璇㈡柟寮� - by @m-xlsea [<samp>(d8542)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/d85424ee)
+
+### 馃悶 Bug 淇
+
+- **hooks**:
+ - 瑙e喅 streamsaver 璁块棶涓嶅埌 Github 璧勬簮闂 - by @m-xlsea [<samp>(566b2)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/566b2c2d)
+- **other**:
+ - 淇浠g爜鐢熸垚绫诲瀷瀹氫箟鏂囦欢閲嶅闂 - by @m-xlsea [<samp>(f7c7f)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f7c7fc41)
+- **packages**:
+ - 淇 cleanup 浼氬垹闄ゅ瘜鏂囨湰缂栬緫鍣ㄨ祫婧愰棶棰� - by @m-xlsea [<samp>(9ca7c)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/9ca7ca8f)
+- **projects**:
+ - 淇瀛楀吀鏁版嵁閲嶅鑾峰彇闂 - by @m-xlsea [<samp>(3628c)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/3628c249)
+ - 淇敼寮洪��鍦ㄧ嚎璁惧鎺ュ彛 - by **AN** [<samp>(dbcf8)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/dbcf8d42)
+ - 淇浠g爜鐢熸垚閫昏緫鍒ゆ柇闂 - by **AN** [<samp>(6fc7b)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/6fc7b11b)
+ - 淇閮ㄩ棬瀛楀吀 sys_normal_disable 閲嶅鑾峰彇 Merge pull request !11 from 绱犺繕鐪�/N/A - by @m-xlsea [<samp>(ad938)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ad9386eb)
+ - 淇鏈竻绌烘枃浠跺垪琛紝涓婁紶鍥炴樉闂 - by **AN** [<samp>(229e0)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/229e0044)
+ - Fix i18n-ally not working when setting moduleResolution to bundler. fixed #780 - by @xiaobao0505 in https://gitee.com/xlsea/ruoyi-plus-soybean/issues/780 [<samp>(41191)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/41191d54)
+ - 淇瑙掕壊鍒楄〃鎿嶄綔鏍忓睍绀轰笉鍏ㄩ棶棰� - by @m-xlsea [<samp>(62f2c)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/62f2c6d5)
+ - 淇鐢ㄦ埛瀵煎叆缁撴灉淇℃伅鏈覆鏌撴爣绛鹃棶棰� - by **AN** [<samp>(efc95)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/efc953c0)
+ - 淇瑙掕壊鐢ㄦ埛鍒嗛厤鏈皟鐢ㄦ帴鍙i棶棰� - by @m-xlsea [<samp>(ff874)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ff87415d)
+- **styles**:
+ - 淇鐧诲綍椤靛钩鏉跨晫闈㈡粴鍔ㄩ棶棰� - by @m-xlsea [<samp>(90145)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/90145fa5)
+- **utils**:
+ - 淇isNull鍜孖sNotNull鍒ゆ柇鏂规硶娼滃湪闂 - by **AN** [<samp>(90d32)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/90d32ee2)
+
+### 馃拝 閲嶆瀯
+
+- **projects**: 璋冩暣绉熸埛濂楅鑿滃崟鎺ュ彛 - by **AN** [<samp>(b9999)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/b9999935)
+
+### 馃摉 鏂囨。
+
+- **other**: 淇敼鏂囨。鍐呭 - by @m-xlsea [<samp>(3ae99)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/3ae9922d)
+- **projects**: 浼樺寲 cursor 瑙勫垯鍙� mcp - by @m-xlsea [<samp>(a3199)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/a31994dc)
+- **readme**: 鏇存柊 README.md 鏂囦欢 - by @m-xlsea [<samp>(99675)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/99675cbc)
+
+### 馃彙 鏉傞」
+
+- **deps**:
+ - update NodeJS and pnpm version requirements in package.json and documentation - by **Junior25306** [<samp>(a5c4b)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/a5c4b4e3)
+ - update deps - by @soybeanjs [<samp>(5cb1c)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/5cb1cebd)
+ - update deps - by @soybeanjs [<samp>(aeb63)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/aeb63690)
+ - update deps - by @m-xlsea [<samp>(89c71)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/89c716e1)
+- **packages**:
+ - update Vite version to 7 in package.json and documentation. - by **Azir** [<samp>(03dd6)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/03dd64c5)
+- **projects**:
+ - update pnpm-lock.yaml - by @m-xlsea [<samp>(7c6ca)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7c6ca91e)
+- **vscode**:
+ - remove unused vue.server.hybridMode setting from .vscode/settings.json - by @soybeanjs [<samp>(13319)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/133196f3)
+
+### 鉂わ笍 璐$尞鍊�
+
+[](https://github.com/m-xlsea) [](https://github.com/soybeanjs) [](https://github.com/xiaobao0505) [](https://gitee.com/elio-an) [](https://github.com/Azir-11) [Junior25306](mailto:dayu429@qq.com)
+
+## [v1.1.0](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v1.0.0...v1.1.0) (2025-07-01)
+
+### 馃殌 鏂板姛鑳�
+
+- **components**:
+ - 鏂板琛ㄥ崟涓婁紶缁勪欢 - by @m-xlsea [<samp>(03c8a)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/03c8a7f5)
+- **other**:
+ - 鏂板鑿滃崟瀛楀吀澶氳瑷�閫傞厤 SQL - by @m-xlsea [<samp>(0f33f)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/0f33f4a3)
+- **projects**:
+ - add configurable user name watermark option - by @wenyuanw [<samp>(7c3da)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7c3dac42)
+ - 鑿滃崟瀛楀吀閫傞厤 i18n - by @m-xlsea [<samp>(39dd9)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/39dd9acc)
+ - 鏂板瀛楀吀澶氳瑷�閫傞厤 - by @m-xlsea [<samp>(8c840)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/8c84063a)
+- **styles**:
+ - 淇鐧诲綍椤电Щ鍔ㄧ鏄剧ず闂 - by @m-xlsea [<samp>(742e3)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/742e3858)
+
+### 馃悶 Bug 淇
+
+- **app**:
+ - replace console.error with window.console.error for consistency - by @soybeanjs [<samp>(7d840)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7d84062e)
+- **auth**:
+ - remove redundant authStore declaration in resetStore function - by @soybeanjs [<samp>(c57f8)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/c57f88aa)
+- **components**:
+ - 淇鑿滃崟鏍戦�夋嫨缁勪欢 - by @m-xlsea [<samp>(bbda8)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/bbda803e)
+ - 淇鏍戦�夋嫨缁勪欢鍐嶆鍕鹃�夌埗瀛愯仈鍔ㄥ鑷村叏閫夐棶棰� - by @m-xlsea [<samp>(aeb73)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/aeb736eb)
+ - 淇閮ㄩ棬閫夋嫨缁勪欢闈炴爲缁撴瀯锛岄粯璁ゅ睍寮�澶辫触闂 - by **AN** [<samp>(da1c1)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/da1c16e0)
+ - 淇涓婁紶缁勪欢鍥炴樉闂锛屼慨鏀筧ccept鍙傛暟閫昏緫 - by **AN** [<samp>(e16a0)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e16a0fa6)
+ - 淇鑿滃崟閫夋嫨鏍囩娓叉煋闂 - by @m-xlsea [<samp>(6e6cc)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/6e6cc4d9)
+- **other**:
+ - 淇浠g爜鐢熸垚闂 - by @m-xlsea [<samp>(1ec10)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/1ec10991)
+ - 浠g爜鐢熸垚妯℃澘 dateRangeTime 閿欒 - by @m-xlsea [<samp>(f0810)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f0810bce)
+ - 淇浠g爜鐢熸垚瀛楀吀鐩稿叧闂 - by @m-xlsea [<samp>(94d18)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/94d1863e)
+ - 淇浠g爜鐢熸垚绫诲瀷瀹氫箟鏂囦欢閲嶅闂 - by @m-xlsea [<samp>(f7c7fc41)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f7c7fc41)
+- **projects**:
+ - 淇鑷畾涔夋暟鎹潈闄愭病鏈変繚瀛樿鑹查儴闂╞ug - by **AN** [<samp>(a0f33)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/a0f33664)
+ - 淇鐧诲綍杩囨湡鍚庯紝閲嶅寮圭獥闂 - by **AN** [<samp>(cafee)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/cafee1db)
+ - 淇棣栭〉鏈粠鐜鍙橀噺鑾峰彇闂 - by @m-xlsea [<samp>(031b7)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/031b7f69)
+ - 淇瀵煎嚭鏌ヨ鍙傛暟闂 - by @m-xlsea [<samp>(ffa47)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ffa47c37)
+ - 淇鏉冮檺瀛楃鏄剧ず閫昏緫閿欒闂 - by **AN** [<samp>(0ac0a)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/0ac0a093)
+ - 鐩綍绫诲瀷绂佺敤iframe閫夐」 - by **AN** [<samp>(72b8f)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/72b8f56e)
+ - 淇鍒囨崲鐢ㄦ埛鎴栫櫥褰曡繃鏈熼儴鍒嗛棶棰� - by @m-xlsea [<samp>(27f06)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/27f06195)
+ - 淇鎺ュ彛璇锋眰寮傚父鎷︽埅闂 - by @m-xlsea [<samp>(031d0)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/031d071a)
+ - 淇涓汉淇℃伅-淇敼瀵嗙爜鏈姞瀵嗕笖鍙傛暟閿欒闂 - by **AN** [<samp>(8b315)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/8b3151b8)
+ - 璋冩暣灞炴�у悕 - by **AN** [<samp>(62e6c)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/62e6c776)
+ - ensure proper text color when themes are inverted - by @wenyuanw [<samp>(afd60)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/afd60421)
+- **styles**:
+ - 娣诲姞婊氬姩鏉★紝鍘婚櫎椤电爜 - by **AN** [<samp>(d37ad)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/d37adc36)
+- **types**:
+ - The environment variable VITE_ICON_LOCAL_PREFIX has the wrong type. - by **chenziwen** [<samp>(da149)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/da149e5b)
+- **utils**:
+ - 淇 鍒犻櫎褰撳墠tab涓烘渶鍚庝竴涓椂锛宼ab鍒囨崲閿欒bug. - by **AN** [<samp>(64bd1)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/64bd119c)
+
+### 馃洜 浼樺寲
+
+- **components**:
+ - optimize spacing for lang-switch dropdown options - by @wenyuanw [<samp>(fcb89)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/fcb89883)
+- **projects**:
+ - optimize tab deletion logic. closed #755 - by @wenyuanw in https://gitee.com/xlsea/ruoyi-plus-soybean/issues/755 [<samp>(e6044)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e6044d0f)
+ - optimize tab deletion logic - by **AN** [<samp>(858c3)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/858c3180)
+ - 浼樺寲鎺ュ彛璇锋眰寮傚父鎷︽埅浠g爜 - by @m-xlsea [<samp>(47191)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/471912e1)
+
+### 馃拝 閲嶆瀯
+
+- **iframe-page**: remove unused lifecycle hooks and clean up script setup - by @soybeanjs [<samp>(276d8)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/276d836c)
+- **projects**: 琛ュ厖formTip淇℃伅 - by **AN** [<samp>(f36ac)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f36ac9ab)
+
+### 馃摉 鏂囨。
+
+- **readme**:
+ - 鏇存柊 README.md 鏂囦欢 - by @m-xlsea [<samp>(99675cb)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/99675cb)
+
+### 馃彙 鏉傞」
+
+- **deps**:
+ - update deps - by @soybeanjs [<samp>(3e4e1)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/3e4e17ab)
+ - update deps - by @soybeanjs [<samp>(dc674)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/dc674ce8)
+ - update deps - by @m-xlsea [<samp>(fec05)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/fec0563e)
+- **projects**:
+ - 绉婚櫎鏈娇鐢ㄤ唬鐮� - by **AN** [<samp>(d141e)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/d141ed5b)
+ - update deps & fix `moduleResolution` - by @soybeanjs [<samp>(dbd99)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/dbd995c1)
+
+### 馃帹 鏍峰紡
+
+- **projects**:
+ - 鏇存崲 logo 涓庡姞杞芥牱寮� - by @m-xlsea [<samp>(7e4ec)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7e4ecae6)
+ - 閲嶆瀯鐧诲綍椤垫牱寮� - by @m-xlsea [<samp>(40680)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/406800de)
+ - 淇敼鎸夐挳鏂囨湰棰滆壊 - by @m-xlsea [<samp>(907f0)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/907f0439)
+ - 浼樺寲绉诲姩绔瓧浣撳ぇ灏� - by @m-xlsea [<samp>(8b4e4)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/8b4e41ce)
+
+### 鉂わ笍 璐$尞鑰�
+
+[](https://gitee.com/xlsea) [](https://github.com/soybeanjs) [](https://github.com/wenyuanw) [](https://gitee.com/elio-an) [](https://github.com/chen-ziwen)
+[](https://gitee.com/wangzhongqi0917) [](https://gitee.com/qq1822213252) [](https://gitee.com/tangzc), [metabytes](https://gitee.com/metabytes)
+
+
+## [v1.0.0](https://gitee.com/xlsea/ruoyi-plus-soybean/releases/tag/v1.0.0) (2025-06-05)
+
+### 馃殌 鏂板姛鑳�
+
+1.0.0 鐗堟湰姝e紡鍙戝竷锛屾鐗堟湰涓嶅寘鍚伐浣滄祦涓庡璇█锛岃鏈熷緟鍚庣画鐗堟湰鍙戝竷銆�
+
+### 鉂わ笍 璐$尞鑰�
+
+棣栨鍙戠増涓嶅睍绀鸿繃澶氳础鐚�咃紝鏁璋呰В
+
+[](https://github.com/honghuangdc) [](https://gitee.com/xlsea) [](https://gitee.com/elio-an) [](https://github.com/wangqiqi95)
diff --git a/ruoyi-plus-soybean/LICENSE b/ruoyi-plus-soybean/LICENSE
new file mode 100755
index 0000000..5d56e4a
--- /dev/null
+++ b/ruoyi-plus-soybean/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 xlsea
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/ruoyi-plus-soybean/README.md b/ruoyi-plus-soybean/README.md
new file mode 100755
index 0000000..faed3fa
--- /dev/null
+++ b/ruoyi-plus-soybean/README.md
@@ -0,0 +1,391 @@
+<div align="center">
+ <img src="https://docs.ruoyi.xlsea.cn/logo.svg" width="160">
+ <h1>RuoYi-Plus-Soybean</h1>
+</div>
+
+<div style="height: 10px; clear: both;"></div>
+
+<div align="center">
+ <p>涓�涓熀浜� <a href="https://gitee.com/dromara/RuoYi-Vue-Plus" target="_blank">RuoYi-Vue-Plus</a> 鐨勫悗绔兘鍔涘拰 <a href="https://github.com/soybeanjs/soybean-admin" target="_blank">Soybean Admin</a> 鍓嶇鐗规�х殑鏅鸿兘鍖栫儫鍘傜患鍚堢鐞嗗钩鍙�</p>
+ <p>
+ <a href="https://gitcode.com/xlsea/ruoyi-plus-soybean" target="_blank"><img src="https://gitcode.com/xlsea/ruoyi-plus-soybean/star/badge.svg" alt="GitCode"></a>
+ <a href="https://github.com/m-xlsea/ruoyi-plus-soybean" target="_blank"><img src="https://img.shields.io/github/stars/m-xlsea/ruoyi-plus-soybean" alt="Github"></a>
+ <a href="https://gitee.com/xlsea/ruoyi-plus-soybean" target="_blank"><img src="https://gitee.com/xlsea/ruoyi-plus-soybean/badge/star.svg" alt="Gitee"></a>
+ <a href="https://vuejs.org" target="_blank"><img src="https://img.shields.io/badge/Vue-3.5-brightgreen" alt="vue"></a>
+ <a href="https://www.typescriptlang.org" target="_blank"><img src="https://img.shields.io/badge/TypeScript-5.8-blue" alt="typescript"></a>
+ <a href="https://vite.dev" target="_blank"><img src="https://img.shields.io/badge/Vite-6.2-orange" alt="vite"></a>
+ <a href="https://www.naiveui.com" target="_blank"><img src="https://img.shields.io/badge/NaiveUI-2.41-purple" alt="naive-ui"></a>
+ <a href="./LICENSE" target="_blank"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="license"></a>
+ </p>
+</div>
+
+# 馃摙 閲嶈閫氱煡
+
+2.0.0 鐗堟湰宸茬粡姝e紡鍙戝竷锛堝伐浣滄祦鐗堟湰璇峰垏鎹� [flow](https://gitee.com/xlsea/ruoyi-plus-soybean/tree/flow/) 鍒嗘敮鏌ョ湅锛夛紝浣嗕粛鐒跺缓璁細
+- 鍦ㄧ敓浜х幆澧冧娇鐢ㄥ墠杩涜鍏呭垎娴嬭瘯
+- 鍏虫敞椤圭洰鏇存柊锛屽強鏃惰幏鍙栨渶鏂扮増鏈�
+- 绉瀬鍙嶉闂锛屽府鍔╂垜浠揩閫熻凯浠�
+
+**鍚庣画瑙勫垝**
+- 澶氳瑷�鍥介檯鍖栧畬鍠�
+- 鎬ц兘浼樺寲鍜岀ǔ瀹氭�ф彁鍗�
+
+> 濡傛灉瀵硅椤圭洰鎰熷叴瓒o紝鍙互缁欎竴涓� Star 鏀寔涓�涓嬶紝璋㈣阿锛�
+> 璇峰ぇ瀹惰笂璺冩彁浜� PR 鍜� Issue锛屼竴璧峰畬鍠勮繖涓」鐩�
+
+# 鉂楀紑鍙戝墠蹇呯湅
+
+<p style="font-weight: bold; font-size: 24px;">鏈」鐩己鍒朵娇鐢� pnpm 鏋勫缓锛岃缁嗚鐪� <a href="#瀹夎姝ラ鍙婅鏄�">瀹夎姝ラ鍙婅鏄�</a></p>
+
+<p style="font-weight: bold; font-size: 24px;">鍚庣闇�瑕佹浛鎹唬鐮佺敓鎴愭ā鏉夸笌鑿滃崟 SQL锛岃缁嗚鐪� <a href="#浠g爜鐢熸垚涓庤彍鍗曟洿鏂�">浠g爜鐢熸垚涓庤彍鍗曟洿鏂�</a></p>
+
+# 馃拵 鍙嬫儏閾炬帴
+
+- [Snail Job Pro](https://pro.snailjob.opensnail.com/home) - 鐏垫椿锛屽彲闈犲拰蹇�熺殑鍒嗗竷寮忎换鍔¢噸璇曞拰鍒嗗竷寮忎换鍔¤皟搴﹀钩鍙�
+- [AiZuDa - 鐖辩粍鎼紙椋為緳宸ヤ綔娴佷紒涓氱増锛塢(https://naiveui.aizuda.com) - 鍍忔惌绉湪涓�鏍疯繘琛屼綆浠g爜鐢氳嚦闆朵唬鐮佸揩閫熸瀯寤哄簲鐢�
+
+## 馃搵 椤圭洰姒傝堪
+
+RuoYi-Plus-Soybean 鏄竴涓櫤鑳藉寲鐑熷巶缁煎悎绠$悊骞冲彴锛屽畠缁撳悎浜� RuoYi-Vue-Plus 鐨勫己澶у悗绔姛鑳藉拰 Soybean Admin 鐨勭幇浠e寲鍓嶇鐗规�э紝涓虹儫鑽変紒涓氭彁渚涗簡瀹屾暣鐨勭敓浜т笌绠$悊瑙e喅鏂规銆�
+
+### 馃専 椤圭洰鐗圭偣
+
+- **澶氱鎴锋灦鏋�**锛氬畬鏁存敮鎸丼aaS澶氱鎴锋ā寮忥紝鐏垫椿鐨勭鎴风鐞嗚兘鍔�
+- **鐜颁唬鍓嶇鎶�鏈爤**锛氬熀浜嶸ue 3銆乀ypeScript銆乂ite鍜孨aive UI鏋勫缓
+- **Monorepo宸ョ▼绠$悊**锛氫娇鐢╬npm workspaces绠$悊澶氬寘缁撴瀯
+- **涓板瘜鐨勭粍浠跺簱**锛氬唴缃ぇ閲忎笟鍔$粍浠跺拰甯冨眬閫夐」
+- **涓婚瀹氬埗**锛氭敮鎸佸绉嶅竷灞�妯″紡鍜屼富棰橀厤鑹�
+- **鍥介檯鍖�**锛氬唴缃璇█鏀寔
+- **鏉冮檺绠$悊**锛氱簿缁嗙殑鍩轰簬瑙掕壊鐨勬潈闄愭帶鍒�
+
+## 馃洜锔� 鎶�鏈爤
+
+### 鍓嶇
+- **鏍稿績妗嗘灦**锛歏ue 3.5.x
+- **寮�鍙戣瑷�**锛歍ypeScript 5.8.x
+- **鏋勫缓宸ュ叿**锛歏ite 6.2.x
+- **UI缁勪欢搴�**锛歂aive UI 2.41.x
+- **鐘舵�佺鐞�**锛歅inia 3.0.x
+- **璺敱**锛歏ue Router 4.5.x
+- **HTTP瀹㈡埛绔�**锛欰xios/Alova
+- **CSS**锛歎noCSS
+- **鍖呯鐞嗗櫒**锛歱npm 8.x+
+
+### 鍚庣锛堜笌RuoYi-Vue-Plus鍏煎锛�
+- **鏍稿績妗嗘灦**锛歋pring Boot
+- **瀹夊叏妗嗘灦**锛歋pring Security
+- **鏉冮檺璁よ瘉**锛歋a-Token
+- **鏁版嵁鎿嶄綔**锛歁yBatis-Plus
+- **鏁版嵁搴�**锛歁ySQL
+
+## 馃彈锔� 椤圭洰缁撴瀯
+
+```
+root
+鈹溾攢鈹� build # 鏋勫缓閰嶇疆鍜屾彃浠�
+鈹� 鈹溾攢鈹� config # 鏋勫缓閰嶇疆鏂囦欢
+鈹� 鈹斺攢鈹� plugins # Vite 鎻掍欢
+鈹溾攢鈹� docs # 鏂囨。鍜屾ā鏉�
+鈹� 鈹溾攢鈹� java # 浠g爜鐢熸垚宸ュ叿绫�
+鈹� 鈹斺攢鈹� template # 浠g爜鐢熸垚妯℃澘
+鈹溾攢鈹� packages # Monorepo鍖�
+鈹� 鈹溾攢鈹� alova # 浣跨敤Alova鐨凥TTP瀹㈡埛绔疄鐜�
+鈹� 鈹溾攢鈹� axios # 浣跨敤Axios鐨凥TTP瀹㈡埛绔疄鐜�
+鈹� 鈹溾攢鈹� color # 棰滆壊绠$悊宸ュ叿
+鈹� 鈹溾攢鈹� hooks # 鍙鐢ㄧ殑Vue缁勫悎鍑芥暟
+鈹� 鈹溾攢鈹� materials # UI缁勪欢鍜屾潗鏂�
+鈹� 鈹溾攢鈹� ofetch # 浣跨敤ofetch鐨凥TTP瀹㈡埛绔疄鐜�
+鈹� 鈹溾攢鈹� scripts # 鏋勫缓鍜屽紑鍙戣剼鏈�
+鈹� 鈹溾攢鈹� uno-preset # UnoCSS棰勮閰嶇疆
+鈹� 鈹斺攢鈹� utils # 閫氱敤宸ュ叿鍑芥暟
+鈹溾攢鈹� public # 闈欐�佽祫婧�
+鈹溾攢鈹� src # 涓诲簲鐢ㄦ簮浠g爜
+鈹� 鈹溾攢鈹� assets # 闈欐�佽祫婧�(鍥剧墖銆佸浘鏍�)
+鈹� 鈹溾攢鈹� components # 鍙鐢ㄧ殑 Vue 缁勪欢
+鈹� 鈹溾攢鈹� constants # 搴旂敤甯搁噺
+鈹� 鈹溾攢鈹� enum # TypeScript 鏋氫妇
+鈹� 鈹溾攢鈹� hooks # Vue 缁勫悎鍑芥暟
+鈹� 鈹溾攢鈹� layouts # 椤甸潰甯冨眬
+鈹� 鈹溾攢鈹� locales # 鍥介檯鍖�
+鈹� 鈹溾攢鈹� plugins # Vue 鎻掍欢
+鈹� 鈹溾攢鈹� router # Vue Router 閰嶇疆
+鈹� 鈹溾攢鈹� service # API 鏈嶅姟
+鈹� 鈹溾攢鈹� store # Pinia 瀛樺偍妯″潡
+鈹� 鈹溾攢鈹� styles # 鍏ㄥ眬鏍峰紡
+鈹� 鈹溾攢鈹� theme # 涓婚閰嶇疆
+鈹� 鈹溾攢鈹� typings # TypeScript 绫诲瀷瀹氫箟
+鈹� 鈹溾攢鈹� utils # 宸ュ叿鍑芥暟
+鈹� 鈹斺攢鈹� views # 椤甸潰缁勪欢
+鈹斺攢鈹� vite.config.ts # Vite 閰嶇疆
+```
+
+## 馃殌 鐜瑕佹眰涓庡畨瑁�
+
+### 鐜瑕佹眰
+- Node.js >= 20.19.0
+- pnpm >= 10.5.0
+- Git
+
+### 瀹夎姝ラ鍙婅鏄�
+
+1. 鍏嬮殕浠撳簱
+```bash
+git clone https://gitee.com/xlsea/ruoyi-plus-soybean.git
+cd ruoyi-plus-soybean
+```
+
+2. 瀹夎 pnpm (濡傛灉鏈畨瑁�)
+
+```bash
+npm install pnpm -g
+```
+
+璁剧疆娣樺疂闀滃儚
+```bash
+pnpm config set registry https://registry.npmmirror.com
+```
+
+3. 瀹夎渚濊禆
+```bash
+pnpm install
+```
+
+4. 杩愯寮�鍙戞湇鍔″櫒
+```bash
+pnpm dev
+```
+
+5. 鏋勫缓鐢熶骇鐗堟湰
+```bash
+pnpm build
+```
+
+### 浠g爜鐢熸垚涓庤彍鍗曟洿鏂�
+
+椤圭洰鎻愪緵浜嗕唬鐮佺敓鎴愬伐鍏峰拰鑿滃崟SQL鏇存柊鏂囦欢锛屽湪 <a href="https://gitee.com/xlsea/ruoyi-plus-soybean/tree/master/docs" target="_blank">docs</a> 鐩綍涓嬶細
+
+- **浠g爜鐢熸垚宸ュ叿**
+ - 浠g爜鐢熸垚宸ュ叿绫讳綅浜� `docs/java` 鐩綍锛屽鏋滄病鏈変慨鏀硅繃VelocityUtils.java鏂囦欢锛岀洿鎺ユ浛鎹㈠嵆鍙�
+ - 浠g爜鐢熸垚妯℃澘浣嶄簬 `docs/template` 鐩綍锛岃鍦╮uoyi-generator妯″潡鐨刞resource/vm`涓嬫柊寤� `soy`鏂囦欢澶癸紝骞跺皢鎵�鏈夋ā鏉挎嫹璐濊嚦`soy`鏂囦欢澶逛腑
+
+- **鑿滃崟SQL鏇存柊**
+ - 鑿滃崟鏁版嵁鏇存柊SQL鏂囦欢浣嶄簬 `docs/sql` 鐩綍
+ - 鍦ㄧ郴缁熷垵濮嬪寲鎴栨洿鏂版椂锛岄渶瑕佹墽琛岀浉搴旂殑SQL鏂囦欢鏉ユ洿鏂拌彍鍗曟暟鎹�
+
+## 馃摑 寮�鍙戞寚鍗�
+
+### 鍙敤鐨勮剼鏈懡浠�
+
+```bash
+# 寮�鍙戠幆澧�
+pnpm dev
+
+# 娴嬭瘯鐜
+pnpm dev:test
+
+# 鐢熶骇鐜
+pnpm dev:prod
+
+# 鏋勫缓鐢熶骇鐗堟湰
+pnpm build
+
+# 鏋勫缓寮�鍙戠増鏈�
+pnpm build:dev
+
+# 鏋勫缓娴嬭瘯鐗堟湰
+pnpm build:test
+
+# 棰勮鏋勫缓
+pnpm preview
+
+# 绫诲瀷妫�鏌�
+pnpm typecheck
+
+# 浠g爜瑙勮寖妫�鏌ュ苟淇
+pnpm lint
+
+# 璺敱鐢熸垚
+pnpm gen-route
+
+# 鎻愪氦浠g爜
+pnpm commit
+
+# 涓枃鎻愪氦淇℃伅
+pnpm commit:zh
+
+# 渚濊禆鍖呮洿鏂�
+pnpm update-pkg
+
+# 娓呯悊椤圭洰
+pnpm cleanup
+
+# 鍙戝竷鏂扮増鏈�
+pnpm release
+```
+
+### 浠g爜瑙勮寖涓庨鏍�
+
+椤圭洰浣跨敤ESLint杩涜浠g爜妫�鏌ワ紝閬靛惊浠ヤ笅瑙勮寖锛�
+
+- **鍛藉悕瑙勮寖**锛�
+ - Vue缁勪欢: PascalCase (濡� UserProfile.vue)
+ - TypeScript鏂囦欢: camelCase (濡� userService.ts)
+ - CSS/SCSS: kebab-case (濡� user-profile.scss)
+
+- **浠g爜椋庢牸**锛�
+ - 浣跨敤Vue 3 Composition API
+ - 浣跨敤TypeScript绫诲瀷绯荤粺
+ - 閬靛惊鍗曚竴鑱岃矗鍘熷垯
+
+### 鏍稿績寮�鍙戞ā寮�
+
+#### 鐘舵�佺鐞�
+浣跨敤Pinia杩涜鐘舵�佺鐞嗭紝妯″潡浣嶄簬`src/store/modules`鐩綍锛�
+- **app**: 搴旂敤鍏ㄥ眬鐘舵��
+- **theme**: 涓婚閰嶇疆
+- **route**: 璺敱淇℃伅
+- **tab**: 鏍囩椤电鐞�
+- **auth**: 璁よ瘉淇℃伅
+- **dict**: 瀛楀吀绠$悊
+- **notice**: 閫氱煡绠$悊
+
+#### API浜や簰
+椤圭洰鏀寔澶氱HTTP瀹㈡埛绔疄鐜帮細
+
+- **Axios**:
+```typescript
+import { useRequest } from '@/hooks/common/request';
+
+const { data, loading, error } = useRequest(() => api.getData(params));
+```
+
+- **Hooks浣跨敤**:
+```typescript
+// 甯冨皵鍊肩鐞�
+import { useBoolean } from '@sa/hooks';
+const { bool, setTrue, setFalse } = useBoolean();
+
+// 鍔犺浇鐘舵�佺鐞�
+import { useLoading } from '@sa/hooks';
+const { loading, startLoading, endLoading } = useLoading();
+
+// 琛ㄦ牸绠$悊
+import { useTable } from '@/hooks/common/table';
+const { tableData, loading, getPaginationData } = useTable(fetchTableData);
+```
+
+#### 缁勪欢浣跨敤
+椤圭洰鍖呭惈澶氱涓氬姟缁勪欢锛�
+
+- **琛ㄦ牸缁勪欢**锛氭敮鎸佸垪璁剧疆銆佹悳绱㈠尯鍩熷拰楂樼骇鎿嶄綔
+- **琛ㄥ崟缁勪欢**锛氶泦鎴愰獙璇佸拰琛ㄥ崟甯冨眬
+- **瀛楀吀缁勪欢**锛氬瓧鍏搁�夋嫨銆佹爣绛惧拰鍗曢��
+- **甯冨眬缁勪欢**锛氭敮鎸佸绉嶅竷灞�妯″紡鍜屼富棰�
+
+### UnoCSS浣跨敤鎸囧崡
+椤圭洰浼樺厛浣跨敤 UnoCSS 鏉ュ疄鐜版牱寮忥細
+
+```html
+<div class="flex flex-col items-center justify-center p-4 m-2 bg-blue-100 dark:bg-blue-800 rounded-md">
+ <span class="text-lg font-bold text-center">鍐呭</span>
+</div>
+```
+
+### 鍥介檯鍖�
+椤圭洰浣跨敤vue-i18n瀹炵幇鍥介檯鍖栨敮鎸侊細
+
+```typescript
+// 鍦ㄧ粍浠朵腑浣跨敤
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
+console.log(t('common.confirm'));
+```
+
+## 馃拵 鐗规�т笌鍔熻兘
+
+### 鍓嶇鐗规��
+- **澶氱甯冨眬妯″紡**锛氭敮鎸佸瀭鐩淬�佹按骞炽�佹贩鍚堢瓑澶氱甯冨眬
+- **鍙厤缃殑涓婚**锛氭槑鏆楁ā寮忋�佷富棰樿壊瀹氬埗
+- **鏍囩椤电鐞�**锛氬绉嶆爣绛鹃鏍笺�佸彸閿彍鍗�
+- **缁勪欢灏佽**锛氳繘搴︽潯銆佸浘鏍囥�佸姞杞藉姩鐢荤瓑
+- **璺敱鐢熸垚**锛氬熀浜庣洰褰曠粨鏋勭殑璺敱鐢熸垚
+- **鏉冮檺绠$悊**锛氳彍鍗曞拰鎸夐挳绾у埆鐨勬潈闄愭帶鍒�
+
+### 涓氬姟鍔熻兘
+- **鐢ㄦ埛绠$悊**锛氱敤鎴蜂俊鎭淮鎶ゃ�佽鑹插垎閰�
+- **瑙掕壊绠$悊**锛氳鑹叉潈闄愰厤缃�
+- **鑿滃崟绠$悊**锛氱郴缁熷姛鑳介厤缃�
+- **閮ㄩ棬绠$悊**锛氱粍缁囨灦鏋勭淮鎶�
+- **瀛楀吀绠$悊**锛氭暟鎹瓧鍏搁厤缃�
+- **绉熸埛绠$悊**锛氬绉熸埛閰嶇疆
+- **绯荤粺鐩戞帶**锛氱櫥褰曟棩蹇椼�佹搷浣滄棩蹇椼�佸湪绾跨敤鎴枫�佺紦瀛樼洃鎺�
+- **浠g爜鐢熸垚**锛氱敓鎴愬墠鍚庣浠g爜锛屾彁鍗囧紑鍙戞晥鐜�
+
+## 馃 璐$尞鎸囧崡
+
+### 寮�鍙戞祦绋�
+1. Fork椤圭洰
+2. 鍒涘缓鍔熻兘鍒嗘敮 (`git checkout -b feature/amazing-feature`)
+3. 鎻愪氦鏇存敼 (`git commit -m 'feat: add amazing feature'`)
+4. 鎺ㄩ�佸埌鍒嗘敮 (`git push origin feature/amazing-feature`)
+5. 鎻愪氦Pull Request
+
+### 鎻愪氦瑙勮寖
+椤圭洰浣跨敤绾﹀畾寮忔彁浜よ鑼冿細
+
+- `feat`: 鏂板姛鑳�
+- `fix`: 淇Bug
+- `docs`: 鏂囨。鏇存柊
+- `style`: 浠g爜椋庢牸璋冩暣
+- `refactor`: 浠g爜閲嶆瀯
+- `perf`: 鎬ц兘浼樺寲
+- `test`: 娴嬭瘯浠g爜
+- `chore`: 鏋勫缓鎴栧伐鍏峰彉鍔�
+
+## 馃搫 璁稿彲璇�
+
+[MIT License](./LICENSE)
+
+## 馃敆 鐩稿叧閾炬帴
+
+- [RuoYi-Vue-Plus](https://gitee.com/dromara/RuoYi-Vue-Plus) - 鍚庣鍩虹妗嗘灦
+- [Soybean Admin](https://github.com/soybeanjs/soybean-admin) - 鍓嶇鍩虹妗嗘灦
+- [RuoYi-Plus-Soybean](https://ruoyi.xlsea.cn) - 瀹樻柟婕旂ず绔欑偣
+- [RuoYi-Plus-Soybean-Docs](https://docs.ruoyi.xlsea.cn) - 椤圭洰鏂囨。
+- [Open Hives](https://openhives.com/questions) - OpenHives 闂瓟绀惧尯
+
+## 馃摦 鑱旂郴鏂瑰紡
+
+- **浣滆��**: xlsea
+- **閭**: m@xlsea.cn
+- **浣滆�呬富椤�**: https://gitee.com/xlsea
+
+鏇村鍛ㄨ竟鐢熸�佽缈婚槄 [鍛ㄨ竟鐢熸�乚(https://docs.soybeanjs.cn/zh/awesome) 鏂囨。銆�
+
+
+- **浣滆��**: Elio
+- **閭**: 1983933789@qq.com
+- **浣滆�呬富椤�**: https://gitee.com/ahcode
+
+## 馃挰 浜ゆ祦缇�
+
+**鍔犵兢鍓嶈鍏堥槄璇讳竴涓嬪唴瀹癸細**
+
+- 绂佹鍐呭锛氶粍鑵斻�佹毚鍔涜█璁恒�佹斂娌昏瘽棰橈紝杩濊�呯洿鎺ラ鏈虹エ锛堣涪鍑虹兢锛�
+- 閬囧埌闂璇峰厛闃呰 [椤圭洰鏂囨。](https://docs.ruoyi.xlsea.cn) 鍜� [Soybean 鏂囨。](https://docs.soybeanjs.cn/)锛屾煇浜涚畝鍗曢棶棰樹笉浜堢悊鐫�
+- 铚$瑪灏忔柊澶村儚涓烘満鍣ㄤ汉鍔╂墜锛岀鑱婁笉淇濊瘉鍥炲锛岄棶棰樿鍦ㄧ兢鍐呰璁�
+
+<img src="https://foruda.gitee.com/images/1749174520085305975/ad1b54fe_5601833.png" width="300px" />
+
+娣诲姞浣滆�呭井淇″娉細鍔犵兢
+
+## 馃Ё 鎹愮尞浣滆��
+
+浣滆�呬负鍏艰亴鍋氬紑婧愶紝骞虫椂杩橀渶瑕佸伐浣滐紝濡傛灉甯埌浜嗘偍鍙互璇蜂綔鑰呭悆涓洅楗�
+
+<img src="https://foruda.gitee.com/images/1746840166037207866/f8c6f06b_5601833.png" width="300px" height="300px" />
+
+## 馃 鎹愯禒鍒楄〃
+
+**鎹愯禒鍒楄〃宸茬Щ鑷� [鎹愯禒鍒楄〃](https://docs.ruoyi.xlsea.cn/other/donate.html)**
diff --git a/ruoyi-plus-soybean/build/config/index.ts b/ruoyi-plus-soybean/build/config/index.ts
new file mode 100755
index 0000000..8a9621a
--- /dev/null
+++ b/ruoyi-plus-soybean/build/config/index.ts
@@ -0,0 +1,2 @@
+export * from './proxy';
+export * from './time';
diff --git a/ruoyi-plus-soybean/build/config/proxy.ts b/ruoyi-plus-soybean/build/config/proxy.ts
new file mode 100755
index 0000000..f2a53ed
--- /dev/null
+++ b/ruoyi-plus-soybean/build/config/proxy.ts
@@ -0,0 +1,56 @@
+import type { ProxyOptions } from 'vite';
+import { bgRed, bgYellow, green, lightBlue } from 'kolorist';
+import { consola } from 'consola';
+import { createServiceConfig } from '../../src/utils/service';
+
+/**
+ * Set http proxy
+ *
+ * @param env - The current env
+ * @param enable - If enable http proxy
+ */
+export function createViteProxy(env: Env.ImportMeta, enable: boolean) {
+ const isEnableHttpProxy = enable && env.VITE_HTTP_PROXY === 'Y';
+
+ if (!isEnableHttpProxy) return undefined;
+
+ const isEnableProxyLog = env.VITE_PROXY_LOG === 'Y';
+
+ const { baseURL, proxyPattern, ws, other } = createServiceConfig(env);
+
+ const proxy: Record<string, ProxyOptions> = createProxyItem({ baseURL, ws, proxyPattern }, isEnableProxyLog);
+
+ other.forEach(item => {
+ Object.assign(proxy, createProxyItem(item, isEnableProxyLog));
+ });
+
+ return proxy;
+}
+
+function createProxyItem(item: App.Service.ServiceConfigItem, enableLog: boolean) {
+ const proxy: Record<string, ProxyOptions> = {};
+
+ proxy[item.proxyPattern] = {
+ target: item.baseURL,
+ changeOrigin: true,
+ ws: item.ws,
+ configure: (_proxy, options) => {
+ _proxy.on('proxyReq', (_proxyReq, req, _res) => {
+ if (!enableLog) return;
+
+ const requestUrl = `${lightBlue('[proxy url]')}: ${bgYellow(` ${req.method} `)} ${green(`${item.proxyPattern}${req.url}`)}`;
+
+ const proxyUrl = `${lightBlue('[real request url]')}: ${green(`${options.target}${req.url}`)}`;
+
+ consola.log(`\n${requestUrl}\n${proxyUrl}`);
+ });
+ _proxy.on('error', (_err, req, _res) => {
+ if (!enableLog) return;
+ consola.log(bgRed(`Error: ${req.method} `), green(`${options.target}${req.url}`));
+ });
+ },
+ rewrite: path => path.replace(new RegExp(`^${item.proxyPattern}`), '')
+ };
+
+ return proxy;
+}
diff --git a/ruoyi-plus-soybean/build/config/time.ts b/ruoyi-plus-soybean/build/config/time.ts
new file mode 100755
index 0000000..3b57146
--- /dev/null
+++ b/ruoyi-plus-soybean/build/config/time.ts
@@ -0,0 +1,12 @@
+import dayjs from 'dayjs';
+import utc from 'dayjs/plugin/utc';
+import timezone from 'dayjs/plugin/timezone';
+
+export function getBuildTime() {
+ dayjs.extend(utc);
+ dayjs.extend(timezone);
+
+ const buildTime = dayjs.tz(Date.now(), 'Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss');
+
+ return buildTime;
+}
diff --git a/ruoyi-plus-soybean/build/plugins/devtools.ts b/ruoyi-plus-soybean/build/plugins/devtools.ts
new file mode 100755
index 0000000..34c08d1
--- /dev/null
+++ b/ruoyi-plus-soybean/build/plugins/devtools.ts
@@ -0,0 +1,9 @@
+import VueDevtools from 'vite-plugin-vue-devtools';
+
+export function setupDevtoolsPlugin(viteEnv: Env.ImportMeta) {
+ const { VITE_DEVTOOLS_LAUNCH_EDITOR } = viteEnv;
+
+ return VueDevtools({
+ launchEditor: VITE_DEVTOOLS_LAUNCH_EDITOR
+ });
+}
diff --git a/ruoyi-plus-soybean/build/plugins/html.ts b/ruoyi-plus-soybean/build/plugins/html.ts
new file mode 100755
index 0000000..b94d24f
--- /dev/null
+++ b/ruoyi-plus-soybean/build/plugins/html.ts
@@ -0,0 +1,13 @@
+import type { Plugin } from 'vite';
+
+export function setupHtmlPlugin(buildTime: string) {
+ const plugin: Plugin = {
+ name: 'html-plugin',
+ apply: 'build',
+ transformIndexHtml(html) {
+ return html.replace('<head>', `<head>\n <meta name="buildTime" content="${buildTime}">`);
+ }
+ };
+
+ return plugin;
+}
diff --git a/ruoyi-plus-soybean/build/plugins/index.ts b/ruoyi-plus-soybean/build/plugins/index.ts
new file mode 100755
index 0000000..1243fc2
--- /dev/null
+++ b/ruoyi-plus-soybean/build/plugins/index.ts
@@ -0,0 +1,24 @@
+import type { PluginOption } from 'vite';
+import vue from '@vitejs/plugin-vue';
+import vueJsx from '@vitejs/plugin-vue-jsx';
+import progress from 'vite-plugin-progress';
+import { setupElegantRouter } from './router';
+import { setupUnocss } from './unocss';
+import { setupUnplugin } from './unplugin';
+import { setupHtmlPlugin } from './html';
+import { setupDevtoolsPlugin } from './devtools';
+
+export function setupVitePlugins(viteEnv: Env.ImportMeta, buildTime: string) {
+ const plugins: PluginOption = [
+ vue(),
+ vueJsx(),
+ setupDevtoolsPlugin(viteEnv),
+ setupElegantRouter(),
+ setupUnocss(viteEnv),
+ ...setupUnplugin(viteEnv),
+ progress(),
+ setupHtmlPlugin(buildTime)
+ ];
+
+ return plugins;
+}
diff --git a/ruoyi-plus-soybean/build/plugins/router.ts b/ruoyi-plus-soybean/build/plugins/router.ts
new file mode 100755
index 0000000..952e15d
--- /dev/null
+++ b/ruoyi-plus-soybean/build/plugins/router.ts
@@ -0,0 +1,44 @@
+import type { RouteMeta } from 'vue-router';
+import ElegantVueRouter from '@elegant-router/vue/vite';
+import type { RouteKey } from '@elegant-router/types';
+
+export function setupElegantRouter() {
+ return ElegantVueRouter({
+ layouts: {
+ base: 'src/layouts/base-layout/index.vue',
+ blank: 'src/layouts/blank-layout/index.vue'
+ },
+ customRoutes: {
+ names: ['exception_403', 'exception_404', 'exception_500']
+ },
+ routePathTransformer(routeName, routePath) {
+ const key = routeName as RouteKey;
+
+ if (key === 'login') {
+ const modules: UnionKey.LoginModule[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat'];
+
+ const moduleReg = modules.join('|');
+
+ return `/login/:module(${moduleReg})?`;
+ }
+
+ return routePath;
+ },
+ onRouteMetaGen(routeName) {
+ const key = routeName as RouteKey;
+
+ const constantRoutes: RouteKey[] = ['login', '403', '404', '500'];
+
+ const meta: Partial<RouteMeta> = {
+ title: key,
+ i18nKey: `route.${key}` as App.I18n.I18nKey
+ };
+
+ if (constantRoutes.includes(key)) {
+ meta.constant = true;
+ }
+
+ return meta;
+ }
+ });
+}
diff --git a/ruoyi-plus-soybean/build/plugins/unocss.ts b/ruoyi-plus-soybean/build/plugins/unocss.ts
new file mode 100755
index 0000000..06b41d3
--- /dev/null
+++ b/ruoyi-plus-soybean/build/plugins/unocss.ts
@@ -0,0 +1,32 @@
+import process from 'node:process';
+import path from 'node:path';
+import unocss from '@unocss/vite';
+import presetIcons from '@unocss/preset-icons';
+import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders';
+
+export function setupUnocss(viteEnv: Env.ImportMeta) {
+ const { VITE_ICON_PREFIX, VITE_ICON_LOCAL_PREFIX } = viteEnv;
+
+ const localIconPath = path.join(process.cwd(), 'src/assets/svg-icon');
+
+ /** The name of the local icon collection */
+ const collectionName = VITE_ICON_LOCAL_PREFIX.replace(`${VITE_ICON_PREFIX}-`, '');
+
+ return unocss({
+ presets: [
+ presetIcons({
+ prefix: `${VITE_ICON_PREFIX}-`,
+ scale: 1,
+ extraProperties: {
+ display: 'inline-block'
+ },
+ collections: {
+ [collectionName]: FileSystemIconLoader(localIconPath, svg =>
+ svg.replace(/^<svg\s/, '<svg width="1em" height="1em" ')
+ )
+ },
+ warn: true
+ })
+ ]
+ });
+}
diff --git a/ruoyi-plus-soybean/build/plugins/unplugin.ts b/ruoyi-plus-soybean/build/plugins/unplugin.ts
new file mode 100755
index 0000000..2055c4f
--- /dev/null
+++ b/ruoyi-plus-soybean/build/plugins/unplugin.ts
@@ -0,0 +1,47 @@
+import process from 'node:process';
+import path from 'node:path';
+import type { PluginOption } from 'vite';
+import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
+import Icons from 'unplugin-icons/vite';
+import IconsResolver from 'unplugin-icons/resolver';
+import Components from 'unplugin-vue-components/vite';
+import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
+import { FileSystemIconLoader } from 'unplugin-icons/loaders';
+
+export function setupUnplugin(viteEnv: Env.ImportMeta) {
+ const { VITE_ICON_PREFIX, VITE_ICON_LOCAL_PREFIX } = viteEnv;
+
+ const localIconPath = path.join(process.cwd(), 'src/assets/svg-icon');
+
+ /** The name of the local icon collection */
+ const collectionName = VITE_ICON_LOCAL_PREFIX.replace(`${VITE_ICON_PREFIX}-`, '');
+
+ const plugins: PluginOption[] = [
+ Icons({
+ compiler: 'vue3',
+ customCollections: {
+ [collectionName]: FileSystemIconLoader(localIconPath, svg =>
+ svg.replace(/^<svg\s/, '<svg width="1em" height="1em" ')
+ )
+ },
+ scale: 1,
+ defaultClass: 'inline-block'
+ }),
+ Components({
+ dts: 'src/typings/components.d.ts',
+ types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }],
+ resolvers: [
+ NaiveUiResolver(),
+ IconsResolver({ customCollections: [collectionName], componentPrefix: VITE_ICON_PREFIX })
+ ]
+ }),
+ createSvgIconsPlugin({
+ iconDirs: [localIconPath],
+ symbolId: `${VITE_ICON_LOCAL_PREFIX}-[dir]-[name]`,
+ inject: 'body-last',
+ customDomId: '__SVG_ICON_LOCAL__'
+ })
+ ];
+
+ return plugins;
+}
diff --git a/ruoyi-plus-soybean/docs/README.md b/ruoyi-plus-soybean/docs/README.md
new file mode 100755
index 0000000..60c21d0
--- /dev/null
+++ b/ruoyi-plus-soybean/docs/README.md
@@ -0,0 +1,11 @@
+## java
+
+鍚庣浠g爜鐢熸垚宸ュ叿绫绘浛鎹㈡枃浠�
+
+## template
+
+浠g爜鐢熸垚妯℃澘鏂囦欢
+
+## sql
+
+鑿滃崟鏁版嵁鏇挎崲 SQL
diff --git a/ruoyi-plus-soybean/docs/java/VelocityUtils.java b/ruoyi-plus-soybean/docs/java/VelocityUtils.java
new file mode 100755
index 0000000..b9a50ce
--- /dev/null
+++ b/ruoyi-plus-soybean/docs/java/VelocityUtils.java
@@ -0,0 +1,389 @@
+package org.dromara.generator.util;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.StrUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.velocity.VelocityContext;
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.mybatis.enums.DataBaseType;
+import org.dromara.common.mybatis.helper.DataBaseHelper;
+import org.dromara.generator.constant.GenConstants;
+import org.dromara.generator.domain.GenTable;
+import org.dromara.generator.domain.GenTableColumn;
+
+import java.util.*;
+
+/**
+ * 妯℃澘澶勭悊宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class VelocityUtils {
+
+ /**
+ * 椤圭洰绌洪棿璺緞
+ */
+ private static final String PROJECT_PATH = "main/java";
+
+ /**
+ * mybatis绌洪棿璺緞
+ */
+ private static final String MYBATIS_PATH = "main/resources/mapper";
+
+ /**
+ * 榛樿涓婄骇鑿滃崟锛岀郴缁熷伐鍏�
+ */
+ private static final String DEFAULT_PARENT_MENU_ID = "3";
+
+ /**
+ * 璁剧疆妯℃澘鍙橀噺淇℃伅
+ *
+ * @return 妯℃澘鍒楄〃
+ */
+ public static VelocityContext prepareContext(GenTable genTable) {
+ String moduleName = genTable.getModuleName();
+ String businessName = genTable.getBusinessName();
+ String packageName = genTable.getPackageName();
+ String tplCategory = genTable.getTplCategory();
+ String functionName = genTable.getFunctionName();
+
+ VelocityContext velocityContext = new VelocityContext();
+ velocityContext.put("tplCategory", genTable.getTplCategory());
+ velocityContext.put("tableName", genTable.getTableName());
+ velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "銆愯濉啓鍔熻兘鍚嶇О銆�");
+ velocityContext.put("ClassName", genTable.getClassName());
+ velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
+ velocityContext.put("moduleName", StrUtil.toSymbolCase(genTable.getModuleName(), '-'));
+ velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
+ velocityContext.put("businessName", genTable.getBusinessName());
+ velocityContext.put("business_name", StrUtil.toUnderlineCase(genTable.getBusinessName()));
+ velocityContext.put("business__name", StrUtil.toSymbolCase(genTable.getBusinessName(), '-'));
+ velocityContext.put("businessname", StrUtil.toSymbolCase(genTable.getBusinessName(), ' '));
+ velocityContext.put("basePackage", getPackagePrefix(packageName));
+ velocityContext.put("packageName", packageName);
+ velocityContext.put("author", genTable.getFunctionAuthor());
+ velocityContext.put("datetime", DateUtils.getDate());
+ velocityContext.put("pkColumn", genTable.getPkColumn());
+ velocityContext.put("importList", getImportList(genTable));
+ velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
+ velocityContext.put("dicts", getDicts(genTable));
+ velocityContext.put("dictList", getDictList(genTable));
+ velocityContext.put("pkColumn", genTable.getPkColumn());
+ velocityContext.put("columns", genTable.getColumns());
+ velocityContext.put("table", genTable);
+ velocityContext.put("StrUtil", new StrUtil());
+ setMenuVelocityContext(velocityContext, genTable);
+ if (GenConstants.TPL_TREE.equals(tplCategory)) {
+ setTreeVelocityContext(velocityContext, genTable);
+ }
+ return velocityContext;
+ }
+
+ public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) {
+ String options = genTable.getOptions();
+ Dict paramsObj = JsonUtils.parseMap(options);
+ String parentMenuId = getParentMenuId(paramsObj);
+ context.put("parentMenuId", parentMenuId);
+ }
+
+ public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) {
+ String options = genTable.getOptions();
+ Dict paramsObj = JsonUtils.parseMap(options);
+ String treeCode = getTreecode(paramsObj);
+ String treeParentCode = getTreeParentCode(paramsObj);
+ String treeName = getTreeName(paramsObj);
+
+ context.put("treeCode", treeCode);
+ context.put("treeParentCode", treeParentCode);
+ context.put("treeName", treeName);
+ context.put("expandColumn", getExpandColumn(genTable));
+ if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
+ context.put("tree_parent_code", paramsObj.get(GenConstants.TREE_PARENT_CODE));
+ }
+ if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
+ context.put("tree_name", paramsObj.get(GenConstants.TREE_NAME));
+ }
+ }
+
+ /**
+ * 鑾峰彇妯℃澘淇℃伅
+ *
+ * @return 妯℃澘鍒楄〃
+ */
+ public static List<String> getTemplateList(String tplCategory) {
+ List<String> templates = new ArrayList<>();
+ templates.add("vm/java/domain.java.vm");
+ templates.add("vm/java/vo.java.vm");
+ templates.add("vm/java/bo.java.vm");
+ templates.add("vm/java/mapper.java.vm");
+ templates.add("vm/java/service.java.vm");
+ templates.add("vm/java/serviceImpl.java.vm");
+ templates.add("vm/java/controller.java.vm");
+ templates.add("vm/xml/mapper.xml.vm");
+ DataBaseType dataBaseType = DataBaseHelper.getDataBaseType();
+ if (dataBaseType.isOracle()) {
+ templates.add("vm/sql/oracle/sql.vm");
+ } else if (dataBaseType.isPostgreSql()) {
+ templates.add("vm/sql/postgres/sql.vm");
+ } else if (dataBaseType.isSqlServer()) {
+ templates.add("vm/sql/sqlserver/sql.vm");
+ } else {
+ templates.add("vm/sql/sql.vm");
+ }
+ templates.add("vm/soy/typings/api.d.ts.vm");
+ templates.add("vm/soy/api/api.ts.vm");
+ templates.add("vm/soy/modules/search.vue.vm");
+ templates.add("vm/soy/modules/operate-drawer.vue.vm");
+ if (GenConstants.TPL_CRUD.equals(tplCategory)) {
+ templates.add("vm/soy/index.vue.vm");
+ } else if (GenConstants.TPL_TREE.equals(tplCategory)) {
+ templates.add("vm/soy/index-tree.vue.vm");
+ }
+ return templates;
+ }
+
+ /**
+ * 鑾峰彇鏂囦欢鍚�
+ */
+ public static String getFileName(String template, GenTable genTable) {
+ // 鏂囦欢鍚嶇О
+ String fileName = "";
+ // 鍖呰矾寰�
+ String packageName = genTable.getPackageName();
+ // 妯″潡鍚�
+ String moduleName = genTable.getModuleName();
+ // 澶у啓绫诲悕
+ String className = genTable.getClassName();
+ // 涓氬姟鍚嶇О
+ String businessName = genTable.getBusinessName();
+
+ String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
+ String mybatisPath = MYBATIS_PATH + "/" + moduleName;
+ String soybeanPath = "soy";
+ String soybeanModuleName = StrUtil.toSymbolCase(moduleName, '-');
+ if (template.contains("domain.java.vm")) {
+ fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
+ }
+ if (template.contains("vo.java.vm")) {
+ fileName = StringUtils.format("{}/domain/vo/{}Vo.java", javaPath, className);
+ }
+ if (template.contains("bo.java.vm")) {
+ fileName = StringUtils.format("{}/domain/bo/{}Bo.java", javaPath, className);
+ }
+ if (template.contains("mapper.java.vm")) {
+ fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className);
+ } else if (template.contains("service.java.vm")) {
+ fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className);
+ } else if (template.contains("serviceImpl.java.vm")) {
+ fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className);
+ } else if (template.contains("controller.java.vm")) {
+ fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className);
+ } else if (template.contains("mapper.xml.vm")) {
+ fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className);
+ } else if (template.contains("sql.vm")) {
+ fileName = businessName + "Menu.sql";
+ } else if (template.contains("index.vue.vm")) {
+ fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
+ } else if (template.contains("index-tree.vue.vm")) {
+ fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
+ } else if (template.contains("api.d.ts.vm")) {
+ fileName = StringUtils.format("{}/typings/api/{}.{}.api.d.ts", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
+ } else if (template.contains("api.ts.vm")) {
+ fileName = StringUtils.format("{}/service/api/{}/{}.ts", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
+ } else if (template.contains("search.vue.vm")) {
+ fileName = StringUtils.format("{}/views/{}/{}/modules/{}-search.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
+ } else if (template.contains("operate-drawer.vue.vm")) {
+ fileName = StringUtils.format("{}/views/{}/{}/modules/{}-operate-drawer.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
+ }
+ return fileName;
+ }
+
+ /**
+ * 鑾峰彇鍖呭墠缂�
+ *
+ * @param packageName 鍖呭悕绉�
+ * @return 鍖呭墠缂�鍚嶇О
+ */
+ public static String getPackagePrefix(String packageName) {
+ int lastIndex = packageName.lastIndexOf(".");
+ return StringUtils.substring(packageName, 0, lastIndex);
+ }
+
+ /**
+ * 鏍规嵁鍒楃被鍨嬭幏鍙栧鍏ュ寘
+ *
+ * @param genTable 涓氬姟琛ㄥ璞�
+ * @return 杩斿洖闇�瑕佸鍏ョ殑鍖呭垪琛�
+ */
+ public static HashSet<String> getImportList(GenTable genTable) {
+ List<GenTableColumn> columns = genTable.getColumns();
+ HashSet<String> importList = new HashSet<>();
+ for (GenTableColumn column : columns) {
+ if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) {
+ importList.add("java.util.Date");
+ importList.add("com.fasterxml.jackson.annotation.JsonFormat");
+ } else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
+ importList.add("java.math.BigDecimal");
+ } else if (!column.isSuperColumn() && "imageUpload".equals(column.getHtmlType())) {
+ importList.add("org.dromara.common.translation.annotation.Translation");
+ importList.add("org.dromara.common.translation.constant.TransConstant");
+ }
+ }
+ return importList;
+ }
+
+ /**
+ * 鏍规嵁鍒楃被鍨嬭幏鍙栧瓧鍏哥粍
+ *
+ * @param genTable 涓氬姟琛ㄥ璞�
+ * @return 杩斿洖瀛楀吀缁�
+ */
+ public static String getDicts(GenTable genTable) {
+ List<GenTableColumn> columns = genTable.getColumns();
+ Set<String> dicts = new HashSet<>();
+ addDicts(dicts, columns);
+ return StringUtils.join(dicts, ", ");
+ }
+
+ /**
+ * 娣诲姞瀛楀吀鍒楄〃
+ *
+ * @param dicts 瀛楀吀鍒楄〃
+ * @param columns 鍒楅泦鍚�
+ */
+ public static void addDicts(Set<String> dicts, List<GenTableColumn> columns) {
+ for (GenTableColumn column : columns) {
+ if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
+ column.getHtmlType(),
+ GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX)) {
+ dicts.add("'" + column.getDictType() + "'");
+ }
+ }
+ }
+
+ /**
+ * 鏍规嵁鍒楃被鍨嬭幏鍙栧瓧鍏哥粍
+ *
+ * @param genTable 涓氬姟琛ㄥ璞�
+ * @return 杩斿洖瀛楀吀缁�
+ */
+ public static Set<Map<String, Object>> getDictList(GenTable genTable) {
+ List<GenTableColumn> columns = genTable.getColumns();
+ Set<Map<String, Object>> dicts = new HashSet<>();
+ addDictList(dicts, columns);
+ return dicts;
+ }
+
+ /**
+ * 娣诲姞瀛楀吀鍒楄〃
+ *
+ * @param dicts 瀛楀吀鍒楄〃
+ * @param columns 鍒楅泦鍚�
+ */
+ public static void addDictList(Set<Map<String, Object>> dicts, List<GenTableColumn> columns) {
+ for (GenTableColumn column : columns) {
+ if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
+ column.getHtmlType(),
+ GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX)) {
+ Map<String, Object> dict = new HashMap<>();
+ dict.put("type", column.getDictType());
+ dict.put("name", StringUtils.toCamelCase(column.getDictType()));
+ dict.put("immediate", column.isList());
+ dicts.add(dict);
+ }
+ }
+ }
+
+ /**
+ * 鑾峰彇鏉冮檺鍓嶇紑
+ *
+ * @param moduleName 妯″潡鍚嶇О
+ * @param businessName 涓氬姟鍚嶇О
+ * @return 杩斿洖鏉冮檺鍓嶇紑
+ */
+ public static String getPermissionPrefix(String moduleName, String businessName) {
+ return StringUtils.format("{}:{}", moduleName, businessName);
+ }
+
+ /**
+ * 鑾峰彇涓婄骇鑿滃崟ID瀛楁
+ *
+ * @param paramsObj 鐢熸垚鍏朵粬閫夐」
+ * @return 涓婄骇鑿滃崟ID瀛楁
+ */
+ public static String getParentMenuId(Dict paramsObj) {
+ if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
+ && StringUtils.isNotEmpty(paramsObj.getStr(GenConstants.PARENT_MENU_ID))) {
+ return paramsObj.getStr(GenConstants.PARENT_MENU_ID);
+ }
+ return DEFAULT_PARENT_MENU_ID;
+ }
+
+ /**
+ * 鑾峰彇鏍戠紪鐮�
+ *
+ * @param paramsObj 鐢熸垚鍏朵粬閫夐」
+ * @return 鏍戠紪鐮�
+ */
+ public static String getTreecode(Map<String, Object> paramsObj) {
+ if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_CODE)) {
+ return StringUtils.toCamelCase(Convert.toStr(paramsObj.get(GenConstants.TREE_CODE)));
+ }
+ return StringUtils.EMPTY;
+ }
+
+ /**
+ * 鑾峰彇鏍戠埗缂栫爜
+ *
+ * @param paramsObj 鐢熸垚鍏朵粬閫夐」
+ * @return 鏍戠埗缂栫爜
+ */
+ public static String getTreeParentCode(Dict paramsObj) {
+ if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
+ return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_PARENT_CODE));
+ }
+ return StringUtils.EMPTY;
+ }
+
+ /**
+ * 鑾峰彇鏍戝悕绉�
+ *
+ * @param paramsObj 鐢熸垚鍏朵粬閫夐」
+ * @return 鏍戝悕绉�
+ */
+ public static String getTreeName(Dict paramsObj) {
+ if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_NAME)) {
+ return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_NAME));
+ }
+ return StringUtils.EMPTY;
+ }
+
+ /**
+ * 鑾峰彇闇�瑕佸湪鍝竴鍒椾笂闈㈡樉绀哄睍寮�鎸夐挳
+ *
+ * @param genTable 涓氬姟琛ㄥ璞�
+ * @return 灞曞紑鎸夐挳鍒楀簭鍙�
+ */
+ public static int getExpandColumn(GenTable genTable) {
+ String options = genTable.getOptions();
+ Dict paramsObj = JsonUtils.parseMap(options);
+ String treeName = paramsObj.getStr(GenConstants.TREE_NAME);
+ int num = 0;
+ for (GenTableColumn column : genTable.getColumns()) {
+ if (column.isList()) {
+ num++;
+ String columnName = column.getColumnName();
+ if (columnName.equals(treeName)) {
+ break;
+ }
+ }
+ }
+ return num;
+ }
+}
diff --git a/ruoyi-plus-soybean/docs/sql/sys_dict_data.sql b/ruoyi-plus-soybean/docs/sql/sys_dict_data.sql
new file mode 100755
index 0000000..7f61ef4
--- /dev/null
+++ b/ruoyi-plus-soybean/docs/sql/sys_dict_data.sql
@@ -0,0 +1,59 @@
+-- 淇敼瀛楀吀鏁版嵁琛ㄧ殑 list_class 瀛楁锛屽皢 danger 鏀逛负 error
+UPDATE `sys_dict_data` SET `list_class` = 'error' WHERE `list_class` = 'danger';
+
+-- 瀛楀吀閫傞厤澶氳瑷�
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_user_sex.male', `dict_type` = 'sys_user_sex' WHERE `dict_code` = 1;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_user_sex.female', `dict_type` = 'sys_user_sex' WHERE `dict_code` = 2;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_user_sex.unknown', `dict_type` = 'sys_user_sex' WHERE `dict_code` = 3;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_show_hide.show', `dict_type` = 'sys_show_hide' WHERE `dict_code` = 4;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_show_hide.hide', `dict_type` = 'sys_show_hide' WHERE `dict_code` = 5;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_normal_disable.normal', `dict_type` = 'sys_normal_disable' WHERE `dict_code` = 6;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_normal_disable.disable', `dict_type` = 'sys_normal_disable' WHERE `dict_code` = 7;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_yes_no.yes', `dict_type` = 'sys_yes_no' WHERE `dict_code` = 12;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_yes_no.no', `dict_type` = 'sys_yes_no' WHERE `dict_code` = 13;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_notice_type.notice', `dict_type` = 'sys_notice_type' WHERE `dict_code` = 14;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_notice_type.announcement', `dict_type` = 'sys_notice_type' WHERE `dict_code` = 15;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_notice_status.normal', `dict_type` = 'sys_notice_status' WHERE `dict_code` = 16;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_notice_status.close', `dict_type` = 'sys_notice_status' WHERE `dict_code` = 17;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_oper_type.insert', `dict_type` = 'sys_oper_type' WHERE `dict_code` = 18;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_oper_type.update', `dict_type` = 'sys_oper_type' WHERE `dict_code` = 19;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_oper_type.delete', `dict_type` = 'sys_oper_type' WHERE `dict_code` = 20;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_oper_type.grant', `dict_type` = 'sys_oper_type' WHERE `dict_code` = 21;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_oper_type.export', `dict_type` = 'sys_oper_type' WHERE `dict_code` = 22;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_oper_type.import', `dict_type` = 'sys_oper_type' WHERE `dict_code` = 23;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_oper_type.force', `dict_type` = 'sys_oper_type' WHERE `dict_code` = 24;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_oper_type.gencode', `dict_type` = 'sys_oper_type' WHERE `dict_code` = 25;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_oper_type.clean', `dict_type` = 'sys_oper_type' WHERE `dict_code` = 26;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_common_status.success', `dict_type` = 'sys_common_status' WHERE `dict_code` = 27;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_common_status.fail', `dict_type` = 'sys_common_status' WHERE `dict_code` = 28;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_oper_type.other', `dict_type` = 'sys_oper_type' WHERE `dict_code` = 29;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_grant_type.password', `dict_type` = 'sys_grant_type' WHERE `dict_code` = 30;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_grant_type.sms', `dict_type` = 'sys_grant_type' WHERE `dict_code` = 31;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_grant_type.email', `dict_type` = 'sys_grant_type' WHERE `dict_code` = 32;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_grant_type.miniapp', `dict_type` = 'sys_grant_type' WHERE `dict_code` = 33;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_grant_type.social', `dict_type` = 'sys_grant_type' WHERE `dict_code` = 34;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_device_type.pc', `dict_type` = 'sys_device_type' WHERE `dict_code` = 35;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_device_type.android', `dict_type` = 'sys_device_type' WHERE `dict_code` = 36;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_device_type.ios', `dict_type` = 'sys_device_type' WHERE `dict_code` = 37;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.sys_device_type.miniapp', `dict_type` = 'sys_device_type' WHERE `dict_code` = 38;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_business_status.revoked', `dict_type` = 'wf_business_status' WHERE `dict_code` = 39;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_business_status.draft', `dict_type` = 'wf_business_status' WHERE `dict_code` = 40;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_business_status.pending', `dict_type` = 'wf_business_status' WHERE `dict_code` = 41;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_business_status.completed', `dict_type` = 'wf_business_status' WHERE `dict_code` = 42;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_business_status.cancelled', `dict_type` = 'wf_business_status' WHERE `dict_code` = 43;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_business_status.returned', `dict_type` = 'wf_business_status' WHERE `dict_code` = 44;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_business_status.terminated', `dict_type` = 'wf_business_status' WHERE `dict_code` = 45;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_form_type.custom_form', `dict_type` = 'wf_form_type' WHERE `dict_code` = 46;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_form_type.dynamic_form', `dict_type` = 'wf_form_type' WHERE `dict_code` = 47;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_task_status.revoke', `dict_type` = 'wf_task_status' WHERE `dict_code` = 48;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_task_status.pass', `dict_type` = 'wf_task_status' WHERE `dict_code` = 49;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_task_status.pending_review', `dict_type` = 'wf_task_status' WHERE `dict_code` = 50;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_task_status.cancel', `dict_type` = 'wf_task_status' WHERE `dict_code` = 51;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_task_status.return', `dict_type` = 'wf_task_status' WHERE `dict_code` = 52;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_task_status.terminate', `dict_type` = 'wf_task_status' WHERE `dict_code` = 53;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_task_status.transfer', `dict_type` = 'wf_task_status' WHERE `dict_code` = 54;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_task_status.delegate', `dict_type` = 'wf_task_status' WHERE `dict_code` = 55;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_task_status.copy', `dict_type` = 'wf_task_status' WHERE `dict_code` = 56;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_task_status.add_sign', `dict_type` = 'wf_task_status' WHERE `dict_code` = 57;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_task_status.minus_sign', `dict_type` = 'wf_task_status' WHERE `dict_code` = 58;
+UPDATE `sys_dict_data` SET `dict_label` = 'dict.wf_task_status.timeout', `dict_type` = 'wf_task_status' WHERE `dict_code` = 59;
diff --git a/ruoyi-plus-soybean/docs/sql/sys_menu.sql b/ruoyi-plus-soybean/docs/sql/sys_menu.sql
new file mode 100755
index 0000000..ec5f107
--- /dev/null
+++ b/ruoyi-plus-soybean/docs/sql/sys_menu.sql
@@ -0,0 +1,43 @@
+-- 鐩綍绫诲瀷鑿滃崟
+UPDATE `sys_menu` SET `component` = 'Layout', `icon` = 'carbon:cloud-service-management', `menu_name` = 'route.system' WHERE `menu_id` = 1;
+UPDATE `sys_menu` SET `component` = 'Layout', `icon` = 'stash:dashboard', `menu_name` = 'route.monitor' WHERE `menu_id` = 2;
+UPDATE `sys_menu` SET `component` = 'Layout', `icon` = 'tabler:tools', `menu_name` = 'route.tool' WHERE `menu_id` = 3;
+UPDATE `sys_menu` SET `component` = 'Layout', `icon` = 'material-symbols:kid-star-outline', `menu_name` = 'route.demo' WHERE `menu_id` = 5;
+UPDATE `sys_menu` SET `component` = 'Layout', `icon` = 'tabler:building-cog', `menu_name` = 'menu.system_tenant' WHERE `menu_id` = 6;
+UPDATE `sys_menu` SET `component` = 'Layout', `icon` = 'tabler:logs', `menu_name` = 'menu.system_log' WHERE `menu_id` = 108;
+
+-- 椤甸潰绫诲瀷
+UPDATE `sys_menu` SET `icon` = 'ic:round-manage-accounts', `menu_name` = 'route.system_user' WHERE `menu_id` = 100;
+UPDATE `sys_menu` SET `icon` = 'carbon:user-role', `menu_name` = 'route.system_role' WHERE `menu_id` = 101;
+UPDATE `sys_menu` SET `icon` = 'material-symbols:route', `menu_name` = 'route.system_menu' WHERE `menu_id` = 102;
+UPDATE `sys_menu` SET `icon` = 'mingcute:department-line', `menu_name` = 'route.system_dept' WHERE `menu_id` = 103;
+UPDATE `sys_menu` SET `icon` = 'hugeicons:permanent-job', `menu_name` = 'route.system_post' WHERE `menu_id` = 104;
+UPDATE `sys_menu` SET `icon` = 'qlementine-icons:dictionary-16', `menu_name` = 'route.system_dict' WHERE `menu_id` = 105;
+UPDATE `sys_menu` SET `icon` = 'carbon:parameter', `menu_name` = 'route.system_config' WHERE `menu_id` = 106;
+UPDATE `sys_menu` SET `icon` = 'solar:chat-line-outline', `menu_name` = 'route.system_notice' WHERE `menu_id` = 107;
+UPDATE `sys_menu` SET `icon` = 'majesticons:status-online-line', `menu_name` = 'route.monitor_online' WHERE `menu_id` = 109;
+UPDATE `sys_menu` SET `icon` = 'simple-icons:redis', `menu_name` = 'route.monitor_cache' WHERE `menu_id` = 113;
+UPDATE `sys_menu` SET `icon` = 'material-symbols:code-blocks-outline', `menu_name` = 'route.tool_gen' WHERE `menu_id` = 115;
+UPDATE `sys_menu` SET `icon` = 'material-symbols:attach-file', `menu_name` = 'route.system_oss' WHERE `menu_id` = 118;
+UPDATE `sys_menu` SET `icon` = 'tabler:building-skyscraper', `menu_name` = 'route.system_tenant' WHERE `menu_id` = 121;
+UPDATE `sys_menu` SET `icon` = 'lets-icons:package-box-alt', `menu_name` = 'route.system_tenant-package' WHERE `menu_id` = 122;
+UPDATE `sys_menu` SET `icon` = 'tabler:device-imac-cog', `menu_name` = 'route.system_client' WHERE `menu_id` = 123;
+UPDATE `sys_menu` SET `icon` = 'carbon:operations-record', `menu_name` = 'route.monitor_operlog' WHERE `menu_id` = 500;
+UPDATE `sys_menu` SET `icon` = 'tabler:login-2', `menu_name` = 'route.monitor_logininfor' WHERE `menu_id` = 501;
+UPDATE `sys_menu` SET `icon` = 'gg:debug', `menu_name` = 'route.demo_demo' WHERE `menu_id` = 1500;
+UPDATE `sys_menu` SET `icon` = 'gg:debug', `menu_name` = 'route.demo_tree' WHERE `menu_id` = 1506;
+UPDATE `sys_menu` SET `path` = 'oss/config', `component` = 'system/oss-config/index', `icon` = 'hugeicons:configuration-01', `menu_name` = 'route.system_oss-config' WHERE `menu_id` = 133;
+INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (9, 'route.about', 0, 99, 'about', 'about/index', '', 1, 1, 'C', '0', '0', '', 'fluent:book-information-24-regular', 103, 1, sysdate(), null, null, '鍏充簬椤甸潰') ON DUPLICATE KEY UPDATE `update_time` = sysdate();
+
+-- IFrame 绫诲瀷
+UPDATE `sys_menu` SET `component` = 'FrameView', `query_param` = '{"url": "https://ruoyi.xlsea.cn/admin/"}', `is_frame` = 2, `icon` = 'bx:bxl-spring-boot', `menu_name` = 'menu.monitor_admin' WHERE `menu_id` = 117;
+UPDATE `sys_menu` SET `component` = 'FrameView', `query_param` = '{"url": "https://preview.snailjob.opensnail.com/"}', `is_frame` = 2, `icon` = 'gridicons:scheduled', `menu_name` = 'menu.monitor_snail-job' WHERE `menu_id` = 120;
+-- 澶栭摼绫诲瀷
+UPDATE `sys_menu` SET `menu_name` = 'RuoYi-Vue-Plus', `order_num` = 100, `path` = 'https://gitee.com/dromara/RuoYi-Vue-Plus', `component` = 'FrameView', `icon` = 'local-icon-gitee', `remark` = 'RuoYi-Vue-Plus 浠撳簱鍦板潃' WHERE `menu_id` = 4;
+INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (7, 'Soybean Admin', 0, 100, 'https://github.com/soybeanjs', 'FrameView', '', 0, 0, 'M', '0', '0', '', 'mdi:github', 103, 1, sysdate(), null, null, 'Soybean Admin 浠撳簱鍦板潃') ON DUPLICATE KEY UPDATE `update_time` = sysdate();
+INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (8, 'RuoYi-Plus-Soybean', 0, 100, 'https://gitee.com/xlsea/ruoyi-plus-soybean', 'FrameView', '', 0, 0, 'M', '0', '0', '', 'local-icon-gitee', 103, 1, sysdate(), null, null, 'RuoYi-Plus-Soybean 浠撳簱鍦板潃') ON DUPLICATE KEY UPDATE `update_time` = sysdate();
+
+-- plus-ui 闇�瑕佺鐢ㄧ殑椤甸潰
+UPDATE `sys_menu` SET `status` = '1' WHERE `menu_id` IN ( '116', '130', '131', '132' );
+-- 宸ヤ綔娴侀渶瑕佺鐢ㄧ殑椤甸潰
+UPDATE `sys_menu` SET `status` = '1' WHERE `menu_id` IN ( '11616', '11618', '11638', '11700', '11701' );
diff --git a/ruoyi-plus-soybean/docs/template/api/api.ts.vm b/ruoyi-plus-soybean/docs/template/api/api.ts.vm
new file mode 100755
index 0000000..f910941
--- /dev/null
+++ b/ruoyi-plus-soybean/docs/template/api/api.ts.vm
@@ -0,0 +1,41 @@
+import { request } from '@/service/request';
+#set($responseType = "")
+#if($tplCategory == "tree")
+ #set($responseType = "Api.${ModuleName}.${BusinessName}[]")
+#else
+ #set($responseType = "Api.${ModuleName}.${BusinessName}List")
+#end
+
+/** 鑾峰彇${functionName}鍒楄〃 */
+export function fetchGet${BusinessName}List (params?: Api.${ModuleName}.${BusinessName}SearchParams) {
+ return request<$responseType>({
+ url: '/${moduleName}/${businessName}/list',
+ method: 'get',
+ params
+ });
+}
+/** 鏂板${functionName} */
+export function fetchCreate${BusinessName} (data: Api.${ModuleName}.${BusinessName}OperateParams) {
+ return request<boolean>({
+ url: '/${moduleName}/${businessName}',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼${functionName} */
+export function fetchUpdate${BusinessName} (data: Api.${ModuleName}.${BusinessName}OperateParams) {
+ return request<boolean>({
+ url: '/${moduleName}/${businessName}',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎${functionName} */
+export function fetchBatchDelete${BusinessName} (${pkColumn.javaField}s: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/${moduleName}/${businessName}/${${pkColumn.javaField}s.join(',')}`,
+ method: 'delete'
+ });
+}
diff --git a/ruoyi-plus-soybean/docs/template/index-tree.vue.vm b/ruoyi-plus-soybean/docs/template/index-tree.vue.vm
new file mode 100755
index 0000000..5a0a99a
--- /dev/null
+++ b/ruoyi-plus-soybean/docs/template/index-tree.vue.vm
@@ -0,0 +1,232 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import { fetchBatchDelete${BusinessName}, fetchGet${BusinessName}List } from '@/service/api/${moduleName}/${business__name}';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { treeTransform, useNaiveTreeTable, useTableOperate } from '@/hooks/common/table';
+import { useDownload } from '@/hooks/business/download';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import ${BusinessName}OperateDrawer from './modules/${business__name}-operate-drawer.vue';
+import ${BusinessName}Search from './modules/${business__name}-search.vue';
+#if($dictList && $dictList.size() > 0)
+import { useDict } from '@/hooks/business/dict';
+#end
+
+defineOptions({
+ name: '${BusinessName}List'
+});
+
+#if($dictList && $dictList.size() > 0)
+#foreach($dict in $dictList)
+useDict('${dict.type}'#if(!$dict.immediate), false#end);
+#end#end
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.$ModuleName.${BusinessName}SearchParams>({
+#foreach ($column in $columns)
+ #if($column.query)
+ $column.javaField: null#if($foreach.hasNext),#end
+ #end
+#end
+ params: {}
+});
+
+const {
+ columns,
+ columnChecks,
+ data,
+ rows,
+ getData,
+ loading,
+ expandedRowKeys,
+ isCollapse,
+ expandAll,
+ collapseAll,
+ scrollX
+} = useNaiveTreeTable({
+ keyField: '$pkColumn.javaField',
+ api: () => fetchGet${BusinessName}List(searchParams.value),
+ transform: response => treeTransform(response, { idField: '$pkColumn.javaField' }),
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+#foreach ($column in $columns)
+ #if($column.list)
+ {
+ key: '$column.javaField',
+ title: '$column.columnComment',
+ align: 'center',
+ minWidth: 120#if($column.dictType),
+ render(row) {
+ return <DictTag value={row.$column.javaField} dictCode="$column.dictType" />;
+ }#end
+ },
+ #end
+#end
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const addBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:add-2-rounded"
+ tooltipContent={$t('common.add')}
+ onClick={() => addInRow(row)}
+ />
+ );
+ };
+
+ const editBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.$pkColumn.javaField)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.$pkColumn.javaField)}
+ />
+ );
+ };
+
+ const buttons = [];
+ if (hasAuth('${moduleName}:${businessName}:add')) buttons.push(addBtn());
+ if (hasAuth('${moduleName}:${businessName}:edit')) buttons.push(editBtn());
+ if (hasAuth('${moduleName}:${businessName}:remove')) buttons.push(deleteBtn());
+
+ return (
+ <div class="flex-center gap-8px">
+ {buttons.map((btn, index) => (
+ <>
+ {index !== 0 && <NDivider vertical />}
+ {btn}
+ </>
+ ))}
+ </div>
+ );
+ }
+ }
+ ]
+});
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(rows, '$pkColumn.javaField', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDelete${BusinessName}(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete($pkColumn.javaField: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDelete${BusinessName}([$pkColumn.javaField]);
+ if (error) return;
+ onDeleted();
+}
+
+function edit($pkColumn.javaField: CommonType.IdType) {
+ handleEdit($pkColumn.javaField);
+}
+
+function addInRow(row: Api.$ModuleName.${BusinessName}) {
+ editingData.value = jsonClone(row);
+ handleAdd();
+}
+
+function handleExport() {
+ download('/${moduleName}/${businessName}/export', searchParams.value, `${functionName}_#[[${new Date().getTime()}]]#.xlsx`);
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <${BusinessName}Search v-model:model="searchParams" @search="getData" />
+ <NCard title="${functionName}鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('${moduleName}:${businessName}:add')"
+ :show-delete="hasAuth('${moduleName}:${businessName}:remove')"
+ :show-export="false"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ >
+ <template #prefix>
+ <NButton v-if="!isCollapse" :disabled="!data.length" size="small" @click="expandAll">
+ <template #icon>
+ <icon-quill-expand />
+ </template>
+ 鍏ㄩ儴灞曞紑
+ </NButton>
+ <NButton v-if="isCollapse" :disabled="!data.length" size="small" @click="collapseAll">
+ <template #icon>
+ <icon-quill-collapse />
+ </template>
+ 鍏ㄩ儴鏀惰捣
+ </NButton>
+ </template>
+ </TableHeaderOperation>
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ v-model:expanded-row-keys="expandedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.#foreach($column in $columns)#if($column.isPk == '1')$column.javaField#end#end"
+ class="sm:h-full"
+ />
+ <${BusinessName}OperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getData"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/docs/template/index.vue.vm b/ruoyi-plus-soybean/docs/template/index.vue.vm
new file mode 100755
index 0000000..f1cc6dc
--- /dev/null
+++ b/ruoyi-plus-soybean/docs/template/index.vue.vm
@@ -0,0 +1,198 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDelete${BusinessName}, fetchGet${BusinessName}List } from '@/service/api/${moduleName}/${business__name}';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import ${BusinessName}OperateDrawer from './modules/${business__name}-operate-drawer.vue';
+import ${BusinessName}Search from './modules/${business__name}-search.vue';
+#if($dictList && $dictList.size() > 0)
+import { useDict } from '@/hooks/business/dict';
+#end
+
+defineOptions({
+ name: '${BusinessName}List'
+});
+
+#if($dictList && $dictList.size() > 0)
+#foreach($dict in $dictList)
+useDict('${dict.type}'#if(!$dict.immediate), false#end);
+#end#end
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.$ModuleName.${BusinessName}SearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+#foreach ($column in $columns)
+ #if($column.query)
+ $column.javaField: null#if($foreach.hasNext),#end
+ #end
+#end
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGet${BusinessName}List(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+#foreach ($column in $columns)
+ #if($column.list)
+ {
+ key: '$column.javaField',
+ title: '$column.columnComment',
+ align: 'center',
+ minWidth: 120#if($column.dictType),
+ render(row) {
+ return <DictTag value={row.$column.javaField} dictCode="$column.dictType" />;
+ }#end
+ },
+ #end
+#end
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('${moduleName}:${businessName}:edit') || !hasAuth('${moduleName}:${businessName}:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('${moduleName}:${businessName}:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.$pkColumn.javaField)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('${moduleName}:${businessName}:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.$pkColumn.javaField)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+});
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, '$pkColumn.javaField', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDelete${BusinessName}(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete($pkColumn.javaField: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDelete${BusinessName}([$pkColumn.javaField]);
+ if (error) return;
+ onDeleted();
+}
+
+function edit($pkColumn.javaField: CommonType.IdType) {
+ handleEdit($pkColumn.javaField);
+}
+
+function handleExport() {
+ download('/${moduleName}/${businessName}/export', searchParams.value, `${functionName}_#[[${new Date().getTime()}]]#.xlsx`);
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <${BusinessName}Search v-model:model="searchParams" @search="getDataByPage" />
+ <NCard title="${functionName}鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('${moduleName}:${businessName}:add')"
+ :show-delete="hasAuth('${moduleName}:${businessName}:remove')"
+ :show-export="hasAuth('${moduleName}:${businessName}:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ />
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.$pkColumn.javaField"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <${BusinessName}OperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getDataByPage"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/docs/template/modules/operate-drawer.vue.vm b/ruoyi-plus-soybean/docs/template/modules/operate-drawer.vue.vm
new file mode 100755
index 0000000..60725e4
--- /dev/null
+++ b/ruoyi-plus-soybean/docs/template/modules/operate-drawer.vue.vm
@@ -0,0 +1,257 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { fetchCreate${BusinessName}, fetchUpdate${BusinessName}#if($tplCategory == 'tree'), fetchGet${BusinessName}List#end } from '@/service/api/${moduleName}/${business__name}';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+#if($tplCategory == 'tree')
+import { handleTree } from '@/utils/common';
+#end
+import { $t } from '@/locales';
+
+defineOptions({
+ name: '${BusinessName}OperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.$ModuleName.${BusinessName} | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+#if($tplCategory == 'tree')
+
+const treeList = ref<Api.Demo.Tree[]>([]);
+#end
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: '鏂板${functionName}',
+ edit: '缂栬緫${functionName}'
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.$ModuleName.${BusinessName}OperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+#foreach($column in $columns)
+ #if($column.insert() || $column.isEdit())
+ ${column.javaField}:#if($column.javaType == 'String' || ($!column.dictType && $column.dictType != '')) ''#else null#end#if($foreach.hasNext),#end
+ #end
+#end
+ };
+}
+
+type RuleKey = Extract<
+ keyof Model,
+#foreach($column in $columns)
+#if($column.required)
+ | '$column.javaField'#if($foreach.hasNext)#end
+#end#end>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+#foreach($column in $columns)
+#if($column.required)
+ $column.javaField: createRequiredRule('${column.columnComment}涓嶈兘涓虹┖')#if($foreach.hasNext),#end
+#end
+#end
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+#if($tplCategory == 'tree')
+ model.value.$treeParentCode = props.rowData?.$treeCode || 0;
+#end
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ #set($operateColumns = [])
+ #foreach($column in $columns)#if($column.insert || $column.edit)#set($dummy = $operateColumns.add($column))#end#end
+ const { #foreach($column in $operateColumns)$column.javaField#if($foreach.hasNext), #end#end } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ #set($addFields = [])
+ #foreach($column in $columns)#if($column.insert)#set($dummy = $addFields.add($column.javaField))#end#end
+ const { error } = await fetchCreate${BusinessName}({ #foreach($field in $addFields)$field#if($foreach.hasNext), #end#end });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ #set($editFields = [])
+ #foreach($column in $columns)#if($column.edit)#set($dummy = $editFields.add($column.javaField))#end#end
+ const { error } = await fetchUpdate${BusinessName}({ #foreach($field in $editFields)$field#if($foreach.hasNext), #end#end });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+#if($tplCategory == 'tree')
+
+async function getTreeList() {
+ const { data, error } = await fetchGet${BusinessName}List();
+ if (error) {
+ return;
+ }
+ const { tree } = handleTree(data);
+ treeList.value = tree;
+}
+#end
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ getTreeList();
+ }
+});
+#if($tplCategory == 'tree')
+
+const treeOptions = computed(() => {
+ return [
+ {
+ id: 0,
+ treeName: '椤剁骇鑺傜偣',
+ children: treeList.value
+ }
+ ];
+});
+#end
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+#set($immediateDictList = [])
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if(($column.insert || $column.edit) && !$column.pk)
+#set($isImmediate = !$column.isList() && !$column.isQuery() && !$immediateDictList.contains($column.dictType))
+ <NFormItem label="$column.columnComment" path="$column.javaField">
+ #if($tplCategory == 'tree' && $column.javaField == $treeParentCode)
+ <NTreeSelect
+ v-model:value="model.$treeParentCode"
+ filterable
+ class="h-full"
+ key-field="$treeCode"
+ label-field="$treeName"
+ :options="treeOptions"
+ :default-expanded-keys="[0]"
+ />
+ #elseif($column.htmlType == "textarea" || $column.htmlType == "editor")
+ <NInput
+ v-model:value="model.$column.javaField"
+ :rows="3"
+ type="textarea"
+ placeholder="璇疯緭鍏�$column.columnComment"
+ />
+ #elseif($column.htmlType == "select" && $column.dictType)
+ <DictSelect
+ v-model:value="model.$column.javaField"
+ placeholder="璇烽�夋嫨$column.columnComment"
+ dict-code="$column.dictType"
+ clearable
+#if($isImmediate)#set($void = $immediateDictList.add($column.dictType))
+ immediate
+#end
+ />
+ #elseif($column.htmlType == "select" && !$column.dictType)
+ <NSelect
+ v-model:value="model.$column.javaField"
+ placeholder="璇烽�夋嫨$column.columnComment"
+ :options="[{ value: '0', label: '璇烽�夋嫨瀛楀吀鐢熸垚' }]"
+ clearable
+ />
+ #elseif($column.htmlType == "radio" && $column.dictType)
+ <DictRadio
+ v-model:value="model.$column.javaField"
+ placeholder="璇烽�夋嫨$column.columnComment"
+ dict-code="$column.dictType"
+ clearable
+#if($isImmediate)#set($void = $immediateDictList.add($column.dictType))
+ immediate
+#end
+ />
+ #elseif($column.htmlType == "radio" && !$column.dictType)
+ <NRadioGroup v-model:value="model.$column.javaField">
+ <NSpace>
+ <NRadio value="0" label="璇烽�夋嫨瀛楀吀鐢熸垚" />
+ </NSpace>
+ </NRadioGroup>
+ #elseif($column.htmlType == "checkbox" && $column.dictType)
+ <DictCheckbox
+ v-model:value="model.$column.javaField"
+ placeholder="璇烽�夋嫨$column.columnComment"
+ dict-code="$column.dictType"
+ clearable
+#if($isImmediate)#set($void = $immediateDictList.add($column.dictType))
+ immediate
+#end
+ />
+ #elseif($column.htmlType == "checkbox" && $column.dictType)
+ <NCheckboxGroup v-model:value="model.$column.javaField">
+ <NSpace>
+ <NCheckbox value="0" label="璇烽�夋嫨瀛楀吀鐢熸垚" />
+ </NSpace>
+ </NCheckboxGroup>
+ #elseif($column.htmlType == 'datetime')
+ <NDatePicker
+ v-model:formatted-value="model.$column.javaField"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ />
+ #elseif($column.htmlType == "imageUpload")
+ <OssUpload v-model:value="model.$column.javaField" upload-type="image" />
+ #elseif($column.htmlType == "fileUpload")
+ <OssUpload v-model:value="model.$column.javaField" upload-type="file" />
+ #elseif($column.htmlType == "editor")
+ <TinymceEditor v-model:value="model.$column.javaField" />
+#else <NInput v-model:value="model.$column.javaField" placeholder="璇疯緭鍏�$column.columnComment" />
+ #end
+ </NFormItem>
+#end
+#end
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/docs/template/modules/search.vue.vm b/ruoyi-plus-soybean/docs/template/modules/search.vue.vm
new file mode 100755
index 0000000..4cd4aba
--- /dev/null
+++ b/ruoyi-plus-soybean/docs/template/modules/search.vue.vm
@@ -0,0 +1,130 @@
+#set($ModuleName=$moduleName.substring(0, 1).toUpperCase() + $moduleName.substring(1))
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: '${BusinessName}Search'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+#foreach ($column in $columns)
+ #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+ #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+const dateRange${AttrName} = ref<[string, string] | null>(null);
+#end#end
+const model = defineModel<Api.$ModuleName.${BusinessName}SearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ #foreach ($column in $columns)
+ #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+ #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ dateRange${AttrName}.value = null;
+ #end
+#end
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+#foreach ($column in $columns)
+ #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+ #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ if (dateRange${AttrName}.value?.length) {
+ model.value.params!.begin${AttrName} = dateRange${AttrName}.value[0];
+ model.value.params!.end${AttrName} = dateRange${AttrName}.value[1];
+ }
+ #end
+#end
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="$moduleName-${business__name}-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+#set($immediateDictList = [])
+#foreach($column in $columns)
+#if($column.query)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ <NFormItemGi span="24 s:12 m:6" label="$column.columnComment" label-width="auto" path="$column.javaField" class="pr-24px">
+ #if($!StrUtil.contains("select, radio, checkbox", $column.htmlType) && $column.dictType)
+ <DictSelect
+ v-model:value="model.$column.javaField"
+ placeholder="璇烽�夋嫨$column.columnComment"
+ dict-code="$column.dictType"
+ clearable
+#if(!$column.isList() && !$immediateDictList.contains($column.dictType))#set($void = $immediateDictList.add($column.dictType))
+ immediate
+#end
+ />
+ #elseif($!StrUtil.contains("select, radio, checkbox", $column.htmlType))
+ <NSelect
+ v-model:value="model.$column.javaField"
+ placeholder="璇烽�夋嫨$column.columnComment"
+ :options="[]"
+ clearable
+ />
+ #elseif($column.htmlType == 'datetime' && $column.queryType != "BETWEEN")
+ <NDatePicker
+ v-model:formatted-value="model.$column.javaField"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ />
+ #elseif($column.htmlType == 'datetime' && $column.queryType == "BETWEEN")
+ <NDatePicker
+ v-model:formatted-value="dateRange${AttrName}"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ />
+#else <NInput v-model:value="model.$column.javaField" placeholder="璇疯緭鍏�$column.columnComment" />
+ #end
+ </NFormItemGi>
+ #end
+#end
+ <NFormItemGi :show-feedback="false" span="24" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/docs/template/typings/api.d.ts.vm b/ruoyi-plus-soybean/docs/template/typings/api.d.ts.vm
new file mode 100755
index 0000000..e65af2c
--- /dev/null
+++ b/ruoyi-plus-soybean/docs/template/typings/api.d.ts.vm
@@ -0,0 +1,51 @@
+#set($BaseEntity = ['createDept', 'createBy', 'createTime', 'updateBy', 'updateTime'])
+#set($ModuleName = $moduleName.substring(0, 1).toUpperCase() + $moduleName.substring(1))
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+ /**
+ * namespace ${ModuleName}
+ *
+ * backend api module: "${ModuleName}"
+ */
+ namespace ${ModuleName} {
+ /** ${businessname} */
+ type ${BusinessName} = Common.CommonRecord<{
+ #foreach($column in $columns)#if(!$BaseEntity.contains($column.javaField))
+ /** $column.columnComment */
+ $column.javaField:#if($column.javaField.indexOf("id") != -1 || $column.javaField.indexOf("Id") != -1) CommonType.IdType; #elseif($column.javaType == 'Long' || $column.javaType == 'Integer' || $column.javaType == 'Double' || $column.javaType == 'Float' || $column.javaType == 'BigDecimal') number; #elseif($column.javaType == 'Boolean') boolean; #else string; #end
+ #end#end
+ }>;
+
+ /** ${businessname} search params */
+ type ${BusinessName}SearchParams = CommonType.RecordNullable<
+ Pick<
+ Api.${ModuleName}.${BusinessName},
+ #foreach($column in $columns)
+ #if($column.query && $column.queryType != 'BETWEEN')
+ | '${column.javaField}'
+ #end
+ #end
+ > &
+ Api.Common.CommonSearchParams
+ >;
+
+ /** ${businessname} operate params */
+ type ${BusinessName}OperateParams = CommonType.RecordNullable<
+ Pick<
+ Api.${ModuleName}.${BusinessName},
+ #foreach($column in $columns)
+ #if($column.insert || $column.edit)
+ | '${column.javaField}'
+ #end
+ #end
+ >
+ >;
+
+ /** ${businessname} list */
+ type ${BusinessName}List = Api.Common.PaginatingQueryRecord<${BusinessName}>;
+ }
+}
diff --git a/ruoyi-plus-soybean/eslint.config.js b/ruoyi-plus-soybean/eslint.config.js
new file mode 100755
index 0000000..00537e3
--- /dev/null
+++ b/ruoyi-plus-soybean/eslint.config.js
@@ -0,0 +1,24 @@
+import { defineConfig } from '@soybeanjs/eslint-config';
+
+export default defineConfig(
+ { vue: true, unocss: true },
+ {
+ rules: {
+ 'vue/multi-word-component-names': [
+ 'warn',
+ {
+ ignores: ['index', 'App', 'Register', '[id]', '[url]']
+ }
+ ],
+ 'vue/component-name-in-template-casing': [
+ 'warn',
+ 'PascalCase',
+ {
+ registeredComponentsOnly: false,
+ ignores: ['/^icon-/']
+ }
+ ],
+ 'unocss/order-attributify': 'off'
+ }
+ }
+);
diff --git a/ruoyi-plus-soybean/index.html b/ruoyi-plus-soybean/index.html
new file mode 100755
index 0000000..b04ac55
--- /dev/null
+++ b/ruoyi-plus-soybean/index.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html lang="zh-cmn-Hans">
+ <head>
+ <meta charset="UTF-8" />
+ <link rel="icon" href="/favicon.svg" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <meta name="color-scheme" content="light dark" />
+ <title>%VITE_APP_TITLE%</title>
+ </head>
+ <body>
+ <div id="app"></div>
+ <script type="module" src="/src/main.ts"></script>
+ </body>
+</html>
diff --git a/ruoyi-plus-soybean/package.json b/ruoyi-plus-soybean/package.json
new file mode 100755
index 0000000..78024af
--- /dev/null
+++ b/ruoyi-plus-soybean/package.json
@@ -0,0 +1,127 @@
+{
+ "name": "ruoyi-vue-plus",
+ "type": "module",
+ "version": "2.0.0",
+ "description": "缁撳悎浜� RuoYi-Vue-Plus 鐨勫己澶у悗绔姛鑳藉拰 Soybean Admin 鐨勭幇浠e寲鍓嶇鐗规�э紝涓哄紑鍙戣�呮彁渚涗簡瀹屾暣鐨勪紒涓氱鐞嗚В鍐虫柟妗堛��",
+ "author": {
+ "name": "xlsea",
+ "email": "m@xlsea.cn",
+ "url": "https://gitee.com/xlsea"
+ },
+ "license": "MIT",
+ "homepage": "https://docs.ruoyi.xlsea.cn",
+ "repository": {
+ "url": "https://gitee.com/xlsea/ruoyi-plus-soybean.git"
+ },
+ "bugs": {
+ "url": "https://gitee.com/xlsea/ruoyi-plus-soybean/issues"
+ },
+ "keywords": [
+ "RuoYi-Vue-Plus",
+ "Soybean Admin",
+ "Vue3 admin ",
+ "vue-admin-template",
+ "Vite7",
+ "TypeScript",
+ "naive-ui",
+ "naive-ui-admin",
+ "ant-design-vue v4",
+ "UnoCSS"
+ ],
+ "contributors": [
+ {
+ "name": "Elio",
+ "email": "1983933789@qq.com",
+ "url": "https://gitee.com/elio-an"
+ }
+ ],
+ "engines": {
+ "node": ">=20.19.0",
+ "pnpm": ">=10.5.0"
+ },
+ "scripts": {
+ "build": "vite build --mode prod",
+ "build:dev": "vite build --mode dev",
+ "build:test": "vite build --mode test",
+ "cleanup": "sa cleanup",
+ "commit": "sa git-commit",
+ "commit:zh": "sa git-commit -l=zh-cn",
+ "dev": "vite --mode dev",
+ "dev:prod": "vite --mode prod",
+ "dev:test": "vite --mode test",
+ "gen-route": "sa gen-route",
+ "lint": "eslint . --fix",
+ "prepare": "simple-git-hooks",
+ "preview": "vite preview",
+ "release": "sa release",
+ "typecheck": "vue-tsc --noEmit --skipLibCheck",
+ "update-pkg": "sa update-pkg"
+ },
+ "dependencies": {
+ "@better-scroll/core": "2.5.1",
+ "@iconify/vue": "5.0.0",
+ "@sa/axios": "workspace:*",
+ "@sa/color": "workspace:*",
+ "@sa/hooks": "workspace:*",
+ "@sa/materials": "workspace:*",
+ "@sa/utils": "workspace:*",
+ "@types/streamsaver": "^2.0.5",
+ "@umoteam/editor": "^9.0.1",
+ "@vueuse/core": "14.1.0",
+ "clipboard": "2.0.11",
+ "dayjs": "1.11.19",
+ "defu": "6.1.4",
+ "echarts": "6.0.0",
+ "highlight.js": "^11.11.1",
+ "jsencrypt": "^3.5.4",
+ "json5": "2.2.3",
+ "naive-ui": "2.43.2",
+ "nprogress": "0.2.0",
+ "pinia": "3.0.4",
+ "streamsaver": "^2.0.6",
+ "tailwind-merge": "3.4.0",
+ "vue": "3.5.26",
+ "vue-advanced-cropper": "^2.8.9",
+ "vue-draggable-plus": "0.6.0",
+ "vue-i18n": "11.2.7",
+ "vue-router": "4.6.4"
+ },
+ "devDependencies": {
+ "@elegant-router/vue": "0.3.8",
+ "@iconify/json": "2.2.417",
+ "@sa/scripts": "workspace:*",
+ "@sa/uno-preset": "workspace:*",
+ "@soybeanjs/eslint-config": "1.7.4",
+ "@types/node": "25.0.3",
+ "@types/nprogress": "0.2.3",
+ "@unocss/eslint-config": "66.5.10",
+ "@unocss/preset-icons": "66.5.10",
+ "@unocss/preset-uno": "66.5.10",
+ "@unocss/transformer-directives": "66.5.10",
+ "@unocss/transformer-variant-group": "66.5.10",
+ "@unocss/vite": "66.5.10",
+ "@vitejs/plugin-vue": "6.0.3",
+ "@vitejs/plugin-vue-jsx": "5.1.2",
+ "consola": "3.4.2",
+ "eslint": "9.39.2",
+ "eslint-plugin-vue": "10.6.2",
+ "kolorist": "1.8.0",
+ "sass": "1.97.1",
+ "simple-git-hooks": "2.13.1",
+ "tsx": "4.21.0",
+ "typescript": "5.9.3",
+ "unplugin-icons": "22.5.0",
+ "unplugin-vue-components": "30.0.0",
+ "vite": "7.3.0",
+ "vite-plugin-progress": "0.0.7",
+ "vite-plugin-svg-icons": "2.0.1",
+ "vite-plugin-vue-devtools": "8.0.5",
+ "vue-eslint-parser": "10.2.0",
+ "vue-tsc": "3.2.1"
+ },
+ "simple-git-hooks": {
+ "commit-msg": "pnpm sa git-commit-verify",
+ "pre-commit": "pnpm typecheck && pnpm lint && git diff --exit-code"
+ },
+ "website": "https://ruoyi.xlsea.cn"
+}
diff --git a/ruoyi-plus-soybean/packages/alova/package.json b/ruoyi-plus-soybean/packages/alova/package.json
new file mode 100755
index 0000000..a4d7434
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/alova/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@sa/alova",
+ "version": "2.0.2",
+ "exports": {
+ ".": "./src/index.ts",
+ "./fetch": "./src/fetch.ts",
+ "./client": "./src/client.ts",
+ "./mock": "./src/mock.ts"
+ },
+ "typesVersions": {
+ "*": {
+ "*": ["./src/*"]
+ }
+ },
+ "dependencies": {
+ "@alova/mock": "2.0.18",
+ "@sa/utils": "workspace:*",
+ "alova": "3.4.1"
+ }
+}
diff --git a/ruoyi-plus-soybean/packages/alova/src/client.ts b/ruoyi-plus-soybean/packages/alova/src/client.ts
new file mode 100755
index 0000000..0d76ebb
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/alova/src/client.ts
@@ -0,0 +1 @@
+export * from 'alova/client';
diff --git a/ruoyi-plus-soybean/packages/alova/src/constant.ts b/ruoyi-plus-soybean/packages/alova/src/constant.ts
new file mode 100755
index 0000000..be5c43c
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/alova/src/constant.ts
@@ -0,0 +1,2 @@
+/** the backend error code key */
+export const BACKEND_ERROR_CODE = 'BACKEND_ERROR';
diff --git a/ruoyi-plus-soybean/packages/alova/src/fetch.ts b/ruoyi-plus-soybean/packages/alova/src/fetch.ts
new file mode 100755
index 0000000..8511ce4
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/alova/src/fetch.ts
@@ -0,0 +1,2 @@
+import adapterFetch from 'alova/fetch';
+export default adapterFetch;
diff --git a/ruoyi-plus-soybean/packages/alova/src/index.ts b/ruoyi-plus-soybean/packages/alova/src/index.ts
new file mode 100755
index 0000000..4264253
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/alova/src/index.ts
@@ -0,0 +1,77 @@
+import { createAlova } from 'alova';
+import type { AlovaDefaultCacheAdapter, AlovaGenerics, AlovaGlobalCacheAdapter, AlovaRequestAdapter } from 'alova';
+import VueHook from 'alova/vue';
+import type { VueHookType } from 'alova/vue';
+import adapterFetch from 'alova/fetch';
+import { createServerTokenAuthentication } from 'alova/client';
+import type { FetchRequestInit } from 'alova/fetch';
+import { BACKEND_ERROR_CODE } from './constant';
+import type { CustomAlovaConfig, RequestOptions } from './type';
+
+export const createAlovaRequest = <
+ RequestConfig = FetchRequestInit,
+ ResponseType = Response,
+ ResponseHeader = Headers,
+ L1Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter,
+ L2Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter
+>(
+ customConfig: CustomAlovaConfig<
+ AlovaGenerics<any, any, RequestConfig, ResponseType, ResponseHeader, L1Cache, L2Cache, any>
+ >,
+ options: RequestOptions<AlovaGenerics<any, any, RequestConfig, ResponseType, ResponseHeader, L1Cache, L2Cache, any>>
+) => {
+ const { tokenRefresher } = options;
+ const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication<
+ VueHookType,
+ AlovaRequestAdapter<RequestConfig, ResponseType, ResponseHeader>
+ >({
+ refreshTokenOnSuccess: {
+ isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false,
+ handler: async (response, method) => tokenRefresher?.handler(response, method)
+ },
+ refreshTokenOnError: {
+ isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false,
+ handler: async (response, method) => tokenRefresher?.handler(response, method)
+ }
+ });
+
+ const instance = createAlova({
+ ...customConfig,
+ timeout: customConfig.timeout ?? 10 * 1000,
+ requestAdapter: (customConfig.requestAdapter as any) ?? adapterFetch(),
+ statesHook: VueHook,
+ beforeRequest: onAuthRequired(options.onRequest as any),
+ responded: onResponseRefreshToken({
+ onSuccess: async (response, method) => {
+ // check if http status is success
+ let error: any = null;
+ let transformedData: any = null;
+ try {
+ if (await options.isBackendSuccess(response)) {
+ transformedData = await options.transformBackendResponse(response);
+ } else {
+ error = new Error('the backend request error');
+ error.code = BACKEND_ERROR_CODE;
+ }
+ } catch (err) {
+ error = err;
+ }
+
+ if (error) {
+ await options.onError?.(error, response, method);
+ throw error;
+ }
+
+ return transformedData;
+ },
+ onComplete: options.onComplete,
+ onError: (error, method) => options.onError?.(error, null, method)
+ })
+ });
+
+ return instance;
+};
+
+export { BACKEND_ERROR_CODE };
+export type * from './type';
+export type * from 'alova';
diff --git a/ruoyi-plus-soybean/packages/alova/src/mock.ts b/ruoyi-plus-soybean/packages/alova/src/mock.ts
new file mode 100755
index 0000000..f3aaf08
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/alova/src/mock.ts
@@ -0,0 +1 @@
+export * from '@alova/mock';
diff --git a/ruoyi-plus-soybean/packages/alova/src/type.ts b/ruoyi-plus-soybean/packages/alova/src/type.ts
new file mode 100755
index 0000000..27b84b2
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/alova/src/type.ts
@@ -0,0 +1,52 @@
+import type { AlovaGenerics, AlovaOptions, AlovaRequestAdapter, Method, ResponseCompleteHandler } from 'alova';
+
+export type CustomAlovaConfig<AG extends AlovaGenerics> = Omit<
+ AlovaOptions<AG>,
+ 'statesHook' | 'beforeRequest' | 'responded' | 'requestAdapter'
+> & {
+ /** request adapter. all request of alova will be sent by it. */
+ requestAdapter?: AlovaRequestAdapter<AG['RequestConfig'], AG['Response'], AG['ResponseHeader']>;
+};
+
+export interface RequestOptions<AG extends AlovaGenerics> {
+ /**
+ * The hook before request
+ *
+ * For example: You can add header token in this hook
+ *
+ * @param method alova Method Instance
+ */
+ onRequest?: AlovaOptions<AG>['beforeRequest'];
+ /**
+ * The hook to check backend response is success or not
+ *
+ * @param response alova response
+ */
+ isBackendSuccess: (response: AG['Response']) => Promise<boolean>;
+
+ /** The config to refresh token */
+ tokenRefresher?: {
+ /** detect the token is expired */
+ isExpired(response: AG['Response'], Method: Method<AG>): Promise<boolean> | boolean;
+ /** refresh token handler */
+ handler(response: AG['Response'], Method: Method<AG>): Promise<void>;
+ };
+
+ /** The hook after backend request complete */
+ onComplete?: ResponseCompleteHandler<AG>;
+
+ /**
+ * The hook to handle error
+ *
+ * For example: You can show error message in this hook
+ *
+ * @param error
+ */
+ onError?: (error: any, response: AG['Response'] | null, methodInstance: Method<AG>) => any | Promise<any>;
+ /**
+ * transform backend response when the responseType is json
+ *
+ * @param response alova response
+ */
+ transformBackendResponse: (response: AG['Response']) => any;
+}
diff --git a/ruoyi-plus-soybean/packages/alova/tsconfig.json b/ruoyi-plus-soybean/packages/alova/tsconfig.json
new file mode 100755
index 0000000..5823ed5
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/alova/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "jsx": "preserve",
+ "lib": ["DOM", "ESNext"],
+ "baseUrl": ".",
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "types": ["node"],
+ "strict": true,
+ "strictNullChecks": true,
+ "noUnusedLocals": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/ruoyi-plus-soybean/packages/axios/package.json b/ruoyi-plus-soybean/packages/axios/package.json
new file mode 100755
index 0000000..902101e
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/axios/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "@sa/axios",
+ "version": "2.0.2",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "typesVersions": {
+ "*": {
+ "*": ["./src/*"]
+ }
+ },
+ "dependencies": {
+ "@sa/utils": "workspace:*",
+ "axios": "1.13.2",
+ "axios-retry": "4.5.0",
+ "qs": "6.14.0"
+ },
+ "devDependencies": {
+ "@types/qs": "6.14.0"
+ }
+}
diff --git a/ruoyi-plus-soybean/packages/axios/src/constant.ts b/ruoyi-plus-soybean/packages/axios/src/constant.ts
new file mode 100755
index 0000000..e85ded3
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/axios/src/constant.ts
@@ -0,0 +1,8 @@
+/** request id key */
+export const REQUEST_ID_KEY = 'X-Request-Id';
+
+/** the backend error code key */
+export const BACKEND_ERROR_CODE = 'BACKEND_ERROR';
+
+/** the request canceled code */
+export const REQUEST_CANCELED_CODE = 'ERR_CANCELED';
diff --git a/ruoyi-plus-soybean/packages/axios/src/index.ts b/ruoyi-plus-soybean/packages/axios/src/index.ts
new file mode 100755
index 0000000..08af572
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/axios/src/index.ts
@@ -0,0 +1,179 @@
+import axios, { AxiosError } from 'axios';
+import type { AxiosResponse, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
+import axiosRetry from 'axios-retry';
+import { nanoid } from '@sa/utils';
+import { createAxiosConfig, createDefaultOptions, createRetryOptions } from './options';
+import { transformResponse } from './shared';
+import { BACKEND_ERROR_CODE, REQUEST_ID_KEY } from './constant';
+import type {
+ CustomAxiosRequestConfig,
+ FlatRequestInstance,
+ MappedType,
+ RequestInstance,
+ RequestOption,
+ ResponseType
+} from './type';
+
+function createCommonRequest<
+ ResponseData,
+ ApiData = ResponseData,
+ State extends Record<string, unknown> = Record<string, unknown>
+>(axiosConfig?: CreateAxiosDefaults, options?: Partial<RequestOption<ResponseData, ApiData, State>>) {
+ const opts = createDefaultOptions<ResponseData, ApiData, State>(options);
+
+ const axiosConf = createAxiosConfig(axiosConfig);
+ const instance = axios.create(axiosConf);
+
+ const abortControllerMap = new Map<string, AbortController>();
+
+ // config axios retry
+ const retryOptions = createRetryOptions(axiosConf);
+ axiosRetry(instance, retryOptions);
+
+ instance.interceptors.request.use(conf => {
+ const config: InternalAxiosRequestConfig = { ...conf };
+
+ // set request id
+ const requestId = nanoid();
+ config.headers.set(REQUEST_ID_KEY, requestId);
+
+ // config abort controller
+ if (!config.signal) {
+ const abortController = new AbortController();
+ config.signal = abortController.signal;
+ abortControllerMap.set(requestId, abortController);
+ }
+
+ // handle config by hook
+ const handledConfig = opts.onRequest?.(config) || config;
+
+ return handledConfig;
+ });
+
+ instance.interceptors.response.use(
+ async response => {
+ const responseType: ResponseType = (response.config?.responseType as ResponseType) || 'json';
+
+ await transformResponse(response);
+
+ if (responseType !== 'json' || opts.isBackendSuccess(response)) {
+ return Promise.resolve(response);
+ }
+
+ const fail = await opts.onBackendFail(response, instance);
+ if (fail) {
+ return fail;
+ }
+
+ const backendError = new AxiosError<ResponseData>(
+ 'the backend request error',
+ BACKEND_ERROR_CODE,
+ response.config,
+ response.request,
+ response
+ );
+
+ await opts.onError(backendError);
+
+ return Promise.reject(backendError);
+ },
+ async (error: AxiosError<ResponseData>) => {
+ await opts.onError(error);
+
+ return Promise.reject(error);
+ }
+ );
+
+ function cancelAllRequest() {
+ abortControllerMap.forEach(abortController => {
+ abortController.abort();
+ });
+ abortControllerMap.clear();
+ }
+
+ return {
+ instance,
+ opts,
+ cancelAllRequest
+ };
+}
+
+/**
+ * create a request instance
+ *
+ * @param axiosConfig axios config
+ * @param options request options
+ */
+export function createRequest<ResponseData, ApiData, State extends Record<string, unknown>>(
+ axiosConfig?: CreateAxiosDefaults,
+ options?: Partial<RequestOption<ResponseData, ApiData, State>>
+) {
+ const { instance, opts, cancelAllRequest } = createCommonRequest<ResponseData, ApiData, State>(axiosConfig, options);
+
+ const request: RequestInstance<ApiData, State> = async function request<
+ T extends ApiData = ApiData,
+ R extends ResponseType = 'json'
+ >(config: CustomAxiosRequestConfig) {
+ const response: AxiosResponse<ResponseData> = await instance(config);
+
+ const responseType = response.config?.responseType || 'json';
+
+ if (responseType === 'json') {
+ return opts.transform(response);
+ }
+
+ return response.data as MappedType<R, T>;
+ } as RequestInstance<ApiData, State>;
+
+ request.cancelAllRequest = cancelAllRequest;
+ request.state = {} as State;
+
+ return request;
+}
+
+/**
+ * create a flat request instance
+ *
+ * The response data is a flat object: { data: any, error: AxiosError }
+ *
+ * @param axiosConfig axios config
+ * @param options request options
+ */
+export function createFlatRequest<ResponseData, ApiData, State extends Record<string, unknown>>(
+ axiosConfig?: CreateAxiosDefaults,
+ options?: Partial<RequestOption<ResponseData, ApiData, State>>
+) {
+ const { instance, opts, cancelAllRequest } = createCommonRequest<ResponseData, ApiData, State>(axiosConfig, options);
+
+ const flatRequest: FlatRequestInstance<ResponseData, ApiData, State> = async function flatRequest<
+ T extends ApiData = ApiData,
+ R extends ResponseType = 'json'
+ >(config: CustomAxiosRequestConfig) {
+ try {
+ const response: AxiosResponse<ResponseData> = await instance(config);
+
+ const responseType = response.config?.responseType || 'json';
+
+ if (responseType === 'json') {
+ const data = await opts.transform(response);
+
+ return { data, error: null, response };
+ }
+
+ return { data: response.data as MappedType<R, T>, error: null, response };
+ } catch (error) {
+ return { data: null, error, response: (error as AxiosError<ResponseData>).response };
+ }
+ } as FlatRequestInstance<ResponseData, ApiData, State>;
+
+ flatRequest.cancelAllRequest = cancelAllRequest;
+ flatRequest.state = {
+ ...opts.defaultState
+ } as State;
+
+ return flatRequest;
+}
+
+export { BACKEND_ERROR_CODE, REQUEST_ID_KEY };
+export type * from './type';
+export type { CreateAxiosDefaults, AxiosError };
diff --git a/ruoyi-plus-soybean/packages/axios/src/options.ts b/ruoyi-plus-soybean/packages/axios/src/options.ts
new file mode 100755
index 0000000..e786639
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/axios/src/options.ts
@@ -0,0 +1,60 @@
+import type { CreateAxiosDefaults } from 'axios';
+import type { IAxiosRetryConfig } from 'axios-retry';
+import { stringify } from 'qs';
+import { isHttpSuccess } from './shared';
+import type { RequestOption } from './type';
+
+export function createDefaultOptions<
+ ResponseData,
+ ApiData = ResponseData,
+ State extends Record<string, unknown> = Record<string, unknown>
+>(options?: Partial<RequestOption<ResponseData, ApiData, State>>) {
+ const opts: RequestOption<ResponseData, ApiData, State> = {
+ defaultState: {} as State,
+ transform: async response => response.data as unknown as ApiData,
+ transformBackendResponse: async response => response.data as unknown as ApiData,
+ onRequest: async config => config,
+ isBackendSuccess: _response => true,
+ onBackendFail: async () => {},
+ onError: async () => {}
+ };
+
+ if (options?.transform) {
+ opts.transform = options.transform;
+ } else {
+ opts.transform = options?.transformBackendResponse || opts.transform;
+ }
+
+ Object.assign(opts, options);
+
+ return opts;
+}
+
+export function createRetryOptions(config?: Partial<CreateAxiosDefaults>) {
+ const retryConfig: IAxiosRetryConfig = {
+ retries: 0
+ };
+
+ Object.assign(retryConfig, config);
+
+ return retryConfig;
+}
+
+export function createAxiosConfig(config?: Partial<CreateAxiosDefaults>) {
+ const TEN_SECONDS = 10 * 1000;
+
+ const axiosConfig: CreateAxiosDefaults = {
+ timeout: TEN_SECONDS,
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ validateStatus: isHttpSuccess,
+ paramsSerializer: params => {
+ return stringify(params);
+ }
+ };
+
+ Object.assign(axiosConfig, config);
+
+ return axiosConfig;
+}
diff --git a/ruoyi-plus-soybean/packages/axios/src/shared.ts b/ruoyi-plus-soybean/packages/axios/src/shared.ts
new file mode 100755
index 0000000..1bb68f0
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/axios/src/shared.ts
@@ -0,0 +1,79 @@
+import type { AxiosHeaderValue, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
+import type { ResponseType } from './type';
+
+export function getContentType(config: InternalAxiosRequestConfig) {
+ const contentType: AxiosHeaderValue = config.headers?.['Content-Type'] || 'application/json';
+
+ return contentType;
+}
+
+/**
+ * check if http status is success
+ *
+ * @param status
+ */
+export function isHttpSuccess(status: number) {
+ const isSuccessCode = status >= 200 && status < 300;
+ return isSuccessCode || status === 304;
+}
+
+/**
+ * is response json
+ *
+ * @param response axios response
+ */
+export function isResponseJson(response: AxiosResponse) {
+ const { responseType } = response.config;
+
+ return responseType === 'json' || responseType === undefined;
+}
+
+export async function transformResponse(response: AxiosResponse) {
+ const responseType: ResponseType = (response.config?.responseType as ResponseType) || 'json';
+ if (responseType === 'json') return;
+
+ const isJson = response.headers['content-type']?.includes('application/json');
+ if (!isJson) return;
+
+ if (responseType === 'blob') {
+ await transformBlobToJson(response);
+ }
+
+ if (responseType === 'arrayBuffer') {
+ await transformArrayBufferToJson(response);
+ }
+}
+
+export async function transformBlobToJson(response: AxiosResponse) {
+ try {
+ let data = response.data;
+
+ if (typeof data === 'string') {
+ data = JSON.parse(data);
+ }
+
+ if (Object.prototype.toString.call(data) === '[object Blob]') {
+ const json = await data.text();
+ data = JSON.parse(json);
+ }
+
+ response.data = data;
+ } catch {}
+}
+
+export async function transformArrayBufferToJson(response: AxiosResponse) {
+ try {
+ let data = response.data;
+
+ if (typeof data === 'string') {
+ data = JSON.parse(data);
+ }
+
+ if (Object.prototype.toString.call(data) === '[object ArrayBuffer]') {
+ const json = new TextDecoder().decode(data);
+ data = JSON.parse(json);
+ }
+
+ response.data = data;
+ } catch {}
+}
diff --git a/ruoyi-plus-soybean/packages/axios/src/type.ts b/ruoyi-plus-soybean/packages/axios/src/type.ts
new file mode 100755
index 0000000..0fa6caa
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/axios/src/type.ts
@@ -0,0 +1,130 @@
+import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
+
+export type ContentType =
+ | 'text/html'
+ | 'text/plain'
+ | 'multipart/form-data'
+ | 'application/json'
+ | 'application/x-www-form-urlencoded'
+ | 'application/octet-stream';
+
+export type ResponseTransform<Input = any, Output = any> = (input: Input) => Output | Promise<Output>;
+
+export interface RequestOption<
+ ResponseData,
+ ApiData = ResponseData,
+ State extends Record<string, unknown> = Record<string, unknown>
+> {
+ /**
+ * The default state
+ */
+ defaultState?: State;
+ /**
+ * transform the response data to the api data
+ *
+ * @param response Axios response
+ */
+ transform: ResponseTransform<AxiosResponse<ResponseData>, ApiData>;
+ /**
+ * transform the response data to the api data
+ *
+ * @deprecated use `transform` instead, will be removed in the next major version v3
+ * @param response Axios response
+ */
+ transformBackendResponse: ResponseTransform<AxiosResponse<ResponseData>, ApiData>;
+ /**
+ * The hook before request
+ *
+ * For example: You can add header token in this hook
+ *
+ * @param config Axios config
+ */
+ onRequest: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
+ /**
+ * The hook to check backend response is success or not
+ *
+ * @param response Axios response
+ */
+ isBackendSuccess: (response: AxiosResponse<ResponseData>) => boolean;
+ /**
+ * The hook after backend request fail
+ *
+ * For example: You can handle the expired token in this hook
+ *
+ * @param response Axios response
+ * @param instance Axios instance
+ */
+ onBackendFail: (
+ response: AxiosResponse<ResponseData>,
+ instance: AxiosInstance
+ ) => Promise<AxiosResponse | null> | Promise<void>;
+ /**
+ * The hook to handle error
+ *
+ * For example: You can show error message in this hook
+ *
+ * @param error
+ */
+ onError: (error: AxiosError<ResponseData>) => void | Promise<void>;
+}
+
+interface ResponseMap {
+ blob: Blob;
+ text: string;
+ arrayBuffer: ArrayBuffer;
+ stream: ReadableStream<Uint8Array>;
+ document: Document;
+}
+export type ResponseType = keyof ResponseMap | 'json';
+
+export type MappedType<R extends ResponseType, JsonType = any> = R extends keyof ResponseMap
+ ? ResponseMap[R]
+ : JsonType;
+
+export type CustomAxiosRequestConfig<R extends ResponseType = 'json'> = Omit<AxiosRequestConfig, 'responseType'> & {
+ responseType?: R;
+};
+
+export interface RequestInstanceCommon<State extends Record<string, unknown>> {
+ /**
+ * cancel all request
+ *
+ * if the request provide abort controller sign from config, it will not collect in the abort controller map
+ */
+ cancelAllRequest: () => void;
+ /** you can set custom state in the request instance */
+ state: State;
+}
+
+/** The request instance */
+export interface RequestInstance<ApiData, State extends Record<string, unknown>> extends RequestInstanceCommon<State> {
+ <T extends ApiData = ApiData, R extends ResponseType = 'json'>(
+ config: CustomAxiosRequestConfig<R>
+ ): Promise<MappedType<R, T>>;
+}
+
+export type FlatResponseSuccessData<ResponseData, ApiData> = {
+ data: ApiData;
+ error: null;
+ response: AxiosResponse<ResponseData>;
+};
+
+export type FlatResponseFailData<ResponseData> = {
+ data: null;
+ error: AxiosError<ResponseData>;
+ response: AxiosResponse<ResponseData>;
+};
+
+export type FlatResponseData<ResponseData, ApiData> =
+ | FlatResponseSuccessData<ResponseData, ApiData>
+ | FlatResponseFailData<ResponseData>;
+
+export interface FlatRequestInstance<
+ ResponseData,
+ ApiData,
+ State extends Record<string, unknown>
+> extends RequestInstanceCommon<State> {
+ <T extends ApiData = ApiData, R extends ResponseType = 'json'>(
+ config: CustomAxiosRequestConfig<R>
+ ): Promise<FlatResponseData<ResponseData, MappedType<R, T>>>;
+}
diff --git a/ruoyi-plus-soybean/packages/axios/tsconfig.json b/ruoyi-plus-soybean/packages/axios/tsconfig.json
new file mode 100755
index 0000000..5823ed5
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/axios/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "jsx": "preserve",
+ "lib": ["DOM", "ESNext"],
+ "baseUrl": ".",
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "types": ["node"],
+ "strict": true,
+ "strictNullChecks": true,
+ "noUnusedLocals": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/ruoyi-plus-soybean/packages/color/package.json b/ruoyi-plus-soybean/packages/color/package.json
new file mode 100755
index 0000000..2668a42
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/color/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@sa/color",
+ "version": "2.0.2",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "typesVersions": {
+ "*": {
+ "*": ["./src/*"]
+ }
+ },
+ "dependencies": {
+ "@sa/utils": "workspace:*",
+ "colord": "2.9.3"
+ }
+}
diff --git a/ruoyi-plus-soybean/packages/color/src/constant/index.ts b/ruoyi-plus-soybean/packages/color/src/constant/index.ts
new file mode 100755
index 0000000..cefbcdb
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/color/src/constant/index.ts
@@ -0,0 +1,2 @@
+export * from './name';
+export * from './palette';
diff --git a/ruoyi-plus-soybean/packages/color/src/constant/name.ts b/ruoyi-plus-soybean/packages/color/src/constant/name.ts
new file mode 100755
index 0000000..36648c3
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/color/src/constant/name.ts
@@ -0,0 +1,1579 @@
+export const colorNames: [hex: string, name: string][] = [
+ ['#000000', 'Black'],
+ ['#000080', 'Navy Blue'],
+ ['#0000c8', 'Dark Blue'],
+ ['#0000ff', 'Blue'],
+ ['#000741', 'Stratos'],
+ ['#001b1c', 'Swamp'],
+ ['#002387', 'Resolution Blue'],
+ ['#002900', 'Deep Fir'],
+ ['#002e20', 'Burnham'],
+ ['#002fa7', 'International Klein Blue'],
+ ['#003153', 'Prussian Blue'],
+ ['#003366', 'Midnight Blue'],
+ ['#003399', 'Smalt'],
+ ['#003532', 'Deep Teal'],
+ ['#003e40', 'Cyprus'],
+ ['#004620', 'Kaitoke Green'],
+ ['#0047ab', 'Cobalt'],
+ ['#004816', 'Crusoe'],
+ ['#004950', 'Sherpa Blue'],
+ ['#0056a7', 'Endeavour'],
+ ['#00581a', 'Camarone'],
+ ['#0066cc', 'Science Blue'],
+ ['#0066ff', 'Blue Ribbon'],
+ ['#00755e', 'Tropical Rain Forest'],
+ ['#0076a3', 'Allports'],
+ ['#007ba7', 'Deep Cerulean'],
+ ['#007ec7', 'Lochmara'],
+ ['#007fff', 'Azure Radiance'],
+ ['#008080', 'Teal'],
+ ['#0095b6', 'Bondi Blue'],
+ ['#009dc4', 'Pacific Blue'],
+ ['#00a693', 'Persian Green'],
+ ['#00a86b', 'Jade'],
+ ['#00cc99', 'Caribbean Green'],
+ ['#00cccc', "Robin's Egg Blue"],
+ ['#00ff00', 'Green'],
+ ['#00ff7f', 'Spring Green'],
+ ['#00ffff', 'Cyan Aqua'],
+ ['#010d1a', 'Blue Charcoal'],
+ ['#011635', 'Midnight'],
+ ['#011d13', 'Holly'],
+ ['#012731', 'Daintree'],
+ ['#01361c', 'Cardin Green'],
+ ['#01371a', 'County Green'],
+ ['#013e62', 'Astronaut Blue'],
+ ['#013f6a', 'Regal Blue'],
+ ['#014b43', 'Aqua Deep'],
+ ['#015e85', 'Orient'],
+ ['#016162', 'Blue Stone'],
+ ['#016d39', 'Fun Green'],
+ ['#01796f', 'Pine Green'],
+ ['#017987', 'Blue Lagoon'],
+ ['#01826b', 'Deep Sea'],
+ ['#01a368', 'Green Haze'],
+ ['#022d15', 'English Holly'],
+ ['#02402c', 'Sherwood Green'],
+ ['#02478e', 'Congress Blue'],
+ ['#024e46', 'Evening Sea'],
+ ['#026395', 'Bahama Blue'],
+ ['#02866f', 'Observatory'],
+ ['#02a4d3', 'Cerulean'],
+ ['#03163c', 'Tangaroa'],
+ ['#032b52', 'Green Vogue'],
+ ['#036a6e', 'Mosque'],
+ ['#041004', 'Midnight Moss'],
+ ['#041322', 'Black Pearl'],
+ ['#042e4c', 'Blue Whale'],
+ ['#044022', 'Zuccini'],
+ ['#044259', 'Teal Blue'],
+ ['#051040', 'Deep Cove'],
+ ['#051657', 'Gulf Blue'],
+ ['#055989', 'Venice Blue'],
+ ['#056f57', 'Watercourse'],
+ ['#062a78', 'Catalina Blue'],
+ ['#063537', 'Tiber'],
+ ['#069b81', 'Gossamer'],
+ ['#06a189', 'Niagara'],
+ ['#073a50', 'Tarawera'],
+ ['#080110', 'Jaguar'],
+ ['#081910', 'Black Bean'],
+ ['#082567', 'Deep Sapphire'],
+ ['#088370', 'Elf Green'],
+ ['#08e8de', 'Bright Turquoise'],
+ ['#092256', 'Downriver'],
+ ['#09230f', 'Palm Green'],
+ ['#09255d', 'Madison'],
+ ['#093624', 'Bottle Green'],
+ ['#095859', 'Deep Sea Green'],
+ ['#097f4b', 'Salem'],
+ ['#0a001c', 'Black Russian'],
+ ['#0a480d', 'Dark Fern'],
+ ['#0a6906', 'Japanese Laurel'],
+ ['#0a6f75', 'Atoll'],
+ ['#0b0b0b', 'Cod Gray'],
+ ['#0b0f08', 'Marshland'],
+ ['#0b1107', 'Gordons Green'],
+ ['#0b1304', 'Black Forest'],
+ ['#0b6207', 'San Felix'],
+ ['#0bda51', 'Malachite'],
+ ['#0c0b1d', 'Ebony'],
+ ['#0c0d0f', 'Woodsmoke'],
+ ['#0c1911', 'Racing Green'],
+ ['#0c7a79', 'Surfie Green'],
+ ['#0c8990', 'Blue Chill'],
+ ['#0d0332', 'Black Rock'],
+ ['#0d1117', 'Bunker'],
+ ['#0d1c19', 'Aztec'],
+ ['#0d2e1c', 'Bush'],
+ ['#0e0e18', 'Cinder'],
+ ['#0e2a30', 'Firefly'],
+ ['#0f2d9e', 'Torea Bay'],
+ ['#10121d', 'Vulcan'],
+ ['#101405', 'Green Waterloo'],
+ ['#105852', 'Eden'],
+ ['#110c6c', 'Arapawa'],
+ ['#120a8f', 'Ultramarine'],
+ ['#123447', 'Elephant'],
+ ['#126b40', 'Jewel'],
+ ['#130000', 'Diesel'],
+ ['#130a06', 'Asphalt'],
+ ['#13264d', 'Blue Zodiac'],
+ ['#134f19', 'Parsley'],
+ ['#140600', 'Nero'],
+ ['#1450aa', 'Tory Blue'],
+ ['#151f4c', 'Bunting'],
+ ['#1560bd', 'Denim'],
+ ['#15736b', 'Genoa'],
+ ['#161928', 'Mirage'],
+ ['#161d10', 'Hunter Green'],
+ ['#162a40', 'Big Stone'],
+ ['#163222', 'Celtic'],
+ ['#16322c', 'Timber Green'],
+ ['#163531', 'Gable Green'],
+ ['#171f04', 'Pine Tree'],
+ ['#175579', 'Chathams Blue'],
+ ['#182d09', 'Deep Forest Green'],
+ ['#18587a', 'Blumine'],
+ ['#19330e', 'Palm Leaf'],
+ ['#193751', 'Nile Blue'],
+ ['#1959a8', 'Fun Blue'],
+ ['#1a1a68', 'Lucky Point'],
+ ['#1ab385', 'Mountain Meadow'],
+ ['#1b0245', 'Tolopea'],
+ ['#1b1035', 'Haiti'],
+ ['#1b127b', 'Deep Koamaru'],
+ ['#1b1404', 'Acadia'],
+ ['#1b2f11', 'Seaweed'],
+ ['#1b3162', 'Biscay'],
+ ['#1b659d', 'Matisse'],
+ ['#1c1208', 'Crowshead'],
+ ['#1c1e13', 'Rangoon Green'],
+ ['#1c39bb', 'Persian Blue'],
+ ['#1c402e', 'Everglade'],
+ ['#1c7c7d', 'Elm'],
+ ['#1d6142', 'Green Pea'],
+ ['#1e0f04', 'Creole'],
+ ['#1e1609', 'Karaka'],
+ ['#1e1708', 'El Paso'],
+ ['#1e385b', 'Cello'],
+ ['#1e433c', 'Te Papa Green'],
+ ['#1e90ff', 'Dodger Blue'],
+ ['#1e9ab0', 'Eastern Blue'],
+ ['#1f120f', 'Night Rider'],
+ ['#1fc2c2', 'Java'],
+ ['#20208d', 'Jacksons Purple'],
+ ['#202e54', 'Cloud Burst'],
+ ['#204852', 'Blue Dianne'],
+ ['#211a0e', 'Eternity'],
+ ['#220878', 'Deep Blue'],
+ ['#228b22', 'Forest Green'],
+ ['#233418', 'Mallard'],
+ ['#240a40', 'Violet'],
+ ['#240c02', 'Kilamanjaro'],
+ ['#242a1d', 'Log Cabin'],
+ ['#242e16', 'Black Olive'],
+ ['#24500f', 'Green House'],
+ ['#251607', 'Graphite'],
+ ['#251706', 'Cannon Black'],
+ ['#251f4f', 'Port Gore'],
+ ['#25272c', 'Shark'],
+ ['#25311c', 'Green Kelp'],
+ ['#2596d1', 'Curious Blue'],
+ ['#260368', 'Paua'],
+ ['#26056a', 'Paris M'],
+ ['#261105', 'Wood Bark'],
+ ['#261414', 'Gondola'],
+ ['#262335', 'Steel Gray'],
+ ['#26283b', 'Ebony Clay'],
+ ['#273a81', 'Bay Of Many'],
+ ['#27504b', 'Plantation'],
+ ['#278a5b', 'Eucalyptus'],
+ ['#281e15', 'Oil'],
+ ['#283a77', 'Astronaut'],
+ ['#286acd', 'Mariner'],
+ ['#290c5e', 'Violent Violet'],
+ ['#292130', 'Bastille'],
+ ['#292319', 'Zeus'],
+ ['#292937', 'Charade'],
+ ['#297b9a', 'Jelly Bean'],
+ ['#29ab87', 'Jungle Green'],
+ ['#2a0359', 'Cherry Pie'],
+ ['#2a140e', 'Coffee Bean'],
+ ['#2a2630', 'Baltic Sea'],
+ ['#2a380b', 'Turtle Green'],
+ ['#2a52be', 'Cerulean Blue'],
+ ['#2b0202', 'Sepia Black'],
+ ['#2b194f', 'Valhalla'],
+ ['#2b3228', 'Heavy Metal'],
+ ['#2c0e8c', 'Blue Gem'],
+ ['#2c1632', 'Revolver'],
+ ['#2c2133', 'Bleached Cedar'],
+ ['#2c8c84', 'Lochinvar'],
+ ['#2d2510', 'Mikado'],
+ ['#2d383a', 'Outer Space'],
+ ['#2d569b', 'St Tropaz'],
+ ['#2e0329', 'Jacaranda'],
+ ['#2e1905', 'Jacko Bean'],
+ ['#2e3222', 'Rangitoto'],
+ ['#2e3f62', 'Rhino'],
+ ['#2e8b57', 'Sea Green'],
+ ['#2ebfd4', 'Scooter'],
+ ['#2f270e', 'Onion'],
+ ['#2f3cb3', 'Governor Bay'],
+ ['#2f519e', 'Sapphire'],
+ ['#2f5a57', 'Spectra'],
+ ['#2f6168', 'Casal'],
+ ['#300529', 'Melanzane'],
+ ['#301f1e', 'Cocoa Brown'],
+ ['#302a0f', 'Woodrush'],
+ ['#304b6a', 'San Juan'],
+ ['#30d5c8', 'Turquoise'],
+ ['#311c17', 'Eclipse'],
+ ['#314459', 'Pickled Bluewood'],
+ ['#315ba1', 'Azure'],
+ ['#31728d', 'Calypso'],
+ ['#317d82', 'Paradiso'],
+ ['#32127a', 'Persian Indigo'],
+ ['#32293a', 'Blackcurrant'],
+ ['#323232', 'Mine Shaft'],
+ ['#325d52', 'Stromboli'],
+ ['#327c14', 'Bilbao'],
+ ['#327da0', 'Astral'],
+ ['#33036b', 'Christalle'],
+ ['#33292f', 'Thunder'],
+ ['#33cc99', 'Shamrock'],
+ ['#341515', 'Tamarind'],
+ ['#350036', 'Mardi Gras'],
+ ['#350e42', 'Valentino'],
+ ['#350e57', 'Jagger'],
+ ['#353542', 'Tuna'],
+ ['#354e8c', 'Chambray'],
+ ['#363050', 'Martinique'],
+ ['#363534', 'Tuatara'],
+ ['#363c0d', 'Waiouru'],
+ ['#36747d', 'Ming'],
+ ['#368716', 'La Palma'],
+ ['#370202', 'Chocolate'],
+ ['#371d09', 'Clinker'],
+ ['#37290e', 'Brown Tumbleweed'],
+ ['#373021', 'Birch'],
+ ['#377475', 'Oracle'],
+ ['#380474', 'Blue Diamond'],
+ ['#381a51', 'Grape'],
+ ['#383533', 'Dune'],
+ ['#384555', 'Oxford Blue'],
+ ['#384910', 'Clover'],
+ ['#394851', 'Limed Spruce'],
+ ['#396413', 'Dell'],
+ ['#3a0020', 'Toledo'],
+ ['#3a2010', 'Sambuca'],
+ ['#3a2a6a', 'Jacarta'],
+ ['#3a686c', 'William'],
+ ['#3a6a47', 'Killarney'],
+ ['#3ab09e', 'Keppel'],
+ ['#3b000b', 'Temptress'],
+ ['#3b0910', 'Aubergine'],
+ ['#3b1f1f', 'Jon'],
+ ['#3b2820', 'Treehouse'],
+ ['#3b7a57', 'Amazon'],
+ ['#3b91b4', 'Boston Blue'],
+ ['#3c0878', 'Windsor'],
+ ['#3c1206', 'Rebel'],
+ ['#3c1f76', 'Meteorite'],
+ ['#3c2005', 'Dark Ebony'],
+ ['#3c3910', 'Camouflage'],
+ ['#3c4151', 'Bright Gray'],
+ ['#3c4443', 'Cape Cod'],
+ ['#3c493a', 'Lunar Green'],
+ ['#3d0c02', 'Bean '],
+ ['#3d2b1f', 'Bistre'],
+ ['#3d7d52', 'Goblin'],
+ ['#3e0480', 'Kingfisher Daisy'],
+ ['#3e1c14', 'Cedar'],
+ ['#3e2b23', 'English Walnut'],
+ ['#3e2c1c', 'Black Marlin'],
+ ['#3e3a44', 'Ship Gray'],
+ ['#3eabbf', 'Pelorous'],
+ ['#3f2109', 'Bronze'],
+ ['#3f2500', 'Cola'],
+ ['#3f3002', 'Madras'],
+ ['#3f307f', 'Minsk'],
+ ['#3f4c3a', 'Cabbage Pont'],
+ ['#3f583b', 'Tom Thumb'],
+ ['#3f5d53', 'Mineral Green'],
+ ['#3fc1aa', 'Puerto Rico'],
+ ['#3fff00', 'Harlequin'],
+ ['#401801', 'Brown Pod'],
+ ['#40291d', 'Cork'],
+ ['#403b38', 'Masala'],
+ ['#403d19', 'Thatch Green'],
+ ['#405169', 'Fiord'],
+ ['#40826d', 'Viridian'],
+ ['#40a860', 'Chateau Green'],
+ ['#410056', 'Ripe Plum'],
+ ['#411f10', 'Paco'],
+ ['#412010', 'Deep Oak'],
+ ['#413c37', 'Merlin'],
+ ['#414257', 'Gun Powder'],
+ ['#414c7d', 'East Bay'],
+ ['#4169e1', 'Royal Blue'],
+ ['#41aa78', 'Ocean Green'],
+ ['#420303', 'Burnt Maroon'],
+ ['#423921', 'Lisbon Brown'],
+ ['#427977', 'Faded Jade'],
+ ['#431560', 'Scarlet Gum'],
+ ['#433120', 'Iroko'],
+ ['#433e37', 'Armadillo'],
+ ['#434c59', 'River Bed'],
+ ['#436a0d', 'Green Leaf'],
+ ['#44012d', 'Barossa'],
+ ['#441d00', 'Morocco Brown'],
+ ['#444954', 'Mako'],
+ ['#454936', 'Kelp'],
+ ['#456cac', 'San Marino'],
+ ['#45b1e8', 'Picton Blue'],
+ ['#460b41', 'Loulou'],
+ ['#462425', 'Crater Brown'],
+ ['#465945', 'Gray Asparagus'],
+ ['#4682b4', 'Steel Blue'],
+ ['#480404', 'Rustic Red'],
+ ['#480607', 'Bulgarian Rose'],
+ ['#480656', 'Clairvoyant'],
+ ['#481c1c', 'Cocoa Bean'],
+ ['#483131', 'Woody Brown'],
+ ['#483c32', 'Taupe'],
+ ['#49170c', 'Van Cleef'],
+ ['#492615', 'Brown Derby'],
+ ['#49371b', 'Metallic Bronze'],
+ ['#495400', 'Verdun Green'],
+ ['#496679', 'Blue Bayoux'],
+ ['#497183', 'Bismark'],
+ ['#4a2a04', 'Bracken'],
+ ['#4a3004', 'Deep Bronze'],
+ ['#4a3c30', 'Mondo'],
+ ['#4a4244', 'Tundora'],
+ ['#4a444b', 'Gravel'],
+ ['#4a4e5a', 'Trout'],
+ ['#4b0082', 'Pigment Indigo'],
+ ['#4b5d52', 'Nandor'],
+ ['#4c3024', 'Saddle'],
+ ['#4c4f56', 'Abbey'],
+ ['#4d0135', 'Blackberry'],
+ ['#4d0a18', 'Cab Sav'],
+ ['#4d1e01', 'Indian Tan'],
+ ['#4d282d', 'Cowboy'],
+ ['#4d282e', 'Livid Brown'],
+ ['#4d3833', 'Rock'],
+ ['#4d3d14', 'Punga'],
+ ['#4d400f', 'Bronzetone'],
+ ['#4d5328', 'Woodland'],
+ ['#4e0606', 'Mahogany'],
+ ['#4e2a5a', 'Bossanova'],
+ ['#4e3b41', 'Matterhorn'],
+ ['#4e420c', 'Bronze Olive'],
+ ['#4e4562', 'Mulled Wine'],
+ ['#4e6649', 'Axolotl'],
+ ['#4e7f9e', 'Wedgewood'],
+ ['#4eabd1', 'Shakespeare'],
+ ['#4f1c70', 'Honey Flower'],
+ ['#4f2398', 'Daisy Bush'],
+ ['#4f69c6', 'Indigo'],
+ ['#4f7942', 'Fern Green'],
+ ['#4f9d5d', 'Fruit Salad'],
+ ['#4fa83d', 'Apple'],
+ ['#504351', 'Mortar'],
+ ['#507096', 'Kashmir Blue'],
+ ['#507672', 'Cutty Sark'],
+ ['#50c878', 'Emerald'],
+ ['#514649', 'Emperor'],
+ ['#516e3d', 'Chalet Green'],
+ ['#517c66', 'Como'],
+ ['#51808f', 'Smalt Blue'],
+ ['#52001f', 'Castro'],
+ ['#520c17', 'Maroon Oak'],
+ ['#523c94', 'Gigas'],
+ ['#533455', 'Voodoo'],
+ ['#534491', 'Victoria'],
+ ['#53824b', 'Hippie Green'],
+ ['#541012', 'Heath'],
+ ['#544333', 'Judge Gray'],
+ ['#54534d', 'Fuscous Gray'],
+ ['#549019', 'Vida Loca'],
+ ['#55280c', 'Cioccolato'],
+ ['#555b10', 'Saratoga'],
+ ['#556d56', 'Finlandia'],
+ ['#5590d9', 'Havelock Blue'],
+ ['#56b4be', 'Fountain Blue'],
+ ['#578363', 'Spring Leaves'],
+ ['#583401', 'Saddle Brown'],
+ ['#585562', 'Scarpa Flow'],
+ ['#587156', 'Cactus'],
+ ['#589aaf', 'Hippie Blue'],
+ ['#591d35', 'Wine Berry'],
+ ['#592804', 'Brown Bramble'],
+ ['#593737', 'Congo Brown'],
+ ['#594433', 'Millbrook'],
+ ['#5a6e9c', 'Waikawa Gray'],
+ ['#5a87a0', 'Horizon'],
+ ['#5b3013', 'Jambalaya'],
+ ['#5c0120', 'Bordeaux'],
+ ['#5c0536', 'Mulberry Wood'],
+ ['#5c2e01', 'Carnaby Tan'],
+ ['#5c5d75', 'Comet'],
+ ['#5d1e0f', 'Redwood'],
+ ['#5d4c51', 'Don Juan'],
+ ['#5d5c58', 'Chicago'],
+ ['#5d5e37', 'Verdigris'],
+ ['#5d7747', 'Dingley'],
+ ['#5da19f', 'Breaker Bay'],
+ ['#5e483e', 'Kabul'],
+ ['#5e5d3b', 'Hemlock'],
+ ['#5f3d26', 'Irish Coffee'],
+ ['#5f5f6e', 'Mid Gray'],
+ ['#5f6672', 'Shuttle Gray'],
+ ['#5fa777', 'Aqua Forest'],
+ ['#5fb3ac', 'Tradewind'],
+ ['#604913', 'Horses Neck'],
+ ['#605b73', 'Smoky'],
+ ['#606e68', 'Corduroy'],
+ ['#6093d1', 'Danube'],
+ ['#612718', 'Espresso'],
+ ['#614051', 'Eggplant'],
+ ['#615d30', 'Costa Del Sol'],
+ ['#61845f', 'Glade Green'],
+ ['#622f30', 'Buccaneer'],
+ ['#623f2d', 'Quincy'],
+ ['#624e9a', 'Butterfly Bush'],
+ ['#625119', 'West Coast'],
+ ['#626649', 'Finch'],
+ ['#639a8f', 'Patina'],
+ ['#63b76c', 'Fern'],
+ ['#6456b7', 'Blue Violet'],
+ ['#646077', 'Dolphin'],
+ ['#646463', 'Storm Dust'],
+ ['#646a54', 'Siam'],
+ ['#646e75', 'Nevada'],
+ ['#6495ed', 'Cornflower Blue'],
+ ['#64ccdb', 'Viking'],
+ ['#65000b', 'Rosewood'],
+ ['#651a14', 'Cherrywood'],
+ ['#652dc1', 'Purple Heart'],
+ ['#657220', 'Fern Frond'],
+ ['#65745d', 'Willow Grove'],
+ ['#65869f', 'Hoki'],
+ ['#660045', 'Pompadour'],
+ ['#660099', 'Purple'],
+ ['#66023c', 'Tyrian Purple'],
+ ['#661010', 'Dark Tan'],
+ ['#66b58f', 'Silver Tree'],
+ ['#66ff00', 'Bright Green'],
+ ['#66ff66', 'Screamin Green'],
+ ['#67032d', 'Black Rose'],
+ ['#675fa6', 'Scampi'],
+ ['#676662', 'Ironside Gray'],
+ ['#678975', 'Viridian Green'],
+ ['#67a712', 'Christi'],
+ ['#683600', 'Nutmeg Wood Finish'],
+ ['#685558', 'Zambezi'],
+ ['#685e6e', 'Salt Box'],
+ ['#692545', 'Tawny Port'],
+ ['#692d54', 'Finn'],
+ ['#695f62', 'Scorpion'],
+ ['#697e9a', 'Lynch'],
+ ['#6a442e', 'Spice'],
+ ['#6a5d1b', 'Himalaya'],
+ ['#6a6051', 'Soya Bean'],
+ ['#6b2a14', 'Hairy Heath'],
+ ['#6b3fa0', 'Royal Purple'],
+ ['#6b4e31', 'Shingle Fawn'],
+ ['#6b5755', 'Dorado'],
+ ['#6b8ba2', 'Bermuda Gray'],
+ ['#6b8e23', 'Olive Drab'],
+ ['#6c3082', 'Eminence'],
+ ['#6cdae7', 'Turquoise Blue'],
+ ['#6d0101', 'Lonestar'],
+ ['#6d5e54', 'Pine Cone'],
+ ['#6d6c6c', 'Dove Gray'],
+ ['#6d9292', 'Juniper'],
+ ['#6d92a1', 'Gothic'],
+ ['#6e0902', 'Red Oxide'],
+ ['#6e1d14', 'Moccaccino'],
+ ['#6e4826', 'Pickled Bean'],
+ ['#6e4b26', 'Dallas'],
+ ['#6e6d57', 'Kokoda'],
+ ['#6e7783', 'Pale Sky'],
+ ['#6f440c', 'Cafe Royale'],
+ ['#6f6a61', 'Flint'],
+ ['#6f8e63', 'Highland'],
+ ['#6f9d02', 'Limeade'],
+ ['#6fd0c5', 'Downy'],
+ ['#701c1c', 'Persian Plum'],
+ ['#704214', 'Sepia'],
+ ['#704a07', 'Antique Bronze'],
+ ['#704f50', 'Ferra'],
+ ['#706555', 'Coffee'],
+ ['#708090', 'Slate Gray'],
+ ['#711a00', 'Cedar Wood Finish'],
+ ['#71291d', 'Metallic Copper'],
+ ['#714693', 'Affair'],
+ ['#714ab2', 'Studio'],
+ ['#715d47', 'Tobacco Brown'],
+ ['#716338', 'Yellow Metal'],
+ ['#716b56', 'Peat'],
+ ['#716e10', 'Olivetone'],
+ ['#717486', 'Storm Gray'],
+ ['#718080', 'Sirocco'],
+ ['#71d9e2', 'Aquamarine Blue'],
+ ['#72010f', 'Venetian Red'],
+ ['#724a2f', 'Old Copper'],
+ ['#726d4e', 'Go Ben'],
+ ['#727b89', 'Raven'],
+ ['#731e8f', 'Seance'],
+ ['#734a12', 'Raw Umber'],
+ ['#736c9f', 'Kimberly'],
+ ['#736d58', 'Crocodile'],
+ ['#737829', 'Crete'],
+ ['#738678', 'Xanadu'],
+ ['#74640d', 'Spicy Mustard'],
+ ['#747d63', 'Limed Ash'],
+ ['#747d83', 'Rolling Stone'],
+ ['#748881', 'Blue Smoke'],
+ ['#749378', 'Laurel'],
+ ['#74c365', 'Mantis'],
+ ['#755a57', 'Russett'],
+ ['#7563a8', 'Deluge'],
+ ['#76395d', 'Cosmic'],
+ ['#7666c6', 'Blue Marguerite'],
+ ['#76bd17', 'Lima'],
+ ['#76d7ea', 'Sky Blue'],
+ ['#770f05', 'Dark Burgundy'],
+ ['#771f1f', 'Crown Of Thorns'],
+ ['#773f1a', 'Walnut'],
+ ['#776f61', 'Pablo'],
+ ['#778120', 'Pacifika'],
+ ['#779e86', 'Oxley'],
+ ['#77dd77', 'Pastel Green'],
+ ['#780109', 'Japanese Maple'],
+ ['#782d19', 'Mocha'],
+ ['#782f16', 'Peanut'],
+ ['#78866b', 'Camouflage Green'],
+ ['#788a25', 'Wasabi'],
+ ['#788bba', 'Ship Cove'],
+ ['#78a39c', 'Sea Nymph'],
+ ['#795d4c', 'Roman Coffee'],
+ ['#796878', 'Old Lavender'],
+ ['#796989', 'Rum'],
+ ['#796a78', 'Fedora'],
+ ['#796d62', 'Sandstone'],
+ ['#79deec', 'Spray'],
+ ['#7a013a', 'Siren'],
+ ['#7a58c1', 'Fuchsia Blue'],
+ ['#7a7a7a', 'Boulder'],
+ ['#7a89b8', 'Wild Blue Yonder'],
+ ['#7ac488', 'De York'],
+ ['#7b3801', 'Red Beech'],
+ ['#7b3f00', 'Cinnamon'],
+ ['#7b6608', 'Yukon Gold'],
+ ['#7b7874', 'Tapa'],
+ ['#7b7c94', 'Waterloo '],
+ ['#7b8265', 'Flax Smoke'],
+ ['#7b9f80', 'Amulet'],
+ ['#7ba05b', 'Asparagus'],
+ ['#7c1c05', 'Kenyan Copper'],
+ ['#7c7631', 'Pesto'],
+ ['#7c778a', 'Topaz'],
+ ['#7c7b7a', 'Concord'],
+ ['#7c7b82', 'Jumbo'],
+ ['#7c881a', 'Trendy Green'],
+ ['#7ca1a6', 'Gumbo'],
+ ['#7cb0a1', 'Acapulco'],
+ ['#7cb7bb', 'Neptune'],
+ ['#7d2c14', 'Pueblo'],
+ ['#7da98d', 'Bay Leaf'],
+ ['#7dc8f7', 'Malibu'],
+ ['#7dd8c6', 'Bermuda'],
+ ['#7e3a15', 'Copper Canyon'],
+ ['#7f1734', 'Claret'],
+ ['#7f3a02', 'Peru Tan'],
+ ['#7f626d', 'Falcon'],
+ ['#7f7589', 'Mobster'],
+ ['#7f76d3', 'Moody Blue'],
+ ['#7fff00', 'Chartreuse'],
+ ['#7fffd4', 'Aquamarine'],
+ ['#800000', 'Maroon'],
+ ['#800b47', 'Rose Bud Cherry'],
+ ['#801818', 'Falu Red'],
+ ['#80341f', 'Red Robin'],
+ ['#803790', 'Vivid Violet'],
+ ['#80461b', 'Russet'],
+ ['#807e79', 'Friar Gray'],
+ ['#808000', 'Olive'],
+ ['#808080', 'Gray'],
+ ['#80b3ae', 'Gulf Stream'],
+ ['#80b3c4', 'Glacier'],
+ ['#80ccea', 'Seagull'],
+ ['#81422c', 'Nutmeg'],
+ ['#816e71', 'Spicy Pink'],
+ ['#817377', 'Empress'],
+ ['#819885', 'Spanish Green'],
+ ['#826f65', 'Sand Dune'],
+ ['#828685', 'Gunsmoke'],
+ ['#828f72', 'Battleship Gray'],
+ ['#831923', 'Merlot'],
+ ['#837050', 'Shadow'],
+ ['#83aa5d', 'Chelsea Cucumber'],
+ ['#83d0c6', 'Monte Carlo'],
+ ['#843179', 'Plum'],
+ ['#84a0a0', 'Granny Smith'],
+ ['#8581d9', 'Chetwode Blue'],
+ ['#858470', 'Bandicoot'],
+ ['#859faf', 'Bali Hai'],
+ ['#85c4cc', 'Half Baked'],
+ ['#860111', 'Red Devil'],
+ ['#863c3c', 'Lotus'],
+ ['#86483c', 'Ironstone'],
+ ['#864d1e', 'Bull Shot'],
+ ['#86560a', 'Rusty Nail'],
+ ['#868974', 'Bitter'],
+ ['#86949f', 'Regent Gray'],
+ ['#871550', 'Disco'],
+ ['#87756e', 'Americano'],
+ ['#877c7b', 'Hurricane'],
+ ['#878d91', 'Oslo Gray'],
+ ['#87ab39', 'Sushi'],
+ ['#885342', 'Spicy Mix'],
+ ['#886221', 'Kumera'],
+ ['#888387', 'Suva Gray'],
+ ['#888d65', 'Avocado'],
+ ['#893456', 'Camelot'],
+ ['#893843', 'Solid Pink'],
+ ['#894367', 'Cannon Pink'],
+ ['#897d6d', 'Makara'],
+ ['#8a3324', 'Burnt Umber'],
+ ['#8a73d6', 'True V'],
+ ['#8a8360', 'Clay Creek'],
+ ['#8a8389', 'Monsoon'],
+ ['#8a8f8a', 'Stack'],
+ ['#8ab9f1', 'Jordy Blue'],
+ ['#8b00ff', 'Electric Violet'],
+ ['#8b0723', 'Monarch'],
+ ['#8b6b0b', 'Corn Harvest'],
+ ['#8b8470', 'Olive Haze'],
+ ['#8b847e', 'Schooner'],
+ ['#8b8680', 'Natural Gray'],
+ ['#8b9c90', 'Mantle'],
+ ['#8b9fee', 'Portage'],
+ ['#8ba690', 'Envy'],
+ ['#8ba9a5', 'Cascade'],
+ ['#8be6d8', 'Riptide'],
+ ['#8c055e', 'Cardinal Pink'],
+ ['#8c472f', 'Mule Fawn'],
+ ['#8c5738', 'Potters Clay'],
+ ['#8c6495', 'Trendy Pink'],
+ ['#8d0226', 'Paprika'],
+ ['#8d3d38', 'Sanguine Brown'],
+ ['#8d3f3f', 'Tosca'],
+ ['#8d7662', 'Cement'],
+ ['#8d8974', 'Granite Green'],
+ ['#8d90a1', 'Manatee'],
+ ['#8da8cc', 'Polo Blue'],
+ ['#8e0000', 'Red Berry'],
+ ['#8e4d1e', 'Rope'],
+ ['#8e6f70', 'Opium'],
+ ['#8e775e', 'Domino'],
+ ['#8e8190', 'Mamba'],
+ ['#8eabc1', 'Nepal'],
+ ['#8f021c', 'Pohutukawa'],
+ ['#8f3e33', 'El Salva'],
+ ['#8f4b0e', 'Korma'],
+ ['#8f8176', 'Squirrel'],
+ ['#8fd6b4', 'Vista Blue'],
+ ['#900020', 'Burgundy'],
+ ['#901e1e', 'Old Brick'],
+ ['#907874', 'Hemp'],
+ ['#907b71', 'Almond Frost'],
+ ['#908d39', 'Sycamore'],
+ ['#92000a', 'Sangria'],
+ ['#924321', 'Cumin'],
+ ['#926f5b', 'Beaver'],
+ ['#928573', 'Stonewall'],
+ ['#928590', 'Venus'],
+ ['#9370db', 'Medium Purple'],
+ ['#93ccea', 'Cornflower'],
+ ['#93dfb8', 'Algae Green'],
+ ['#944747', 'Copper Rust'],
+ ['#948771', 'Arrowtown'],
+ ['#950015', 'Scarlett'],
+ ['#956387', 'Strikemaster'],
+ ['#959396', 'Mountain Mist'],
+ ['#960018', 'Carmine'],
+ ['#964b00', 'Brown'],
+ ['#967059', 'Leather'],
+ ['#9678b6', "Purple Mountain's Majesty"],
+ ['#967bb6', 'Lavender Purple'],
+ ['#96a8a1', 'Pewter'],
+ ['#96bbab', 'Summer Green'],
+ ['#97605d', 'Au Chico'],
+ ['#9771b5', 'Wisteria'],
+ ['#97cd2d', 'Atlantis'],
+ ['#983d61', 'Vin Rouge'],
+ ['#9874d3', 'Lilac Bush'],
+ ['#98777b', 'Bazaar'],
+ ['#98811b', 'Hacienda'],
+ ['#988d77', 'Pale Oyster'],
+ ['#98ff98', 'Mint Green'],
+ ['#990066', 'Fresh Eggplant'],
+ ['#991199', 'Violet Eggplant'],
+ ['#991613', 'Tamarillo'],
+ ['#991b07', 'Totem Pole'],
+ ['#996666', 'Copper Rose'],
+ ['#9966cc', 'Amethyst'],
+ ['#997a8d', 'Mountbatten Pink'],
+ ['#9999cc', 'Blue Bell'],
+ ['#9a3820', 'Prairie Sand'],
+ ['#9a6e61', 'Toast'],
+ ['#9a9577', 'Gurkha'],
+ ['#9ab973', 'Olivine'],
+ ['#9ac2b8', 'Shadow Green'],
+ ['#9b4703', 'Oregon'],
+ ['#9b9e8f', 'Lemon Grass'],
+ ['#9c3336', 'Stiletto'],
+ ['#9d5616', 'Hawaiian Tan'],
+ ['#9dacb7', 'Gull Gray'],
+ ['#9dc209', 'Pistachio'],
+ ['#9de093', 'Granny Smith Apple'],
+ ['#9de5ff', 'Anakiwa'],
+ ['#9e5302', 'Chelsea Gem'],
+ ['#9e5b40', 'Sepia Skin'],
+ ['#9ea587', 'Sage'],
+ ['#9ea91f', 'Citron'],
+ ['#9eb1cd', 'Rock Blue'],
+ ['#9edee0', 'Morning Glory'],
+ ['#9f381d', 'Cognac'],
+ ['#9f821c', 'Reef Gold'],
+ ['#9f9f9c', 'Star Dust'],
+ ['#9fa0b1', 'Santas Gray'],
+ ['#9fd7d3', 'Sinbad'],
+ ['#9fdd8c', 'Feijoa'],
+ ['#a02712', 'Tabasco'],
+ ['#a1750d', 'Buttered Rum'],
+ ['#a1adb5', 'Hit Gray'],
+ ['#a1c50a', 'Citrus'],
+ ['#a1dad7', 'Aqua Island'],
+ ['#a1e9de', 'Water Leaf'],
+ ['#a2006d', 'Flirt'],
+ ['#a23b6c', 'Rouge'],
+ ['#a26645', 'Cape Palliser'],
+ ['#a2aab3', 'Gray Chateau'],
+ ['#a2aeab', 'Edward'],
+ ['#a3807b', 'Pharlap'],
+ ['#a397b4', 'Amethyst Smoke'],
+ ['#a3e3ed', 'Blizzard Blue'],
+ ['#a4a49d', 'Delta'],
+ ['#a4a6d3', 'Wistful'],
+ ['#a4af6e', 'Green Smoke'],
+ ['#a50b5e', 'Jazzberry Jam'],
+ ['#a59b91', 'Zorba'],
+ ['#a5cb0c', 'Bahia'],
+ ['#a62f20', 'Roof Terracotta'],
+ ['#a65529', 'Paarl'],
+ ['#a68b5b', 'Barley Corn'],
+ ['#a69279', 'Donkey Brown'],
+ ['#a6a29a', 'Dawn'],
+ ['#a72525', 'Mexican Red'],
+ ['#a7882c', 'Luxor Gold'],
+ ['#a85307', 'Rich Gold'],
+ ['#a86515', 'Reno Sand'],
+ ['#a86b6b', 'Coral Tree'],
+ ['#a8989b', 'Dusty Gray'],
+ ['#a899e6', 'Dull Lavender'],
+ ['#a8a589', 'Tallow'],
+ ['#a8ae9c', 'Bud'],
+ ['#a8af8e', 'Locust'],
+ ['#a8bd9f', 'Norway'],
+ ['#a8e3bd', 'Chinook'],
+ ['#a9a491', 'Gray Olive'],
+ ['#a9acb6', 'Aluminium'],
+ ['#a9b2c3', 'Cadet Blue'],
+ ['#a9b497', 'Schist'],
+ ['#a9bdbf', 'Tower Gray'],
+ ['#a9bef2', 'Perano'],
+ ['#a9c6c2', 'Opal'],
+ ['#aa375a', 'Night Shadz'],
+ ['#aa4203', 'Fire'],
+ ['#aa8b5b', 'Muesli'],
+ ['#aa8d6f', 'Sandal'],
+ ['#aaa5a9', 'Shady Lady'],
+ ['#aaa9cd', 'Logan'],
+ ['#aaabb7', 'Spun Pearl'],
+ ['#aad6e6', 'Regent St Blue'],
+ ['#aaf0d1', 'Magic Mint'],
+ ['#ab0563', 'Lipstick'],
+ ['#ab3472', 'Royal Heath'],
+ ['#ab917a', 'Sandrift'],
+ ['#aba0d9', 'Cold Purple'],
+ ['#aba196', 'Bronco'],
+ ['#ac8a56', 'Limed Oak'],
+ ['#ac91ce', 'East Side'],
+ ['#ac9e22', 'Lemon Ginger'],
+ ['#aca494', 'Napa'],
+ ['#aca586', 'Hillary'],
+ ['#aca59f', 'Cloudy'],
+ ['#acacac', 'Silver Chalice'],
+ ['#acb78e', 'Swamp Green'],
+ ['#accbb1', 'Spring Rain'],
+ ['#acdd4d', 'Conifer'],
+ ['#ace1af', 'Celadon'],
+ ['#ad781b', 'Mandalay'],
+ ['#adbed1', 'Casper'],
+ ['#addfad', 'Moss Green'],
+ ['#ade6c4', 'Padua'],
+ ['#adff2f', 'Green Yellow'],
+ ['#ae4560', 'Hippie Pink'],
+ ['#ae6020', 'Desert'],
+ ['#ae809e', 'Bouquet'],
+ ['#af4035', 'Medium Carmine'],
+ ['#af4d43', 'Apple Blossom'],
+ ['#af593e', 'Brown Rust'],
+ ['#af8751', 'Driftwood'],
+ ['#af8f2c', 'Alpine'],
+ ['#af9f1c', 'Lucky'],
+ ['#afa09e', 'Martini'],
+ ['#afb1b8', 'Bombay'],
+ ['#afbdd9', 'Pigeon Post'],
+ ['#b04c6a', 'Cadillac'],
+ ['#b05d54', 'Matrix'],
+ ['#b05e81', 'Tapestry'],
+ ['#b06608', 'Mai Tai'],
+ ['#b09a95', 'Del Rio'],
+ ['#b0e0e6', 'Powder Blue'],
+ ['#b0e313', 'Inch Worm'],
+ ['#b10000', 'Bright Red'],
+ ['#b14a0b', 'Vesuvius'],
+ ['#b1610b', 'Pumpkin Skin'],
+ ['#b16d52', 'Santa Fe'],
+ ['#b19461', 'Teak'],
+ ['#b1e2c1', 'Fringy Flower'],
+ ['#b1f4e7', 'Ice Cold'],
+ ['#b20931', 'Shiraz'],
+ ['#b2a1ea', 'Biloba Flower'],
+ ['#b32d29', 'Tall Poppy'],
+ ['#b35213', 'Fiery Orange'],
+ ['#b38007', 'Hot Toddy'],
+ ['#b3af95', 'Taupe Gray'],
+ ['#b3c110', 'La Rioja'],
+ ['#b43332', 'Well Read'],
+ ['#b44668', 'Blush'],
+ ['#b4cfd3', 'Jungle Mist'],
+ ['#b57281', 'Turkish Rose'],
+ ['#b57edc', 'Lavender'],
+ ['#b5a27f', 'Mongoose'],
+ ['#b5b35c', 'Olive Green'],
+ ['#b5d2ce', 'Jet Stream'],
+ ['#b5ecdf', 'Cruise'],
+ ['#b6316c', 'Hibiscus'],
+ ['#b69d98', 'Thatch'],
+ ['#b6b095', 'Heathered Gray'],
+ ['#b6baa4', 'Eagle'],
+ ['#b6d1ea', 'Spindle'],
+ ['#b6d3bf', 'Gum Leaf'],
+ ['#b7410e', 'Rust'],
+ ['#b78e5c', 'Muddy Waters'],
+ ['#b7a214', 'Sahara'],
+ ['#b7a458', 'Husk'],
+ ['#b7b1b1', 'Nobel'],
+ ['#b7c3d0', 'Heather'],
+ ['#b7f0be', 'Madang'],
+ ['#b81104', 'Milano Red'],
+ ['#b87333', 'Copper'],
+ ['#b8b56a', 'Gimblet'],
+ ['#b8c1b1', 'Green Spring'],
+ ['#b8c25d', 'Celery'],
+ ['#b8e0f9', 'Sail'],
+ ['#b94e48', 'Chestnut'],
+ ['#b95140', 'Crail'],
+ ['#b98d28', 'Marigold'],
+ ['#b9c46a', 'Wild Willow'],
+ ['#b9c8ac', 'Rainee'],
+ ['#ba0101', 'Guardsman Red'],
+ ['#ba450c', 'Rock Spray'],
+ ['#ba6f1e', 'Bourbon'],
+ ['#ba7f03', 'Pirate Gold'],
+ ['#bab1a2', 'Nomad'],
+ ['#bac7c9', 'Submarine'],
+ ['#baeef9', 'Charlotte'],
+ ['#bb3385', 'Medium Red Violet'],
+ ['#bb8983', 'Brandy Rose'],
+ ['#bbd009', 'Rio Grande'],
+ ['#bbd7c1', 'Surf'],
+ ['#bcc9c2', 'Powder Ash'],
+ ['#bd5e2e', 'Tuscany'],
+ ['#bd978e', 'Quicksand'],
+ ['#bdb1a8', 'Silk'],
+ ['#bdb2a1', 'Malta'],
+ ['#bdb3c7', 'Chatelle'],
+ ['#bdbbd7', 'Lavender Gray'],
+ ['#bdbdc6', 'French Gray'],
+ ['#bdc8b3', 'Clay Ash'],
+ ['#bdc9ce', 'Loblolly'],
+ ['#bdedfd', 'French Pass'],
+ ['#bea6c3', 'London Hue'],
+ ['#beb5b7', 'Pink Swan'],
+ ['#bede0d', 'Fuego'],
+ ['#bf5500', 'Rose Of Sharon'],
+ ['#bfb8b0', 'Tide'],
+ ['#bfbed8', 'Blue Haze'],
+ ['#bfc1c2', 'Silver Sand'],
+ ['#bfc921', 'Key Lime Pie'],
+ ['#bfdbe2', 'Ziggurat'],
+ ['#bfff00', 'Lime'],
+ ['#c02b18', 'Thunderbird'],
+ ['#c04737', 'Mojo'],
+ ['#c08081', 'Old Rose'],
+ ['#c0c0c0', 'Silver'],
+ ['#c0d3b9', 'Pale Leaf'],
+ ['#c0d8b6', 'Pixie Green'],
+ ['#c1440e', 'Tia Maria'],
+ ['#c154c1', 'Fuchsia Pink'],
+ ['#c1a004', 'Buddha Gold'],
+ ['#c1b7a4', 'Bison Hide'],
+ ['#c1bab0', 'Tea'],
+ ['#c1becd', 'Gray Suit'],
+ ['#c1d7b0', 'Sprout'],
+ ['#c1f07c', 'Sulu'],
+ ['#c26b03', 'Indochine'],
+ ['#c2955d', 'Twine'],
+ ['#c2bdb6', 'Cotton Seed'],
+ ['#c2cac4', 'Pumice'],
+ ['#c2e8e5', 'Jagged Ice'],
+ ['#c32148', 'Maroon Flush'],
+ ['#c3b091', 'Indian Khaki'],
+ ['#c3bfc1', 'Pale Slate'],
+ ['#c3c3bd', 'Gray Nickel'],
+ ['#c3cde6', 'Periwinkle Gray'],
+ ['#c3d1d1', 'Tiara'],
+ ['#c3ddf9', 'Tropical Blue'],
+ ['#c41e3a', 'Cardinal'],
+ ['#c45655', 'Fuzzy Wuzzy Brown'],
+ ['#c45719', 'Orange Roughy'],
+ ['#c4c4bc', 'Mist Gray'],
+ ['#c4d0b0', 'Coriander'],
+ ['#c4f4eb', 'Mint Tulip'],
+ ['#c54b8c', 'Mulberry'],
+ ['#c59922', 'Nugget'],
+ ['#c5994b', 'Tussock'],
+ ['#c5dbca', 'Sea Mist'],
+ ['#c5e17a', 'Yellow Green'],
+ ['#c62d42', 'Brick Red'],
+ ['#c6726b', 'Contessa'],
+ ['#c69191', 'Oriental Pink'],
+ ['#c6a84b', 'Roti'],
+ ['#c6c3b5', 'Ash'],
+ ['#c6c8bd', 'Kangaroo'],
+ ['#c6e610', 'Las Palmas'],
+ ['#c7031e', 'Monza'],
+ ['#c71585', 'Red Violet'],
+ ['#c7bca2', 'Coral Reef'],
+ ['#c7c1ff', 'Melrose'],
+ ['#c7c4bf', 'Cloud'],
+ ['#c7c9d5', 'Ghost'],
+ ['#c7cd90', 'Pine Glade'],
+ ['#c7dde5', 'Botticelli'],
+ ['#c88a65', 'Antique Brass'],
+ ['#c8a2c8', 'Lilac'],
+ ['#c8a528', 'Hokey Pokey'],
+ ['#c8aabf', 'Lily'],
+ ['#c8b568', 'Laser'],
+ ['#c8e3d7', 'Edgewater'],
+ ['#c96323', 'Piper'],
+ ['#c99415', 'Pizza'],
+ ['#c9a0dc', 'Light Wisteria'],
+ ['#c9b29b', 'Rodeo Dust'],
+ ['#c9b35b', 'Sundance'],
+ ['#c9b93b', 'Earls Green'],
+ ['#c9c0bb', 'Silver Rust'],
+ ['#c9d9d2', 'Conch'],
+ ['#c9ffa2', 'Reef'],
+ ['#c9ffe5', 'Aero Blue'],
+ ['#ca3435', 'Flush Mahogany'],
+ ['#cabb48', 'Turmeric'],
+ ['#cadcd4', 'Paris White'],
+ ['#cae00d', 'Bitter Lemon'],
+ ['#cae6da', 'Skeptic'],
+ ['#cb8fa9', 'Viola'],
+ ['#cbcab6', 'Foggy Gray'],
+ ['#cbd3b0', 'Green Mist'],
+ ['#cbdbd6', 'Nebula'],
+ ['#cc3333', 'Persian Red'],
+ ['#cc5500', 'Burnt Orange'],
+ ['#cc7722', 'Ochre'],
+ ['#cc8899', 'Puce'],
+ ['#cccaa8', 'Thistle Green'],
+ ['#ccccff', 'Periwinkle'],
+ ['#ccff00', 'Electric Lime'],
+ ['#cd5700', 'Tenn'],
+ ['#cd5c5c', 'Chestnut Rose'],
+ ['#cd8429', 'Brandy Punch'],
+ ['#cdf4ff', 'Onahau'],
+ ['#ceb98f', 'Sorrell Brown'],
+ ['#cebaba', 'Cold Turkey'],
+ ['#cec291', 'Yuma'],
+ ['#cec7a7', 'Chino'],
+ ['#cfa39d', 'Eunry'],
+ ['#cfb53b', 'Old Gold'],
+ ['#cfdccf', 'Tasman'],
+ ['#cfe5d2', 'Surf Crest'],
+ ['#cff9f3', 'Humming Bird'],
+ ['#cffaf4', 'Scandal'],
+ ['#d05f04', 'Red Stage'],
+ ['#d06da1', 'Hopbush'],
+ ['#d07d12', 'Meteor'],
+ ['#d0bef8', 'Perfume'],
+ ['#d0c0e5', 'Prelude'],
+ ['#d0f0c0', 'Tea Green'],
+ ['#d18f1b', 'Geebung'],
+ ['#d1bea8', 'Vanilla'],
+ ['#d1c6b4', 'Soft Amber'],
+ ['#d1d2ca', 'Celeste'],
+ ['#d1d2dd', 'Mischka'],
+ ['#d1e231', 'Pear'],
+ ['#d2691e', 'Hot Cinnamon'],
+ ['#d27d46', 'Raw Sienna'],
+ ['#d29eaa', 'Careys Pink'],
+ ['#d2b48c', 'Tan'],
+ ['#d2da97', 'Deco'],
+ ['#d2f6de', 'Blue Romance'],
+ ['#d2f8b0', 'Gossip'],
+ ['#d3cbba', 'Sisal'],
+ ['#d3cdc5', 'Swirl'],
+ ['#d47494', 'Charm'],
+ ['#d4b6af', 'Clam Shell'],
+ ['#d4bf8d', 'Straw'],
+ ['#d4c4a8', 'Akaroa'],
+ ['#d4cd16', 'Bird Flower'],
+ ['#d4d7d9', 'Iron'],
+ ['#d4dfe2', 'Geyser'],
+ ['#d4e2fc', 'Hawkes Blue'],
+ ['#d54600', 'Grenadier'],
+ ['#d591a4', 'Can Can'],
+ ['#d59a6f', 'Whiskey'],
+ ['#d5d195', 'Winter Hazel'],
+ ['#d5f6e3', 'Granny Apple'],
+ ['#d69188', 'My Pink'],
+ ['#d6c562', 'Tacha'],
+ ['#d6cef6', 'Moon Raker'],
+ ['#d6d6d1', 'Quill Gray'],
+ ['#d6ffdb', 'Snowy Mint'],
+ ['#d7837f', 'New York Pink'],
+ ['#d7c498', 'Pavlova'],
+ ['#d7d0ff', 'Fog'],
+ ['#d84437', 'Valencia'],
+ ['#d87c63', 'Japonica'],
+ ['#d8bfd8', 'Thistle'],
+ ['#d8c2d5', 'Maverick'],
+ ['#d8fcfa', 'Foam'],
+ ['#d94972', 'Cabaret'],
+ ['#d99376', 'Burning Sand'],
+ ['#d9b99b', 'Cameo'],
+ ['#d9d6cf', 'Timberwolf'],
+ ['#d9dcc1', 'Tana'],
+ ['#d9e4f5', 'Link Water'],
+ ['#d9f7ff', 'Mabel'],
+ ['#da3287', 'Cerise'],
+ ['#da5b38', 'Flame Pea'],
+ ['#da6304', 'Bamboo'],
+ ['#da6a41', 'Red Damask'],
+ ['#da70d6', 'Orchid'],
+ ['#da8a67', 'Copperfield'],
+ ['#daa520', 'Golden Grass'],
+ ['#daecd6', 'Zanah'],
+ ['#daf4f0', 'Iceberg'],
+ ['#dafaff', 'Oyster Bay'],
+ ['#db5079', 'Cranberry'],
+ ['#db9690', 'Petite Orchid'],
+ ['#db995e', 'Di Serria'],
+ ['#dbdbdb', 'Alto'],
+ ['#dbfff8', 'Frosted Mint'],
+ ['#dc143c', 'Crimson'],
+ ['#dc4333', 'Punch'],
+ ['#dcb20c', 'Galliano'],
+ ['#dcb4bc', 'Blossom'],
+ ['#dcd747', 'Wattle'],
+ ['#dcd9d2', 'Westar'],
+ ['#dcddcc', 'Moon Mist'],
+ ['#dcedb4', 'Caper'],
+ ['#dcf0ea', 'Swans Down'],
+ ['#ddd6d5', 'Swiss Coffee'],
+ ['#ddf9f1', 'White Ice'],
+ ['#de3163', 'Cerise Red'],
+ ['#de6360', 'Roman'],
+ ['#dea681', 'Tumbleweed'],
+ ['#deba13', 'Gold Tips'],
+ ['#dec196', 'Brandy'],
+ ['#decbc6', 'Wafer'],
+ ['#ded4a4', 'Sapling'],
+ ['#ded717', 'Barberry'],
+ ['#dee5c0', 'Beryl Green'],
+ ['#def5ff', 'Pattens Blue'],
+ ['#df73ff', 'Heliotrope'],
+ ['#dfbe6f', 'Apache'],
+ ['#dfcd6f', 'Chenin'],
+ ['#dfcfdb', 'Lola'],
+ ['#dfecda', 'Willow Brook'],
+ ['#dfff00', 'Chartreuse Yellow'],
+ ['#e0b0ff', 'Mauve'],
+ ['#e0b646', 'Anzac'],
+ ['#e0b974', 'Harvest Gold'],
+ ['#e0c095', 'Calico'],
+ ['#e0ffff', 'Baby Blue'],
+ ['#e16865', 'Sunglo'],
+ ['#e1bc64', 'Equator'],
+ ['#e1c0c8', 'Pink Flare'],
+ ['#e1e6d6', 'Periglacial Blue'],
+ ['#e1ead4', 'Kidnapper'],
+ ['#e1f6e8', 'Tara'],
+ ['#e25465', 'Mandy'],
+ ['#e2725b', 'Terracotta'],
+ ['#e28913', 'Golden Bell'],
+ ['#e292c0', 'Shocking'],
+ ['#e29418', 'Dixie'],
+ ['#e29cd2', 'Light Orchid'],
+ ['#e2d8ed', 'Snuff'],
+ ['#e2ebed', 'Mystic'],
+ ['#e2f3ec', 'Apple Green'],
+ ['#e30b5c', 'Razzmatazz'],
+ ['#e32636', 'Alizarin Crimson'],
+ ['#e34234', 'Cinnabar'],
+ ['#e3bebe', 'Cavern Pink'],
+ ['#e3f5e1', 'Peppermint'],
+ ['#e3f988', 'Mindaro'],
+ ['#e47698', 'Deep Blush'],
+ ['#e49b0f', 'Gamboge'],
+ ['#e4c2d5', 'Melanie'],
+ ['#e4cfde', 'Twilight'],
+ ['#e4d1c0', 'Bone'],
+ ['#e4d422', 'Sunflower'],
+ ['#e4d5b7', 'Grain Brown'],
+ ['#e4d69b', 'Zombie'],
+ ['#e4f6e7', 'Frostee'],
+ ['#e4ffd1', 'Snow Flurry'],
+ ['#e52b50', 'Amaranth'],
+ ['#e5841b', 'Zest'],
+ ['#e5ccc9', 'Dust Storm'],
+ ['#e5d7bd', 'Stark White'],
+ ['#e5d8af', 'Hampton'],
+ ['#e5e0e1', 'Bon Jour'],
+ ['#e5e5e5', 'Mercury'],
+ ['#e5f9f6', 'Polar'],
+ ['#e64e03', 'Trinidad'],
+ ['#e6be8a', 'Gold Sand'],
+ ['#e6bea5', 'Cashmere'],
+ ['#e6d7b9', 'Double Spanish White'],
+ ['#e6e4d4', 'Satin Linen'],
+ ['#e6f2ea', 'Harp'],
+ ['#e6f8f3', 'Off Green'],
+ ['#e6ffe9', 'Hint Of Green'],
+ ['#e6ffff', 'Tranquil'],
+ ['#e77200', 'Mango Tango'],
+ ['#e7730a', 'Christine'],
+ ['#e79f8c', 'Tonys Pink'],
+ ['#e79fc4', 'Kobi'],
+ ['#e7bcb4', 'Rose Fog'],
+ ['#e7bf05', 'Corn'],
+ ['#e7cd8c', 'Putty'],
+ ['#e7ece6', 'Gray Nurse'],
+ ['#e7f8ff', 'Lily White'],
+ ['#e7feff', 'Bubbles'],
+ ['#e89928', 'Fire Bush'],
+ ['#e8b9b3', 'Shilo'],
+ ['#e8e0d5', 'Pearl Bush'],
+ ['#e8ebe0', 'Green White'],
+ ['#e8f1d4', 'Chrome White'],
+ ['#e8f2eb', 'Gin'],
+ ['#e8f5f2', 'Aqua Squeeze'],
+ ['#e96e00', 'Clementine'],
+ ['#e97451', 'Burnt Sienna'],
+ ['#e97c07', 'Tahiti Gold'],
+ ['#e9cecd', 'Oyster Pink'],
+ ['#e9d75a', 'Confetti'],
+ ['#e9e3e3', 'Ebb'],
+ ['#e9f8ed', 'Ottoman'],
+ ['#e9fffd', 'Clear Day'],
+ ['#ea88a8', 'Carissma'],
+ ['#eaae69', 'Porsche'],
+ ['#eab33b', 'Tulip Tree'],
+ ['#eac674', 'Rob Roy'],
+ ['#eadab8', 'Raffia'],
+ ['#eae8d4', 'White Rock'],
+ ['#eaf6ee', 'Panache'],
+ ['#eaf6ff', 'Solitude'],
+ ['#eaf9f5', 'Aqua Spring'],
+ ['#eafffe', 'Dew'],
+ ['#eb9373', 'Apricot'],
+ ['#ebc2af', 'Zinnwaldite'],
+ ['#eca927', 'Fuel Yellow'],
+ ['#ecc54e', 'Ronchi'],
+ ['#ecc7ee', 'French Lilac'],
+ ['#eccdb9', 'Just Right'],
+ ['#ece090', 'Wild Rice'],
+ ['#ecebbd', 'Fall Green'],
+ ['#ecebce', 'Aths Special'],
+ ['#ecf245', 'Starship'],
+ ['#ed0a3f', 'Red Ribbon'],
+ ['#ed7a1c', 'Tango'],
+ ['#ed9121', 'Carrot Orange'],
+ ['#ed989e', 'Sea Pink'],
+ ['#edb381', 'Tacao'],
+ ['#edc9af', 'Desert Sand'],
+ ['#edcdab', 'Pancho'],
+ ['#eddcb1', 'Chamois'],
+ ['#edea99', 'Primrose'],
+ ['#edf5dd', 'Frost'],
+ ['#edf5f5', 'Aqua Haze'],
+ ['#edf6ff', 'Zumthor'],
+ ['#edf9f1', 'Narvik'],
+ ['#edfc84', 'Honeysuckle'],
+ ['#ee82ee', 'Lavender Magenta'],
+ ['#eec1be', 'Beauty Bush'],
+ ['#eed794', 'Chalky'],
+ ['#eed9c4', 'Almond'],
+ ['#eedc82', 'Flax'],
+ ['#eededa', 'Bizarre'],
+ ['#eee3ad', 'Double Colonial White'],
+ ['#eeeee8', 'Cararra'],
+ ['#eeef78', 'Manz'],
+ ['#eef0c8', 'Tahuna Sands'],
+ ['#eef0f3', 'Athens Gray'],
+ ['#eef3c3', 'Tusk'],
+ ['#eef4de', 'Loafer'],
+ ['#eef6f7', 'Catskill White'],
+ ['#eefdff', 'Twilight Blue'],
+ ['#eeff9a', 'Jonquil'],
+ ['#eeffe2', 'Rice Flower'],
+ ['#ef863f', 'Jaffa'],
+ ['#efefef', 'Gallery'],
+ ['#eff2f3', 'Porcelain'],
+ ['#f091a9', 'Mauvelous'],
+ ['#f0d52d', 'Golden Dream'],
+ ['#f0db7d', 'Golden Sand'],
+ ['#f0dc82', 'Buff'],
+ ['#f0e2ec', 'Prim'],
+ ['#f0e68c', 'Khaki'],
+ ['#f0eefd', 'Selago'],
+ ['#f0eeff', 'Titan White'],
+ ['#f0f8ff', 'Alice Blue'],
+ ['#f0fcea', 'Feta'],
+ ['#f18200', 'Gold Drop'],
+ ['#f19bab', 'Wewak'],
+ ['#f1e788', 'Sahara Sand'],
+ ['#f1e9d2', 'Parchment'],
+ ['#f1e9ff', 'Blue Chalk'],
+ ['#f1eec1', 'Mint Julep'],
+ ['#f1f1f1', 'Seashell'],
+ ['#f1f7f2', 'Saltpan'],
+ ['#f1ffad', 'Tidal'],
+ ['#f1ffc8', 'Chiffon'],
+ ['#f2552a', 'Flamingo'],
+ ['#f28500', 'Tangerine'],
+ ['#f2c3b2', 'Mandys Pink'],
+ ['#f2f2f2', 'Concrete'],
+ ['#f2fafa', 'Black Squeeze'],
+ ['#f34723', 'Pomegranate'],
+ ['#f3ad16', 'Buttercup'],
+ ['#f3d69d', 'New Orleans'],
+ ['#f3d9df', 'Vanilla Ice'],
+ ['#f3e7bb', 'Sidecar'],
+ ['#f3e9e5', 'Dawn Pink'],
+ ['#f3edcf', 'Wheatfield'],
+ ['#f3fb62', 'Canary'],
+ ['#f3fbd4', 'Orinoco'],
+ ['#f3ffd8', 'Carla'],
+ ['#f400a1', 'Hollywood Cerise'],
+ ['#f4a460', 'Sandy brown'],
+ ['#f4c430', 'Saffron'],
+ ['#f4d81c', 'Ripe Lemon'],
+ ['#f4ebd3', 'Janna'],
+ ['#f4f2ee', 'Pampas'],
+ ['#f4f4f4', 'Wild Sand'],
+ ['#f4f8ff', 'Zircon'],
+ ['#f57584', 'Froly'],
+ ['#f5c85c', 'Cream Can'],
+ ['#f5c999', 'Manhattan'],
+ ['#f5d5a0', 'Maize'],
+ ['#f5deb3', 'Wheat'],
+ ['#f5e7a2', 'Sandwisp'],
+ ['#f5e7e2', 'Pot Pourri'],
+ ['#f5e9d3', 'Albescent White'],
+ ['#f5edef', 'Soft Peach'],
+ ['#f5f3e5', 'Ecru White'],
+ ['#f5f5dc', 'Beige'],
+ ['#f5fb3d', 'Golden Fizz'],
+ ['#f5ffbe', 'Australian Mint'],
+ ['#f64a8a', 'French Rose'],
+ ['#f653a6', 'Brilliant Rose'],
+ ['#f6a4c9', 'Illusion'],
+ ['#f6f0e6', 'Merino'],
+ ['#f6f7f7', 'Black Haze'],
+ ['#f6ffdc', 'Spring Sun'],
+ ['#f7468a', 'Violet Red'],
+ ['#f77703', 'Chilean Fire'],
+ ['#f77fbe', 'Persian Pink'],
+ ['#f7b668', 'Rajah'],
+ ['#f7c8da', 'Azalea'],
+ ['#f7dbe6', 'We Peep'],
+ ['#f7f2e1', 'Quarter Spanish White'],
+ ['#f7f5fa', 'Whisper'],
+ ['#f7faf7', 'Snow Drift'],
+ ['#f8b853', 'Casablanca'],
+ ['#f8c3df', 'Chantilly'],
+ ['#f8d9e9', 'Cherub'],
+ ['#f8db9d', 'Marzipan'],
+ ['#f8dd5c', 'Energy Yellow'],
+ ['#f8e4bf', 'Givry'],
+ ['#f8f0e8', 'White Linen'],
+ ['#f8f4ff', 'Magnolia'],
+ ['#f8f6f1', 'Spring Wood'],
+ ['#f8f7dc', 'Coconut Cream'],
+ ['#f8f7fc', 'White Lilac'],
+ ['#f8f8f7', 'Desert Storm'],
+ ['#f8f99c', 'Texas'],
+ ['#f8facd', 'Corn Field'],
+ ['#f8fdd3', 'Mimosa'],
+ ['#f95a61', 'Carnation'],
+ ['#f9bf58', 'Saffron Mango'],
+ ['#f9e0ed', 'Carousel Pink'],
+ ['#f9e4bc', 'Dairy Cream'],
+ ['#f9e663', 'Portica'],
+ ['#f9eaf3', 'Amour'],
+ ['#f9f8e4', 'Rum Swizzle'],
+ ['#f9ff8b', 'Dolly'],
+ ['#f9fff6', 'Sugar Cane'],
+ ['#fa7814', 'Ecstasy'],
+ ['#fa9d5a', 'Tan Hide'],
+ ['#fad3a2', 'Corvette'],
+ ['#fadfad', 'Peach Yellow'],
+ ['#fae600', 'Turbo'],
+ ['#faeab9', 'Astra'],
+ ['#faeccc', 'Champagne'],
+ ['#faf0e6', 'Linen'],
+ ['#faf3f0', 'Fantasy'],
+ ['#faf7d6', 'Citrine White'],
+ ['#fafafa', 'Alabaster'],
+ ['#fafde4', 'Hint Of Yellow'],
+ ['#faffa4', 'Milan'],
+ ['#fb607f', 'Brink Pink'],
+ ['#fb8989', 'Geraldine'],
+ ['#fba0e3', 'Lavender Rose'],
+ ['#fba129', 'Sea Buckthorn'],
+ ['#fbac13', 'Sun'],
+ ['#fbaed2', 'Lavender Pink'],
+ ['#fbb2a3', 'Rose Bud'],
+ ['#fbbeda', 'Cupid'],
+ ['#fbcce7', 'Classic Rose'],
+ ['#fbceb1', 'Apricot Peach'],
+ ['#fbe7b2', 'Banana Mania'],
+ ['#fbe870', 'Marigold Yellow'],
+ ['#fbe96c', 'Festival'],
+ ['#fbea8c', 'Sweet Corn'],
+ ['#fbec5d', 'Candy Corn'],
+ ['#fbf9f9', 'Hint Of Red'],
+ ['#fbffba', 'Shalimar'],
+ ['#fc0fc0', 'Shocking Pink'],
+ ['#fc80a5', 'Tickle Me Pink'],
+ ['#fc9c1d', 'Tree Poppy'],
+ ['#fcc01e', 'Lightning Yellow'],
+ ['#fcd667', 'Goldenrod'],
+ ['#fcd917', 'Candlelight'],
+ ['#fcda98', 'Cherokee'],
+ ['#fcf4d0', 'Double Pearl Lusta'],
+ ['#fcf4dc', 'Pearl Lusta'],
+ ['#fcf8f7', 'Vista White'],
+ ['#fcfbf3', 'Bianca'],
+ ['#fcfeda', 'Moon Glow'],
+ ['#fcffe7', 'China Ivory'],
+ ['#fcfff9', 'Ceramic'],
+ ['#fd0e35', 'Torch Red'],
+ ['#fd5b78', 'Wild Watermelon'],
+ ['#fd7b33', 'Crusta'],
+ ['#fd7c07', 'Sorbus'],
+ ['#fd9fa2', 'Sweet Pink'],
+ ['#fdd5b1', 'Light Apricot'],
+ ['#fdd7e4', 'Pig Pink'],
+ ['#fde1dc', 'Cinderella'],
+ ['#fde295', 'Golden Glow'],
+ ['#fde910', 'Lemon'],
+ ['#fdf5e6', 'Old Lace'],
+ ['#fdf6d3', 'Half Colonial White'],
+ ['#fdf7ad', 'Drover'],
+ ['#fdfeb8', 'Pale Prim'],
+ ['#fdffd5', 'Cumulus'],
+ ['#fe28a2', 'Persian Rose'],
+ ['#fe4c40', 'Sunset Orange'],
+ ['#fe6f5e', 'Bittersweet'],
+ ['#fe9d04', 'California'],
+ ['#fea904', 'Yellow Sea'],
+ ['#febaad', 'Melon'],
+ ['#fed33c', 'Bright Sun'],
+ ['#fed85d', 'Dandelion'],
+ ['#fedb8d', 'Salomie'],
+ ['#fee5ac', 'Cape Honey'],
+ ['#feebf3', 'Remy'],
+ ['#feefce', 'Oasis'],
+ ['#fef0ec', 'Bridesmaid'],
+ ['#fef2c7', 'Beeswax'],
+ ['#fef3d8', 'Bleach White'],
+ ['#fef4cc', 'Pipi'],
+ ['#fef4db', 'Half Spanish White'],
+ ['#fef4f8', 'Wisp Pink'],
+ ['#fef5f1', 'Provincial Pink'],
+ ['#fef7de', 'Half Dutch White'],
+ ['#fef8e2', 'Solitaire'],
+ ['#fef8ff', 'White Pointer'],
+ ['#fef9e3', 'Off Yellow'],
+ ['#fefced', 'Orange White'],
+ ['#ff0000', 'Red'],
+ ['#ff007f', 'Rose'],
+ ['#ff00cc', 'Purple Pizzazz'],
+ ['#ff00ff', 'Magenta Fuchsia'],
+ ['#ff2400', 'Scarlet'],
+ ['#ff3399', 'Wild Strawberry'],
+ ['#ff33cc', 'Razzle Dazzle Rose'],
+ ['#ff355e', 'Radical Red'],
+ ['#ff3f34', 'Red Orange'],
+ ['#ff4040', 'Coral Red'],
+ ['#ff4d00', 'Vermilion'],
+ ['#ff4f00', 'International Orange'],
+ ['#ff6037', 'Outrageous Orange'],
+ ['#ff6600', 'Blaze Orange'],
+ ['#ff66ff', 'Pink Flamingo'],
+ ['#ff681f', 'Orange'],
+ ['#ff69b4', 'Hot Pink'],
+ ['#ff6b53', 'Persimmon'],
+ ['#ff6fff', 'Blush Pink'],
+ ['#ff7034', 'Burning Orange'],
+ ['#ff7518', 'Pumpkin'],
+ ['#ff7d07', 'Flamenco'],
+ ['#ff7f00', 'Flush Orange'],
+ ['#ff7f50', 'Coral'],
+ ['#ff8c69', 'Salmon'],
+ ['#ff9000', 'Pizazz'],
+ ['#ff910f', 'West Side'],
+ ['#ff91a4', 'Pink Salmon'],
+ ['#ff9933', 'Neon Carrot'],
+ ['#ff9966', 'Atomic Tangerine'],
+ ['#ff9980', 'Vivid Tangerine'],
+ ['#ff9e2c', 'Sunshade'],
+ ['#ffa000', 'Orange Peel'],
+ ['#ffa194', 'Mona Lisa'],
+ ['#ffa500', 'Web Orange'],
+ ['#ffa6c9', 'Carnation Pink'],
+ ['#ffab81', 'Hit Pink'],
+ ['#ffae42', 'Yellow Orange'],
+ ['#ffb0ac', 'Cornflower Lilac'],
+ ['#ffb1b3', 'Sundown'],
+ ['#ffb31f', 'My Sin'],
+ ['#ffb555', 'Texas Rose'],
+ ['#ffb7d5', 'Cotton Candy'],
+ ['#ffb97b', 'Macaroni And Cheese'],
+ ['#ffba00', 'Selective Yellow'],
+ ['#ffbd5f', 'Koromiko'],
+ ['#ffbf00', 'Amber'],
+ ['#ffc0a8', 'Wax Flower'],
+ ['#ffc0cb', 'Pink'],
+ ['#ffc3c0', 'Your Pink'],
+ ['#ffc901', 'Supernova'],
+ ['#ffcba4', 'Flesh'],
+ ['#ffcc33', 'Sunglow'],
+ ['#ffcc5c', 'Golden Tainoi'],
+ ['#ffcc99', 'Peach Orange'],
+ ['#ffcd8c', 'Chardonnay'],
+ ['#ffd1dc', 'Pastel Pink'],
+ ['#ffd2b7', 'Romantic'],
+ ['#ffd38c', 'Grandis'],
+ ['#ffd700', 'Gold'],
+ ['#ffd800', 'School Bus Yellow'],
+ ['#ffd8d9', 'Cosmos'],
+ ['#ffdb58', 'Mustard'],
+ ['#ffdcd6', 'Peach Schnapps'],
+ ['#ffddaf', 'Caramel'],
+ ['#ffddcd', 'Tuft Bush'],
+ ['#ffddcf', 'Watusi'],
+ ['#ffddf4', 'Pink Lace'],
+ ['#ffdead', 'Navajo White'],
+ ['#ffdeb3', 'Frangipani'],
+ ['#ffe1df', 'Pippin'],
+ ['#ffe1f2', 'Pale Rose'],
+ ['#ffe2c5', 'Negroni'],
+ ['#ffe5a0', 'Cream Brulee'],
+ ['#ffe5b4', 'Peach'],
+ ['#ffe6c7', 'Tequila'],
+ ['#ffe772', 'Kournikova'],
+ ['#ffeac8', 'Sandy Beach'],
+ ['#ffead4', 'Karry'],
+ ['#ffec13', 'Broom'],
+ ['#ffedbc', 'Colonial White'],
+ ['#ffeed8', 'Derby'],
+ ['#ffefa1', 'Vis Vis'],
+ ['#ffefc1', 'Egg White'],
+ ['#ffefd5', 'Papaya Whip'],
+ ['#ffefec', 'Fair Pink'],
+ ['#fff0db', 'Peach Cream'],
+ ['#fff0f5', 'Lavender Blush'],
+ ['#fff14f', 'Gorse'],
+ ['#fff1b5', 'Buttermilk'],
+ ['#fff1d8', 'Pink Lady'],
+ ['#fff1ee', 'Forget Me Not'],
+ ['#fff1f9', 'Tutu'],
+ ['#fff39d', 'Picasso'],
+ ['#fff3f1', 'Chardon'],
+ ['#fff46e', 'Paris Daisy'],
+ ['#fff4ce', 'Barley White'],
+ ['#fff4dd', 'Egg Sour'],
+ ['#fff4e0', 'Sazerac'],
+ ['#fff4e8', 'Serenade'],
+ ['#fff4f3', 'Chablis'],
+ ['#fff5ee', 'Seashell Peach'],
+ ['#fff5f3', 'Sauvignon'],
+ ['#fff6d4', 'Milk Punch'],
+ ['#fff6df', 'Varden'],
+ ['#fff6f5', 'Rose White'],
+ ['#fff8d1', 'Baja White'],
+ ['#fff9e2', 'Gin Fizz'],
+ ['#fff9e6', 'Early Dawn'],
+ ['#fffacd', 'Lemon Chiffon'],
+ ['#fffaf4', 'Bridal Heath'],
+ ['#fffbdc', 'Scotch Mist'],
+ ['#fffbf9', 'Soapstone'],
+ ['#fffc99', 'Witch Haze'],
+ ['#fffcea', 'Buttery White'],
+ ['#fffcee', 'Island Spice'],
+ ['#fffdd0', 'Cream'],
+ ['#fffde6', 'Chilean Heath'],
+ ['#fffde8', 'Travertine'],
+ ['#fffdf3', 'Orchid White'],
+ ['#fffdf4', 'Quarter Pearl Lusta'],
+ ['#fffee1', 'Half And Half'],
+ ['#fffeec', 'Apricot White'],
+ ['#fffef0', 'Rice Cake'],
+ ['#fffef6', 'Black White'],
+ ['#fffefd', 'Romance'],
+ ['#ffff00', 'Yellow'],
+ ['#ffff66', 'Laser Lemon'],
+ ['#ffff99', 'Pale Canary'],
+ ['#ffffb4', 'Portafino'],
+ ['#fffff0', 'Ivory'],
+ ['#ffffff', 'White']
+];
+
+/**
+ * Map Of hex color values to color names
+ *
+ * - key: hex value
+ * - value: color name
+ */
+export const colorNameMap = colorNames.reduce<Record<string, string>>((acc, [hex, name]) => {
+ acc[hex] = name;
+ return acc;
+}, {});
diff --git a/ruoyi-plus-soybean/packages/color/src/constant/palette.ts b/ruoyi-plus-soybean/packages/color/src/constant/palette.ts
new file mode 100755
index 0000000..9f62664
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/color/src/constant/palette.ts
@@ -0,0 +1,356 @@
+import type { ColorPaletteFamily } from '../types';
+
+export const colorPalettes: ColorPaletteFamily[] = [
+ {
+ name: 'Slate',
+ palettes: [
+ { hex: '#f8fafc', number: 50 },
+ { hex: '#f1f5f9', number: 100 },
+ { hex: '#e2e8f0', number: 200 },
+ { hex: '#cbd5e1', number: 300 },
+ { hex: '#94a3b8', number: 400 },
+ { hex: '#64748b', number: 500 },
+ { hex: '#475569', number: 600 },
+ { hex: '#334155', number: 700 },
+ { hex: '#1e293b', number: 800 },
+ { hex: '#0f172a', number: 900 },
+ { hex: '#020617', number: 950 }
+ ]
+ },
+ {
+ name: 'Gray',
+ palettes: [
+ { hex: '#f9fafb', number: 50 },
+ { hex: '#f3f4f6', number: 100 },
+ { hex: '#e5e7eb', number: 200 },
+ { hex: '#d1d5db', number: 300 },
+ { hex: '#9ca3af', number: 400 },
+ { hex: '#6b7280', number: 500 },
+ { hex: '#4b5563', number: 600 },
+ { hex: '#374151', number: 700 },
+ { hex: '#1f2937', number: 800 },
+ { hex: '#111827', number: 900 },
+ { hex: '#030712', number: 950 }
+ ]
+ },
+ {
+ name: 'Zinc',
+ palettes: [
+ { hex: '#fafafa', number: 50 },
+ { hex: '#f4f4f5', number: 100 },
+ { hex: '#e4e4e7', number: 200 },
+ { hex: '#d4d4d8', number: 300 },
+ { hex: '#a1a1aa', number: 400 },
+ { hex: '#71717a', number: 500 },
+ { hex: '#52525b', number: 600 },
+ { hex: '#3f3f46', number: 700 },
+ { hex: '#27272a', number: 800 },
+ { hex: '#18181b', number: 900 },
+ { hex: '#09090b', number: 950 }
+ ]
+ },
+ {
+ name: 'Neutral',
+ palettes: [
+ { hex: '#fafafa', number: 50 },
+ { hex: '#f5f5f5', number: 100 },
+ { hex: '#e5e5e5', number: 200 },
+ { hex: '#d4d4d4', number: 300 },
+ { hex: '#a3a3a3', number: 400 },
+ { hex: '#737373', number: 500 },
+ { hex: '#525252', number: 600 },
+ { hex: '#404040', number: 700 },
+ { hex: '#262626', number: 800 },
+ { hex: '#171717', number: 900 },
+ { hex: '#0a0a0a', number: 950 }
+ ]
+ },
+ {
+ name: 'Stone',
+ palettes: [
+ { hex: '#fafaf9', number: 50 },
+ { hex: '#f5f5f4', number: 100 },
+ { hex: '#e7e5e4', number: 200 },
+ { hex: '#d6d3d1', number: 300 },
+ { hex: '#a8a29e', number: 400 },
+ { hex: '#78716c', number: 500 },
+ { hex: '#57534e', number: 600 },
+ { hex: '#44403c', number: 700 },
+ { hex: '#292524', number: 800 },
+ { hex: '#1c1917', number: 900 },
+ { hex: '#0c0a09', number: 950 }
+ ]
+ },
+ {
+ name: 'Red',
+ palettes: [
+ { hex: '#fef2f2', number: 50 },
+ { hex: '#fee2e2', number: 100 },
+ { hex: '#fecaca', number: 200 },
+ { hex: '#fca5a5', number: 300 },
+ { hex: '#f87171', number: 400 },
+ { hex: '#ef4444', number: 500 },
+ { hex: '#dc2626', number: 600 },
+ { hex: '#b91c1c', number: 700 },
+ { hex: '#991b1b', number: 800 },
+ { hex: '#7f1d1d', number: 900 },
+ { hex: '#450a0a', number: 950 }
+ ]
+ },
+ {
+ name: 'Orange',
+ palettes: [
+ { hex: '#fff7ed', number: 50 },
+ { hex: '#ffedd5', number: 100 },
+ { hex: '#fed7aa', number: 200 },
+ { hex: '#fdba74', number: 300 },
+ { hex: '#fb923c', number: 400 },
+ { hex: '#f97316', number: 500 },
+ { hex: '#ea580c', number: 600 },
+ { hex: '#c2410c', number: 700 },
+ { hex: '#9a3412', number: 800 },
+ { hex: '#7c2d12', number: 900 },
+ { hex: '#431407', number: 950 }
+ ]
+ },
+ {
+ name: 'Amber',
+ palettes: [
+ { hex: '#fffbeb', number: 50 },
+ { hex: '#fef3c7', number: 100 },
+ { hex: '#fde68a', number: 200 },
+ { hex: '#fcd34d', number: 300 },
+ { hex: '#fbbf24', number: 400 },
+ { hex: '#f59e0b', number: 500 },
+ { hex: '#d97706', number: 600 },
+ { hex: '#b45309', number: 700 },
+ { hex: '#92400e', number: 800 },
+ { hex: '#78350f', number: 900 },
+ { hex: '#451a03', number: 950 }
+ ]
+ },
+ {
+ name: 'Yellow',
+ palettes: [
+ { hex: '#fefce8', number: 50 },
+ { hex: '#fef9c3', number: 100 },
+ { hex: '#fef08a', number: 200 },
+ { hex: '#fde047', number: 300 },
+ { hex: '#facc15', number: 400 },
+ { hex: '#eab308', number: 500 },
+ { hex: '#ca8a04', number: 600 },
+ { hex: '#a16207', number: 700 },
+ { hex: '#854d0e', number: 800 },
+ { hex: '#713f12', number: 900 },
+ { hex: '#422006', number: 950 }
+ ]
+ },
+ {
+ name: 'Lime',
+ palettes: [
+ { hex: '#f7fee7', number: 50 },
+ { hex: '#ecfccb', number: 100 },
+ { hex: '#d9f99d', number: 200 },
+ { hex: '#bef264', number: 300 },
+ { hex: '#a3e635', number: 400 },
+ { hex: '#84cc16', number: 500 },
+ { hex: '#65a30d', number: 600 },
+ { hex: '#4d7c0f', number: 700 },
+ { hex: '#3f6212', number: 800 },
+ { hex: '#365314', number: 900 },
+ { hex: '#1a2e05', number: 950 }
+ ]
+ },
+ {
+ name: 'Green',
+ palettes: [
+ { hex: '#f0fdf4', number: 50 },
+ { hex: '#dcfce7', number: 100 },
+ { hex: '#bbf7d0', number: 200 },
+ { hex: '#86efac', number: 300 },
+ { hex: '#4ade80', number: 400 },
+ { hex: '#22c55e', number: 500 },
+ { hex: '#16a34a', number: 600 },
+ { hex: '#15803d', number: 700 },
+ { hex: '#166534', number: 800 },
+ { hex: '#14532d', number: 900 },
+ { hex: '#052e16', number: 950 }
+ ]
+ },
+ {
+ name: 'Emerald',
+ palettes: [
+ { hex: '#ecfdf5', number: 50 },
+ { hex: '#d1fae5', number: 100 },
+ { hex: '#a7f3d0', number: 200 },
+ { hex: '#6ee7b7', number: 300 },
+ { hex: '#34d399', number: 400 },
+ { hex: '#10b981', number: 500 },
+ { hex: '#059669', number: 600 },
+ { hex: '#047857', number: 700 },
+ { hex: '#065f46', number: 800 },
+ { hex: '#064e3b', number: 900 },
+ { hex: '#022c22', number: 950 }
+ ]
+ },
+ {
+ name: 'Teal',
+ palettes: [
+ { hex: '#f0fdfa', number: 50 },
+ { hex: '#ccfbf1', number: 100 },
+ { hex: '#99f6e4', number: 200 },
+ { hex: '#5eead4', number: 300 },
+ { hex: '#2dd4bf', number: 400 },
+ { hex: '#14b8a6', number: 500 },
+ { hex: '#0d9488', number: 600 },
+ { hex: '#0f766e', number: 700 },
+ { hex: '#115e59', number: 800 },
+ { hex: '#134e4a', number: 900 },
+ { hex: '#042f2e', number: 950 }
+ ]
+ },
+ {
+ name: 'Cyan',
+ palettes: [
+ { hex: '#ecfeff', number: 50 },
+ { hex: '#cffafe', number: 100 },
+ { hex: '#a5f3fc', number: 200 },
+ { hex: '#67e8f9', number: 300 },
+ { hex: '#22d3ee', number: 400 },
+ { hex: '#06b6d4', number: 500 },
+ { hex: '#0891b2', number: 600 },
+ { hex: '#0e7490', number: 700 },
+ { hex: '#155e75', number: 800 },
+ { hex: '#164e63', number: 900 },
+ { hex: '#083344', number: 950 }
+ ]
+ },
+ {
+ name: 'Sky',
+ palettes: [
+ { hex: '#f0f9ff', number: 50 },
+ { hex: '#e0f2fe', number: 100 },
+ { hex: '#bae6fd', number: 200 },
+ { hex: '#7dd3fc', number: 300 },
+ { hex: '#38bdf8', number: 400 },
+ { hex: '#0ea5e9', number: 500 },
+ { hex: '#0284c7', number: 600 },
+ { hex: '#0369a1', number: 700 },
+ { hex: '#075985', number: 800 },
+ { hex: '#0c4a6e', number: 900 },
+ { hex: '#082f49', number: 950 }
+ ]
+ },
+ {
+ name: 'Blue',
+ palettes: [
+ { hex: '#eff6ff', number: 50 },
+ { hex: '#dbeafe', number: 100 },
+ { hex: '#bfdbfe', number: 200 },
+ { hex: '#93c5fd', number: 300 },
+ { hex: '#60a5fa', number: 400 },
+ { hex: '#3b82f6', number: 500 },
+ { hex: '#2563eb', number: 600 },
+ { hex: '#1d4ed8', number: 700 },
+ { hex: '#1e40af', number: 800 },
+ { hex: '#1e3a8a', number: 900 },
+ { hex: '#172554', number: 950 }
+ ]
+ },
+ {
+ name: 'Indigo',
+ palettes: [
+ { hex: '#eef2ff', number: 50 },
+ { hex: '#e0e7ff', number: 100 },
+ { hex: '#c7d2fe', number: 200 },
+ { hex: '#a5b4fc', number: 300 },
+ { hex: '#818cf8', number: 400 },
+ { hex: '#6366f1', number: 500 },
+ { hex: '#4f46e5', number: 600 },
+ { hex: '#4338ca', number: 700 },
+ { hex: '#3730a3', number: 800 },
+ { hex: '#312e81', number: 900 },
+ { hex: '#1e1b4b', number: 950 }
+ ]
+ },
+ {
+ name: 'Violet',
+ palettes: [
+ { hex: '#f5f3ff', number: 50 },
+ { hex: '#ede9fe', number: 100 },
+ { hex: '#ddd6fe', number: 200 },
+ { hex: '#c4b5fd', number: 300 },
+ { hex: '#a78bfa', number: 400 },
+ { hex: '#8b5cf6', number: 500 },
+ { hex: '#7c3aed', number: 600 },
+ { hex: '#6d28d9', number: 700 },
+ { hex: '#5b21b6', number: 800 },
+ { hex: '#4c1d95', number: 900 },
+ { hex: '#2e1065', number: 950 }
+ ]
+ },
+ {
+ name: 'Purple',
+ palettes: [
+ { hex: '#faf5ff', number: 50 },
+ { hex: '#f3e8ff', number: 100 },
+ { hex: '#e9d5ff', number: 200 },
+ { hex: '#d8b4fe', number: 300 },
+ { hex: '#c084fc', number: 400 },
+ { hex: '#a855f7', number: 500 },
+ { hex: '#9333ea', number: 600 },
+ { hex: '#7e22ce', number: 700 },
+ { hex: '#6b21a8', number: 800 },
+ { hex: '#581c87', number: 900 },
+ { hex: '#3b0764', number: 950 }
+ ]
+ },
+ {
+ name: 'Fuchsia',
+ palettes: [
+ { hex: '#fdf4ff', number: 50 },
+ { hex: '#fae8ff', number: 100 },
+ { hex: '#f5d0fe', number: 200 },
+ { hex: '#f0abfc', number: 300 },
+ { hex: '#e879f9', number: 400 },
+ { hex: '#d946ef', number: 500 },
+ { hex: '#c026d3', number: 600 },
+ { hex: '#a21caf', number: 700 },
+ { hex: '#86198f', number: 800 },
+ { hex: '#701a75', number: 900 },
+ { hex: '#4a044e', number: 950 }
+ ]
+ },
+ {
+ name: 'Pink',
+ palettes: [
+ { hex: '#fdf2f8', number: 50 },
+ { hex: '#fce7f3', number: 100 },
+ { hex: '#fbcfe8', number: 200 },
+ { hex: '#f9a8d4', number: 300 },
+ { hex: '#f472b6', number: 400 },
+ { hex: '#ec4899', number: 500 },
+ { hex: '#db2777', number: 600 },
+ { hex: '#be185d', number: 700 },
+ { hex: '#9d174d', number: 800 },
+ { hex: '#831843', number: 900 },
+ { hex: '#500724', number: 950 }
+ ]
+ },
+ {
+ name: 'Rose',
+ palettes: [
+ { hex: '#fff1f2', number: 50 },
+ { hex: '#ffe4e6', number: 100 },
+ { hex: '#fecdd3', number: 200 },
+ { hex: '#fda4af', number: 300 },
+ { hex: '#fb7185', number: 400 },
+ { hex: '#f43f5e', number: 500 },
+ { hex: '#e11d48', number: 600 },
+ { hex: '#be123c', number: 700 },
+ { hex: '#9f1239', number: 800 },
+ { hex: '#881337', number: 900 },
+ { hex: '#4c0519', number: 950 }
+ ]
+ }
+];
diff --git a/ruoyi-plus-soybean/packages/color/src/index.ts b/ruoyi-plus-soybean/packages/color/src/index.ts
new file mode 100755
index 0000000..42a4d54
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/color/src/index.ts
@@ -0,0 +1,7 @@
+import { colorPalettes } from './constant';
+
+export * from './palette';
+export * from './shared';
+export { colorPalettes };
+
+export * from './types';
diff --git a/ruoyi-plus-soybean/packages/color/src/palette/antd.ts b/ruoyi-plus-soybean/packages/color/src/palette/antd.ts
new file mode 100755
index 0000000..c3d5b48
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/color/src/palette/antd.ts
@@ -0,0 +1,176 @@
+import type { AnyColor, HsvColor } from 'colord';
+import { getHex, getHsv, isValidColor, mixColor } from '../shared';
+import type { ColorIndex } from '../types';
+
+/** Hue step */
+const hueStep = 2;
+/** Saturation step, light color part */
+const saturationStep = 16;
+/** Saturation step, dark color part */
+const saturationStep2 = 5;
+/** Brightness step, light color part */
+const brightnessStep1 = 5;
+/** Brightness step, dark color part */
+const brightnessStep2 = 15;
+/** Light color count, main color up */
+const lightColorCount = 5;
+/** Dark color count, main color down */
+const darkColorCount = 4;
+
+/**
+ * Get AntD palette color by index
+ *
+ * @param color - Color
+ * @param index - The color index of color palette (the main color index is 6)
+ * @returns Hex color
+ */
+export function getAntDPaletteColorByIndex(color: AnyColor, index: ColorIndex): string {
+ if (!isValidColor(color)) {
+ throw new Error('invalid input color value');
+ }
+
+ if (index === 6) {
+ return getHex(color);
+ }
+
+ const isLight = index < 6;
+ const hsv = getHsv(color);
+ const i = isLight ? lightColorCount + 1 - index : index - lightColorCount - 1;
+
+ const newHsv: HsvColor = {
+ h: getHue(hsv, i, isLight),
+ s: getSaturation(hsv, i, isLight),
+ v: getValue(hsv, i, isLight)
+ };
+
+ return getHex(newHsv);
+}
+
+/** Map of dark color index and opacity */
+const darkColorMap = [
+ { index: 7, opacity: 0.15 },
+ { index: 6, opacity: 0.25 },
+ { index: 5, opacity: 0.3 },
+ { index: 5, opacity: 0.45 },
+ { index: 5, opacity: 0.65 },
+ { index: 5, opacity: 0.85 },
+ { index: 5, opacity: 0.9 },
+ { index: 4, opacity: 0.93 },
+ { index: 3, opacity: 0.95 },
+ { index: 2, opacity: 0.97 },
+ { index: 1, opacity: 0.98 }
+];
+
+/**
+ * Get AntD color palette
+ *
+ * @param color - Color
+ * @param darkTheme - Dark theme
+ * @param darkThemeMixColor - Dark theme mix color (default: #141414)
+ */
+export function getAntDColorPalette(color: AnyColor, darkTheme = false, darkThemeMixColor = '#141414'): string[] {
+ const indexes: ColorIndex[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
+
+ const patterns = indexes.map(index => getAntDPaletteColorByIndex(color, index));
+
+ if (darkTheme) {
+ const darkPatterns = darkColorMap.map(({ index, opacity }) => {
+ const darkColor = mixColor(darkThemeMixColor, patterns[index], opacity);
+
+ return darkColor;
+ });
+
+ return darkPatterns.map(item => getHex(item));
+ }
+
+ return patterns;
+}
+
+/**
+ * Get hue
+ *
+ * @param hsv - Hsv format color
+ * @param i - The relative distance from 6
+ * @param isLight - Is light color
+ */
+function getHue(hsv: HsvColor, i: number, isLight: boolean) {
+ let hue: number;
+
+ const hsvH = Math.round(hsv.h);
+
+ if (hsvH >= 60 && hsvH <= 240) {
+ hue = isLight ? hsvH - hueStep * i : hsvH + hueStep * i;
+ } else {
+ hue = isLight ? hsvH + hueStep * i : hsvH - hueStep * i;
+ }
+
+ if (hue < 0) {
+ hue += 360;
+ }
+
+ if (hue >= 360) {
+ hue -= 360;
+ }
+
+ return hue;
+}
+
+/**
+ * Get saturation
+ *
+ * @param hsv - Hsv format color
+ * @param i - The relative distance from 6
+ * @param isLight - Is light color
+ */
+function getSaturation(hsv: HsvColor, i: number, isLight: boolean) {
+ if (hsv.h === 0 && hsv.s === 0) {
+ return hsv.s;
+ }
+
+ let saturation: number;
+
+ if (isLight) {
+ saturation = hsv.s - saturationStep * i;
+ } else if (i === darkColorCount) {
+ saturation = hsv.s + saturationStep;
+ } else {
+ saturation = hsv.s + saturationStep2 * i;
+ }
+
+ if (saturation > 100) {
+ saturation = 100;
+ }
+
+ if (isLight && i === lightColorCount && saturation > 10) {
+ saturation = 10;
+ }
+
+ if (saturation < 6) {
+ saturation = 6;
+ }
+
+ return saturation;
+}
+
+/**
+ * Get value of hsv
+ *
+ * @param hsv - Hsv format color
+ * @param i - The relative distance from 6
+ * @param isLight - Is light color
+ */
+function getValue(hsv: HsvColor, i: number, isLight: boolean) {
+ let value: number;
+
+ if (isLight) {
+ value = hsv.v + brightnessStep1 * i;
+ } else {
+ value = hsv.v - brightnessStep2 * i;
+ }
+
+ if (value > 100) {
+ value = 100;
+ }
+
+ return value;
+}
diff --git a/ruoyi-plus-soybean/packages/color/src/palette/index.ts b/ruoyi-plus-soybean/packages/color/src/palette/index.ts
new file mode 100755
index 0000000..ba3b853
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/color/src/palette/index.ts
@@ -0,0 +1,45 @@
+import type { AnyColor } from 'colord';
+import { getHex } from '../shared';
+import type { ColorPaletteNumber } from '../types';
+import { getRecommendedColorPalette } from './recommend';
+import { getAntDColorPalette } from './antd';
+
+/**
+ * get color palette by provided color
+ *
+ * @param color
+ * @param recommended whether to get recommended color palette (the provided color may not be the main color)
+ */
+export function getColorPalette(color: AnyColor, recommended = false) {
+ const colorMap = new Map<ColorPaletteNumber, string>();
+
+ if (recommended) {
+ const colorPalette = getRecommendedColorPalette(getHex(color));
+ colorPalette.palettes.forEach(palette => {
+ colorMap.set(palette.number, palette.hex);
+ });
+ } else {
+ const colors = getAntDColorPalette(color);
+
+ const colorNumbers: ColorPaletteNumber[] = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
+
+ colorNumbers.forEach((number, index) => {
+ colorMap.set(number, colors[index]);
+ });
+ }
+
+ return colorMap;
+}
+
+/**
+ * get color palette color by number
+ *
+ * @param color the provided color
+ * @param number the color palette number
+ * @param recommended whether to get recommended color palette (the provided color may not be the main color)
+ */
+export function getPaletteColorByNumber(color: AnyColor, number: ColorPaletteNumber, recommended = false) {
+ const colorMap = getColorPalette(color, recommended);
+
+ return colorMap.get(number as ColorPaletteNumber)!;
+}
diff --git a/ruoyi-plus-soybean/packages/color/src/palette/recommend.ts b/ruoyi-plus-soybean/packages/color/src/palette/recommend.ts
new file mode 100755
index 0000000..904cae2
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/color/src/palette/recommend.ts
@@ -0,0 +1,152 @@
+import { getColorName, getDeltaE, getHsl, isValidColor, transformHslToHex } from '../shared';
+import { colorPalettes } from '../constant';
+import type {
+ ColorPalette,
+ ColorPaletteFamily,
+ ColorPaletteFamilyWithNearestPalette,
+ ColorPaletteMatch,
+ ColorPaletteNumber
+} from '../types';
+
+/**
+ * get recommended color palette by provided color
+ *
+ * @param color the provided color
+ */
+export function getRecommendedColorPalette(color: string) {
+ const colorPaletteFamily = getRecommendedColorPaletteFamily(color);
+
+ const colorMap = new Map<ColorPaletteNumber, ColorPalette>();
+
+ colorPaletteFamily.palettes.forEach(palette => {
+ colorMap.set(palette.number, palette);
+ });
+
+ const mainColor = colorMap.get(500)!;
+ const matchColor = colorPaletteFamily.palettes.find(palette => palette.hex === color)!;
+
+ const colorPalette: ColorPaletteMatch = {
+ ...colorPaletteFamily,
+ colorMap,
+ main: mainColor,
+ match: matchColor
+ };
+
+ return colorPalette;
+}
+
+/**
+ * get recommended palette color by provided color
+ *
+ * @param color the provided color
+ * @param number the color palette number
+ */
+export function getRecommendedPaletteColorByNumber(color: string, number: ColorPaletteNumber) {
+ const colorPalette = getRecommendedColorPalette(color);
+
+ const { hex } = colorPalette.colorMap.get(number)!;
+
+ return hex;
+}
+
+/**
+ * get color palette family by provided color and color name
+ *
+ * @param color the provided color
+ */
+export function getRecommendedColorPaletteFamily(color: string) {
+ if (!isValidColor(color)) {
+ throw new Error('Invalid color, please check color value!');
+ }
+
+ let colorName = getColorName(color);
+
+ colorName = colorName.toLowerCase().replace(/\s/g, '-');
+
+ const { h: h1, s: s1 } = getHsl(color);
+
+ const { nearestLightnessPalette, palettes } = getNearestColorPaletteFamily(color, colorPalettes);
+
+ const { number, hex } = nearestLightnessPalette;
+
+ const { h: h2, s: s2 } = getHsl(hex);
+
+ const deltaH = h1 - h2;
+
+ const sRatio = s1 / s2;
+
+ const colorPaletteFamily: ColorPaletteFamily = {
+ name: colorName,
+ palettes: palettes.map(palette => {
+ let hexValue = color;
+
+ const isSame = number === palette.number;
+
+ if (!isSame) {
+ const { h: h3, s: s3, l } = getHsl(palette.hex);
+
+ const newH = deltaH < 0 ? h3 + deltaH : h3 - deltaH;
+ const newS = s3 * sRatio;
+
+ hexValue = transformHslToHex({
+ h: newH,
+ s: newS,
+ l
+ });
+ }
+
+ return {
+ hex: hexValue,
+ number: palette.number
+ };
+ })
+ };
+
+ return colorPaletteFamily;
+}
+
+/**
+ * get nearest color palette family
+ *
+ * @param color color
+ * @param families color palette families
+ */
+function getNearestColorPaletteFamily(color: string, families: ColorPaletteFamily[]) {
+ const familyWithConfig = families.map(family => {
+ const palettes = family.palettes.map(palette => {
+ return {
+ ...palette,
+ delta: getDeltaE(color, palette.hex)
+ };
+ });
+
+ const nearestPalette = palettes.reduce((prev, curr) => (prev.delta < curr.delta ? prev : curr));
+
+ return {
+ ...family,
+ palettes,
+ nearestPalette
+ };
+ });
+
+ const nearestPaletteFamily = familyWithConfig.reduce((prev, curr) =>
+ prev.nearestPalette.delta < curr.nearestPalette.delta ? prev : curr
+ );
+
+ const { l } = getHsl(color);
+
+ const paletteFamily: ColorPaletteFamilyWithNearestPalette = {
+ ...nearestPaletteFamily,
+ nearestLightnessPalette: nearestPaletteFamily.palettes.reduce((prev, curr) => {
+ const { l: prevLightness } = getHsl(prev.hex);
+ const { l: currLightness } = getHsl(curr.hex);
+
+ const deltaPrev = Math.abs(prevLightness - l);
+ const deltaCurr = Math.abs(currLightness - l);
+
+ return deltaPrev < deltaCurr ? prev : curr;
+ })
+ };
+
+ return paletteFamily;
+}
diff --git a/ruoyi-plus-soybean/packages/color/src/shared/colord.ts b/ruoyi-plus-soybean/packages/color/src/shared/colord.ts
new file mode 100755
index 0000000..4c52330
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/color/src/shared/colord.ts
@@ -0,0 +1,93 @@
+import { colord, extend } from 'colord';
+import namesPlugin from 'colord/plugins/names';
+import mixPlugin from 'colord/plugins/mix';
+import labPlugin from 'colord/plugins/lab';
+import type { AnyColor, HslColor, RgbColor } from 'colord';
+
+extend([namesPlugin, mixPlugin, labPlugin]);
+
+export function isValidColor(color: AnyColor) {
+ return colord(color).isValid();
+}
+
+export function getHex(color: AnyColor) {
+ return colord(color).toHex();
+}
+
+export function getRgb(color: AnyColor) {
+ return colord(color).toRgb();
+}
+
+export function getHsl(color: AnyColor) {
+ return colord(color).toHsl();
+}
+
+export function getHsv(color: AnyColor) {
+ return colord(color).toHsv();
+}
+
+export function getDeltaE(color1: AnyColor, color2: AnyColor) {
+ return colord(color1).delta(color2);
+}
+
+export function transformHslToHex(color: HslColor) {
+ return colord(color).toHex();
+}
+
+/**
+ * Add color alpha
+ *
+ * @param color - Color
+ * @param alpha - Alpha (0 - 1)
+ */
+export function addColorAlpha(color: AnyColor, alpha: number) {
+ return colord(color).alpha(alpha).toHex();
+}
+
+/**
+ * Mix color
+ *
+ * @param firstColor - First color
+ * @param secondColor - Second color
+ * @param ratio - The ratio of the second color (0 - 1)
+ */
+export function mixColor(firstColor: AnyColor, secondColor: AnyColor, ratio: number) {
+ return colord(firstColor).mix(secondColor, ratio).toHex();
+}
+
+/**
+ * Transform color with opacity to similar color without opacity
+ *
+ * @param color - Color
+ * @param alpha - Alpha (0 - 1)
+ * @param bgColor Background color (usually white or black)
+ */
+export function transformColorWithOpacity(color: AnyColor, alpha: number, bgColor = '#ffffff') {
+ const originColor = addColorAlpha(color, alpha);
+ const { r: oR, g: oG, b: oB } = colord(originColor).toRgb();
+
+ const { r: bgR, g: bgG, b: bgB } = colord(bgColor).toRgb();
+
+ function calRgb(or: number, bg: number, al: number) {
+ return bg + (or - bg) * al;
+ }
+
+ const resultRgb: RgbColor = {
+ r: calRgb(oR, bgR, alpha),
+ g: calRgb(oG, bgG, alpha),
+ b: calRgb(oB, bgB, alpha)
+ };
+
+ return colord(resultRgb).toHex();
+}
+
+/**
+ * Is white color
+ *
+ * @param color - Color
+ */
+export function isWhiteColor(color: AnyColor) {
+ return colord(color).isEqual('#ffffff');
+}
+
+export { colord };
diff --git a/ruoyi-plus-soybean/packages/color/src/shared/index.ts b/ruoyi-plus-soybean/packages/color/src/shared/index.ts
new file mode 100755
index 0000000..ae293ce
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/color/src/shared/index.ts
@@ -0,0 +1,2 @@
+export * from './colord';
+export * from './name';
diff --git a/ruoyi-plus-soybean/packages/color/src/shared/name.ts b/ruoyi-plus-soybean/packages/color/src/shared/name.ts
new file mode 100755
index 0000000..866bd33
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/color/src/shared/name.ts
@@ -0,0 +1,49 @@
+import { colorNames } from '../constant';
+import { getHex, getHsl, getRgb } from './colord';
+
+/**
+ * Get color name
+ *
+ * @param color
+ */
+export function getColorName(color: string) {
+ const hex = getHex(color);
+ const rgb = getRgb(color);
+ const hsl = getHsl(color);
+
+ let ndf = 0;
+ let ndf1 = 0;
+ let ndf2 = 0;
+ let cl = -1;
+ let df = -1;
+
+ let name = '';
+
+ colorNames.some((item, index) => {
+ const [hexValue, colorName] = item;
+
+ const match = hex === hexValue;
+
+ if (match) {
+ name = colorName;
+ } else {
+ const { r, g, b } = getRgb(hexValue);
+ const { h, s, l } = getHsl(hexValue);
+
+ ndf1 = (rgb.r - r) ** 2 + (rgb.g - g) ** 2 + (rgb.b - b) ** 2;
+ ndf2 = (hsl.h - h) ** 2 + (hsl.s - s) ** 2 + (hsl.l - l) ** 2;
+
+ ndf = ndf1 + ndf2 * 2;
+ if (df < 0 || df > ndf) {
+ df = ndf;
+ cl = index;
+ }
+ }
+
+ return match;
+ });
+
+ name = colorNames[cl][1];
+
+ return name;
+}
diff --git a/ruoyi-plus-soybean/packages/color/src/types/index.ts b/ruoyi-plus-soybean/packages/color/src/types/index.ts
new file mode 100755
index 0000000..3a63bc8
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/color/src/types/index.ts
@@ -0,0 +1,58 @@
+/**
+ * the color palette number
+ *
+ * the main color number is 500
+ */
+export type ColorPaletteNumber = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;
+
+/** the color palette */
+export type ColorPalette = {
+ /** the color hex value */
+ hex: string;
+ /**
+ * the color number
+ *
+ * - 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950
+ */
+ number: ColorPaletteNumber;
+};
+
+/** the color palette family */
+export type ColorPaletteFamily = {
+ /** the color palette family name */
+ name: string;
+ /** the color palettes */
+ palettes: ColorPalette[];
+};
+
+/** the color palette with delta */
+export type ColorPaletteWithDelta = ColorPalette & {
+ delta: number;
+};
+
+/** the color palette family with nearest palette */
+export type ColorPaletteFamilyWithNearestPalette = ColorPaletteFamily & {
+ nearestPalette: ColorPaletteWithDelta;
+ nearestLightnessPalette: ColorPaletteWithDelta;
+};
+
+/** the color palette match */
+export type ColorPaletteMatch = ColorPaletteFamily & {
+ /** the color map of the palette */
+ colorMap: Map<ColorPaletteNumber, ColorPalette>;
+ /**
+ * the main color of the palette
+ *
+ * which number is 500
+ */
+ main: ColorPalette;
+ /** the match color of the palette */
+ match: ColorPalette;
+};
+
+/**
+ * The color index of color palette
+ *
+ * From left to right, the color is from light to dark, 6 is main color
+ */
+export type ColorIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
diff --git a/ruoyi-plus-soybean/packages/color/tsconfig.json b/ruoyi-plus-soybean/packages/color/tsconfig.json
new file mode 100755
index 0000000..5823ed5
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/color/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "jsx": "preserve",
+ "lib": ["DOM", "ESNext"],
+ "baseUrl": ".",
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "types": ["node"],
+ "strict": true,
+ "strictNullChecks": true,
+ "noUnusedLocals": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/ruoyi-plus-soybean/packages/hooks/package.json b/ruoyi-plus-soybean/packages/hooks/package.json
new file mode 100755
index 0000000..301ffdc
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/hooks/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@sa/hooks",
+ "version": "2.0.2",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "typesVersions": {
+ "*": {
+ "*": ["./src/*"]
+ }
+ },
+ "dependencies": {
+ "@sa/axios": "workspace:*",
+ "@sa/utils": "workspace:*"
+ }
+}
diff --git a/ruoyi-plus-soybean/packages/hooks/src/index.ts b/ruoyi-plus-soybean/packages/hooks/src/index.ts
new file mode 100755
index 0000000..3a73bbc
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/hooks/src/index.ts
@@ -0,0 +1,9 @@
+import useBoolean from './use-boolean';
+import useLoading from './use-loading';
+import useCountDown from './use-count-down';
+import useContext from './use-context';
+import useSvgIconRender from './use-svg-icon-render';
+import useTable from './use-table';
+
+export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useTable };
+export type * from './use-table';
diff --git a/ruoyi-plus-soybean/packages/hooks/src/use-boolean.ts b/ruoyi-plus-soybean/packages/hooks/src/use-boolean.ts
new file mode 100755
index 0000000..a60d45a
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/hooks/src/use-boolean.ts
@@ -0,0 +1,31 @@
+import { ref } from 'vue';
+
+/**
+ * Boolean
+ *
+ * @param initValue Init value
+ */
+export default function useBoolean(initValue = false) {
+ const bool = ref(initValue);
+
+ function setBool(value: boolean) {
+ bool.value = value;
+ }
+ function setTrue() {
+ setBool(true);
+ }
+ function setFalse() {
+ setBool(false);
+ }
+ function toggle() {
+ setBool(!bool.value);
+ }
+
+ return {
+ bool,
+ setBool,
+ setTrue,
+ setFalse,
+ toggle
+ };
+}
diff --git a/ruoyi-plus-soybean/packages/hooks/src/use-context.ts b/ruoyi-plus-soybean/packages/hooks/src/use-context.ts
new file mode 100755
index 0000000..cea9164
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/hooks/src/use-context.ts
@@ -0,0 +1,96 @@
+import { inject, provide } from 'vue';
+
+/**
+ * Use context
+ *
+ * @example
+ * ```ts
+ * // there are three vue files: A.vue, B.vue, C.vue, and A.vue is the parent component of B.vue and C.vue
+ *
+ * // context.ts
+ * import { ref } from 'vue';
+ * import { useContext } from '@sa/hooks';
+ *
+ * export const [provideDemoContext, useDemoContext] = useContext('demo', () => {
+ * const count = ref(0);
+ *
+ * function increment() {
+ * count.value++;
+ * }
+ *
+ * function decrement() {
+ * count.value--;
+ * }
+ *
+ * return {
+ * count,
+ * increment,
+ * decrement
+ * };
+ * })
+ * ``` // A.vue
+ * ```vue
+ * <template>
+ * <div>A</div>
+ * </template>
+ * <script setup lang="ts">
+ * import { provideDemoContext } from './context';
+ *
+ * provideDemoContext();
+ * // const { increment } = provideDemoContext(); // also can control the store in the parent component
+ * </script>
+ * ``` // B.vue
+ * ```vue
+ * <template>
+ * <div>B</div>
+ * </template>
+ * <script setup lang="ts">
+ * import { useDemoContext } from './context';
+ *
+ * const { count, increment } = useDemoContext();
+ * </script>
+ * ```;
+ *
+ * // C.vue is same as B.vue
+ *
+ * @param contextName Context name
+ * @param fn Context function
+ */
+export default function useContext<Arguments extends Array<any>, T>(
+ contextName: string,
+ composable: (...args: Arguments) => T
+) {
+ const key = Symbol(contextName);
+
+ /**
+ * Injects the context value.
+ *
+ * @param consumerName - The name of the component that is consuming the context. If provided, the component must be
+ * used within the context provider.
+ * @param defaultValue - The default value to return if the context is not provided.
+ * @returns The context value.
+ */
+ const useInject = <N extends string | null | undefined = undefined>(
+ consumerName?: N,
+ defaultValue?: T
+ ): N extends null | undefined ? T | null : T => {
+ const value = inject(key, defaultValue);
+
+ if (consumerName && !value) {
+ throw new Error(`\`${consumerName}\` must be used within \`${contextName}\``);
+ }
+
+ // @ts-expect-error - we want to return null if the value is undefined or null
+ return value || null;
+ };
+
+ const useProvide = (...args: Arguments) => {
+ const value = composable(...args);
+
+ provide(key, value);
+
+ return value;
+ };
+
+ return [useProvide, useInject] as const;
+}
diff --git a/ruoyi-plus-soybean/packages/hooks/src/use-count-down.ts b/ruoyi-plus-soybean/packages/hooks/src/use-count-down.ts
new file mode 100755
index 0000000..4f95b73
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/hooks/src/use-count-down.ts
@@ -0,0 +1,68 @@
+import { computed, onScopeDispose, ref } from 'vue';
+import { useRafFn } from '@vueuse/core';
+
+/**
+ * A hook for implementing a countdown timer. It uses `requestAnimationFrame` for smooth and accurate timing,
+ * independent of the screen refresh rate.
+ *
+ * @param initialSeconds - The total number of seconds for the countdown.
+ */
+export default function useCountDown(initialSeconds: number) {
+ const remainingSeconds = ref(0);
+
+ const count = computed(() => Math.ceil(remainingSeconds.value));
+
+ const isCounting = computed(() => remainingSeconds.value > 0);
+
+ const { pause, resume } = useRafFn(
+ ({ delta }) => {
+ // delta: milliseconds elapsed since the last frame.
+
+ // If countdown already reached zero or below, ensure it's 0 and stop.
+ if (remainingSeconds.value <= 0) {
+ remainingSeconds.value = 0;
+ pause();
+ return;
+ }
+
+ // Calculate seconds passed since the last frame.
+ const secondsPassed = delta / 1000;
+ remainingSeconds.value -= secondsPassed;
+
+ // If countdown has finished after decrementing.
+ if (remainingSeconds.value <= 0) {
+ remainingSeconds.value = 0;
+ pause();
+ }
+ },
+ { immediate: false } // The timer does not start automatically.
+ );
+
+ /**
+ * Starts the countdown.
+ *
+ * @param [updatedSeconds=initialSeconds] - Optionally, start with a new duration. Default is `initialSeconds`
+ */
+ function start(updatedSeconds: number = initialSeconds) {
+ remainingSeconds.value = updatedSeconds;
+ resume();
+ }
+
+ /** Stops the countdown and resets the remaining time to 0. */
+ function stop() {
+ remainingSeconds.value = 0;
+ pause();
+ }
+
+ // Ensure the rAF loop is cleaned up when the component is unmounted.
+ onScopeDispose(() => {
+ pause();
+ });
+
+ return {
+ count,
+ isCounting,
+ start,
+ stop
+ };
+}
diff --git a/ruoyi-plus-soybean/packages/hooks/src/use-loading.ts b/ruoyi-plus-soybean/packages/hooks/src/use-loading.ts
new file mode 100755
index 0000000..b8f89ad
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/hooks/src/use-loading.ts
@@ -0,0 +1,16 @@
+import useBoolean from './use-boolean';
+
+/**
+ * Loading
+ *
+ * @param initValue Init value
+ */
+export default function useLoading(initValue = false) {
+ const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(initValue);
+
+ return {
+ loading,
+ startLoading,
+ endLoading
+ };
+}
diff --git a/ruoyi-plus-soybean/packages/hooks/src/use-request.ts b/ruoyi-plus-soybean/packages/hooks/src/use-request.ts
new file mode 100755
index 0000000..51784af
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/hooks/src/use-request.ts
@@ -0,0 +1,82 @@
+import { ref } from 'vue';
+import type { Ref } from 'vue';
+import { createFlatRequest } from '@sa/axios';
+import type {
+ AxiosError,
+ CreateAxiosDefaults,
+ CustomAxiosRequestConfig,
+ MappedType,
+ RequestInstanceCommon,
+ RequestOption,
+ ResponseType
+} from '@sa/axios';
+import useLoading from './use-loading';
+
+export type HookRequestInstanceResponseSuccessData<ApiData> = {
+ data: Ref<ApiData>;
+ error: Ref<null>;
+};
+
+export type HookRequestInstanceResponseFailData<ResponseData> = {
+ data: Ref<null>;
+ error: Ref<AxiosError<ResponseData>>;
+};
+
+export type HookRequestInstanceResponseData<ResponseData, ApiData> = {
+ loading: Ref<boolean>;
+} & (HookRequestInstanceResponseSuccessData<ApiData> | HookRequestInstanceResponseFailData<ResponseData>);
+
+export interface HookRequestInstance<
+ ResponseData,
+ ApiData,
+ State extends Record<string, unknown>
+> extends RequestInstanceCommon<State> {
+ <T extends ApiData = ApiData, R extends ResponseType = 'json'>(
+ config: CustomAxiosRequestConfig
+ ): HookRequestInstanceResponseData<ResponseData, MappedType<R, T>>;
+}
+
+/**
+ * create a hook request instance
+ *
+ * @param axiosConfig
+ * @param options
+ */
+export default function createHookRequest<ResponseData, ApiData, State extends Record<string, unknown>>(
+ axiosConfig?: CreateAxiosDefaults,
+ options?: Partial<RequestOption<ResponseData, ApiData, State>>
+) {
+ const request = createFlatRequest<ResponseData, ApiData, State>(axiosConfig, options);
+
+ const hookRequest: HookRequestInstance<ResponseData, ApiData, State> = function hookRequest<
+ T extends ApiData = ApiData,
+ R extends ResponseType = 'json'
+ >(config: CustomAxiosRequestConfig) {
+ const { loading, startLoading, endLoading } = useLoading();
+
+ const data = ref(null) as Ref<MappedType<R, T>>;
+ const error = ref(null) as Ref<AxiosError<ResponseData> | null>;
+
+ startLoading();
+
+ request(config).then(res => {
+ if (res.data) {
+ data.value = res.data as MappedType<R, T>;
+ } else {
+ error.value = res.error;
+ }
+
+ endLoading();
+ });
+
+ return {
+ loading,
+ data,
+ error
+ };
+ } as HookRequestInstance<ResponseData, ApiData, State>;
+
+ hookRequest.cancelAllRequest = request.cancelAllRequest;
+
+ return hookRequest;
+}
diff --git a/ruoyi-plus-soybean/packages/hooks/src/use-svg-icon-render.ts b/ruoyi-plus-soybean/packages/hooks/src/use-svg-icon-render.ts
new file mode 100755
index 0000000..62c2206
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/hooks/src/use-svg-icon-render.ts
@@ -0,0 +1,50 @@
+import { h } from 'vue';
+import type { Component } from 'vue';
+
+/**
+ * Svg icon render hook
+ *
+ * @param SvgIcon Svg icon component
+ */
+export default function useSvgIconRender(SvgIcon: Component) {
+ interface IconConfig {
+ /** Iconify icon name */
+ icon?: string;
+ /** Local icon name */
+ localIcon?: string;
+ /** Icon color */
+ color?: string;
+ /** Icon size */
+ fontSize?: number;
+ }
+
+ type IconStyle = Partial<Pick<CSSStyleDeclaration, 'color' | 'fontSize'>>;
+
+ /**
+ * Svg icon VNode
+ *
+ * @param config
+ */
+ const SvgIconVNode = (config: IconConfig) => {
+ const { color, fontSize, icon, localIcon } = config;
+
+ const style: IconStyle = {};
+
+ if (color) {
+ style.color = color;
+ }
+ if (fontSize) {
+ style.fontSize = `${fontSize}px`;
+ }
+
+ if (!icon && !localIcon) {
+ return undefined;
+ }
+
+ return () => h(SvgIcon, { icon, localIcon, style });
+ };
+
+ return {
+ SvgIconVNode
+ };
+}
diff --git a/ruoyi-plus-soybean/packages/hooks/src/use-table.ts b/ruoyi-plus-soybean/packages/hooks/src/use-table.ts
new file mode 100755
index 0000000..7323e7f
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/hooks/src/use-table.ts
@@ -0,0 +1,131 @@
+import { computed, ref } from 'vue';
+import type { Ref, VNodeChild } from 'vue';
+import useBoolean from './use-boolean';
+import useLoading from './use-loading';
+
+export interface PaginationData<T> {
+ data: T[];
+ pageNum: number;
+ total: number;
+}
+
+type GetApiData<ApiData, Pagination extends boolean> = Pagination extends true ? PaginationData<ApiData> : ApiData[];
+
+type Transform<ResponseData, ApiData, Pagination extends boolean> = (
+ response: ResponseData
+) => GetApiData<ApiData, Pagination>;
+
+export type TableColumnCheckTitle = string | ((...args: any) => VNodeChild);
+
+export type TableColumnCheck = {
+ key: string;
+ title: TableColumnCheckTitle;
+ checked: boolean;
+ visible: boolean;
+};
+
+export interface UseTableOptions<ResponseData, ApiData, Column, Pagination extends boolean> {
+ /**
+ * api function to get table data
+ */
+ api: () => Promise<ResponseData>;
+ /**
+ * whether to enable pagination
+ */
+ pagination?: Pagination;
+ /**
+ * transform api response to table data
+ */
+ transform: Transform<ResponseData, ApiData, Pagination>;
+ /**
+ * columns factory
+ */
+ columns: () => Column[];
+ /**
+ * get column checks
+ */
+ getColumnChecks: (columns: Column[]) => TableColumnCheck[];
+ /**
+ * get columns
+ */
+ getColumns: (columns: Column[], checks: TableColumnCheck[]) => Column[];
+ /**
+ * callback when response fetched
+ */
+ onFetched?: (data: GetApiData<ApiData, Pagination>) => void | Promise<void>;
+ /**
+ * whether to get data immediately
+ *
+ * @default true
+ */
+ immediate?: boolean;
+}
+
+export default function useTable<ResponseData, ApiData, Column, Pagination extends boolean>(
+ options: UseTableOptions<ResponseData, ApiData, Column, Pagination>
+) {
+ const { loading, startLoading, endLoading } = useLoading();
+ const { bool: empty, setBool: setEmpty } = useBoolean();
+
+ const { api, pagination, transform, columns, getColumnChecks, getColumns, onFetched, immediate = true } = options;
+
+ const data = ref([]) as Ref<ApiData[]>;
+
+ const columnChecks = ref(getColumnChecks(columns())) as Ref<TableColumnCheck[]>;
+
+ const $columns = computed(() => getColumns(columns(), columnChecks.value));
+
+ function reloadColumns() {
+ const checkMap = new Map(columnChecks.value.map(col => [col.key, col.checked]));
+
+ const defaultChecks = getColumnChecks(columns());
+
+ columnChecks.value = defaultChecks.map(col => ({
+ ...col,
+ checked: checkMap.get(col.key) ?? col.checked
+ }));
+ }
+
+ async function getData() {
+ try {
+ startLoading();
+
+ const response = await api();
+
+ const transformed = transform(response);
+
+ data.value = getTableData(transformed, pagination);
+
+ setEmpty(data.value.length === 0);
+
+ await onFetched?.(transformed);
+ } finally {
+ endLoading();
+ }
+ }
+
+ if (immediate) {
+ getData();
+ }
+
+ return {
+ loading,
+ empty,
+ data,
+ columns: $columns,
+ columnChecks,
+ reloadColumns,
+ getData
+ };
+}
+
+function getTableData<ApiData, Pagination extends boolean>(
+ data: GetApiData<ApiData, Pagination>,
+ pagination?: Pagination
+) {
+ if (pagination) {
+ return (data as PaginationData<ApiData>).data;
+ }
+
+ return data as ApiData[];
+}
diff --git a/ruoyi-plus-soybean/packages/hooks/tsconfig.json b/ruoyi-plus-soybean/packages/hooks/tsconfig.json
new file mode 100755
index 0000000..5823ed5
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/hooks/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "jsx": "preserve",
+ "lib": ["DOM", "ESNext"],
+ "baseUrl": ".",
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "types": ["node"],
+ "strict": true,
+ "strictNullChecks": true,
+ "noUnusedLocals": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/ruoyi-plus-soybean/packages/materials/package.json b/ruoyi-plus-soybean/packages/materials/package.json
new file mode 100755
index 0000000..4f70202
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@sa/materials",
+ "version": "2.0.2",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "typesVersions": {
+ "*": {
+ "*": ["./src/*"]
+ }
+ },
+ "dependencies": {
+ "@sa/utils": "workspace:*",
+ "simplebar-vue": "2.4.2"
+ },
+ "devDependencies": {
+ "typed-css-modules": "0.9.1"
+ }
+}
diff --git a/ruoyi-plus-soybean/packages/materials/src/index.ts b/ruoyi-plus-soybean/packages/materials/src/index.ts
new file mode 100755
index 0000000..f6ca01d
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/index.ts
@@ -0,0 +1,6 @@
+import AdminLayout, { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID } from './libs/admin-layout';
+import PageTab from './libs/page-tab';
+import SimpleScrollbar from './libs/simple-scrollbar';
+
+export { AdminLayout, LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX, PageTab, SimpleScrollbar };
+export * from './types';
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.module.css b/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.module.css
new file mode 100755
index 0000000..e5c8ac8
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.module.css
@@ -0,0 +1,63 @@
+/* @type */
+
+.layout-header,
+.layout-header-placement {
+ height: var(--soy-header-height);
+}
+
+.layout-header {
+ z-index: var(--soy-header-z-index);
+}
+
+.layout-tab {
+ top: var(--soy-header-height);
+ height: var(--soy-tab-height);
+ z-index: var(--soy-tab-z-index);
+}
+
+.layout-tab-placement {
+ height: var(--soy-tab-height);
+}
+
+.layout-sider {
+ width: var(--soy-sider-width);
+ z-index: var(--soy-sider-z-index);
+}
+
+.layout-mobile-sider {
+ z-index: var(--soy-sider-z-index);
+}
+
+.layout-mobile-sider-mask {
+ z-index: var(--soy-mobile-sider-z-index);
+}
+
+.layout-sider_collapsed {
+ width: var(--soy-sider-collapsed-width);
+ z-index: var(--soy-sider-z-index);
+}
+
+.layout-footer,
+.layout-footer-placement {
+ height: var(--soy-footer-height);
+}
+
+.layout-footer {
+ z-index: var(--soy-footer-z-index);
+}
+
+.left-gap {
+ padding-left: var(--soy-sider-width);
+}
+
+.left-gap_collapsed {
+ padding-left: var(--soy-sider-collapsed-width);
+}
+
+.sider-padding-top {
+ padding-top: var(--soy-header-height);
+}
+
+.sider-padding-bottom {
+ padding-bottom: var(--soy-footer-height);
+}
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.module.css.d.ts b/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.module.css.d.ts
new file mode 100755
index 0000000..c326c84
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.module.css.d.ts
@@ -0,0 +1,18 @@
+declare const styles: {
+ readonly 'layout-header': string;
+ readonly 'layout-header-placement': string;
+ readonly 'layout-tab': string;
+ readonly 'layout-tab-placement': string;
+ readonly 'layout-sider': string;
+ readonly 'layout-mobile-sider': string;
+ readonly 'layout-mobile-sider-mask': string;
+ readonly 'layout-sider_collapsed': string;
+ readonly 'layout-footer': string;
+ readonly 'layout-footer-placement': string;
+ readonly 'left-gap': string;
+ readonly 'left-gap_collapsed': string;
+ readonly 'sider-padding-top': string;
+ readonly 'sider-padding-bottom': string;
+};
+
+export default styles;
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.ts b/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.ts
new file mode 100755
index 0000000..0687362
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.ts
@@ -0,0 +1,5 @@
+import AdminLayout from './index.vue';
+import { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID } from './shared';
+
+export default AdminLayout;
+export { LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX };
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.vue b/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.vue
new file mode 100755
index 0000000..8bbc552
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/index.vue
@@ -0,0 +1,236 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import type { AdminLayoutProps } from '../../types';
+import { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID, createLayoutCssVars } from './shared';
+import style from './index.module.css';
+
+defineOptions({
+ name: 'AdminLayout'
+});
+
+const props = withDefaults(defineProps<AdminLayoutProps>(), {
+ mode: 'vertical',
+ scrollMode: 'content',
+ scrollElId: LAYOUT_SCROLL_EL_ID,
+ commonClass: 'transition-all-300',
+ fixedTop: true,
+ maxZIndex: LAYOUT_MAX_Z_INDEX,
+ headerVisible: true,
+ headerHeight: 56,
+ tabVisible: true,
+ tabHeight: 48,
+ siderVisible: true,
+ siderCollapse: false,
+ siderWidth: 220,
+ siderCollapsedWidth: 64,
+ footerVisible: true,
+ footerHeight: 48,
+ rightFooter: false
+});
+
+interface Emits {
+ /** Update siderCollapse */
+ (e: 'update:siderCollapse', collapse: boolean): void;
+}
+
+const emit = defineEmits<Emits>();
+
+type SlotFn = (props?: Record<string, unknown>) => any;
+
+type Slots = {
+ /** Main */
+ default?: SlotFn;
+ /** Header */
+ header?: SlotFn;
+ /** Tab */
+ tab?: SlotFn;
+ /** Sider */
+ sider?: SlotFn;
+ /** Footer */
+ footer?: SlotFn;
+};
+
+const slots = defineSlots<Slots>();
+
+const cssVars = computed(() => createLayoutCssVars(props));
+
+// config visible
+const showHeader = computed(() => Boolean(slots.header) && props.headerVisible);
+const showTab = computed(() => Boolean(slots.tab) && props.tabVisible);
+const showSider = computed(() => !props.isMobile && Boolean(slots.sider) && props.siderVisible);
+const showMobileSider = computed(() => props.isMobile && Boolean(slots.sider) && props.siderVisible);
+const showFooter = computed(() => Boolean(slots.footer) && props.footerVisible);
+
+// scroll mode
+const isWrapperScroll = computed(() => props.scrollMode === 'wrapper');
+const isContentScroll = computed(() => props.scrollMode === 'content');
+
+// layout direction
+const isVertical = computed(() => props.mode === 'vertical');
+const isHorizontal = computed(() => props.mode === 'horizontal');
+
+const fixedHeaderAndTab = computed(() => props.fixedTop || (isHorizontal.value && isWrapperScroll.value));
+
+// css
+const leftGapClass = computed(() => {
+ if (!props.fullContent && showSider.value) {
+ return props.siderCollapse ? style['left-gap_collapsed'] : style['left-gap'];
+ }
+
+ return '';
+});
+
+const headerLeftGapClass = computed(() => (isVertical.value ? leftGapClass.value : ''));
+
+const footerLeftGapClass = computed(() => {
+ const condition1 = isVertical.value;
+ const condition2 = isHorizontal.value && isWrapperScroll.value && !props.fixedFooter;
+ const condition3 = Boolean(isHorizontal.value && props.rightFooter);
+
+ if (condition1 || condition2 || condition3) {
+ return leftGapClass.value;
+ }
+
+ return '';
+});
+
+const siderPaddingClass = computed(() => {
+ let cls = '';
+
+ if (showHeader.value && !headerLeftGapClass.value) {
+ cls += style['sider-padding-top'];
+ }
+ if (showFooter.value && !footerLeftGapClass.value) {
+ cls += ` ${style['sider-padding-bottom']}`;
+ }
+
+ return cls;
+});
+
+function handleClickMask() {
+ emit('update:siderCollapse', true);
+}
+</script>
+
+<template>
+ <div class="relative h-full" :class="[commonClass]" :style="cssVars">
+ <div
+ :id="isWrapperScroll ? scrollElId : undefined"
+ class="h-full flex flex-col"
+ :class="[commonClass, scrollWrapperClass, { 'overflow-y-auto': isWrapperScroll }]"
+ >
+ <!-- Header -->
+ <template v-if="showHeader">
+ <header
+ v-show="!fullContent"
+ class="flex-shrink-0"
+ :class="[
+ style['layout-header'],
+ commonClass,
+ headerLeftGapClass,
+ { 'absolute top-0 left-0 w-full': fixedHeaderAndTab }
+ ]"
+ >
+ <slot name="header"></slot>
+ </header>
+ <div
+ v-show="!fullContent && fixedHeaderAndTab"
+ class="flex-shrink-0 overflow-hidden"
+ :class="[style['layout-header-placement']]"
+ ></div>
+ </template>
+
+ <!-- Tab -->
+ <template v-if="showTab">
+ <div
+ class="flex-shrink-0"
+ :class="[
+ style['layout-tab'],
+ commonClass,
+ tabClass,
+ { 'top-0!': fullContent || !showHeader },
+ leftGapClass,
+ { 'absolute left-0 w-full': fixedHeaderAndTab }
+ ]"
+ >
+ <slot name="tab"></slot>
+ </div>
+ <div
+ v-show="fullContent || fixedHeaderAndTab"
+ class="flex-shrink-0 overflow-hidden"
+ :class="[style['layout-tab-placement']]"
+ ></div>
+ </template>
+
+ <!-- Sider -->
+ <template v-if="showSider">
+ <aside
+ v-show="!fullContent"
+ class="absolute left-0 top-0 h-full"
+ :class="[
+ commonClass,
+ siderClass,
+ siderPaddingClass,
+ siderCollapse ? style['layout-sider_collapsed'] : style['layout-sider']
+ ]"
+ >
+ <slot name="sider"></slot>
+ </aside>
+ </template>
+
+ <!-- Mobile Sider -->
+ <template v-if="showMobileSider">
+ <aside
+ class="absolute left-0 top-0 h-full w-0 bg-white"
+ :class="[
+ commonClass,
+ mobileSiderClass,
+ style['layout-mobile-sider'],
+ siderCollapse ? 'overflow-hidden' : style['layout-sider']
+ ]"
+ >
+ <slot name="sider"></slot>
+ </aside>
+ <div
+ v-show="!siderCollapse"
+ class="absolute left-0 top-0 h-full w-full bg-[rgba(0,0,0,0.2)]"
+ :class="[style['layout-mobile-sider-mask']]"
+ @click="handleClickMask"
+ ></div>
+ </template>
+
+ <!-- Main Content -->
+ <main
+ :id="isContentScroll ? scrollElId : undefined"
+ class="flex flex-col flex-grow"
+ :class="[commonClass, contentClass, leftGapClass, { 'overflow-y-auto': isContentScroll }]"
+ >
+ <slot></slot>
+ </main>
+
+ <!-- Footer -->
+ <template v-if="showFooter">
+ <footer
+ v-show="!fullContent"
+ class="flex-shrink-0"
+ :class="[
+ style['layout-footer'],
+ commonClass,
+ footerClass,
+ footerLeftGapClass,
+ { 'absolute left-0 bottom-0 w-full': fixedFooter }
+ ]"
+ >
+ <slot name="footer"></slot>
+ </footer>
+ <div
+ v-show="!fullContent && fixedFooter"
+ class="flex-shrink-0 overflow-hidden"
+ :class="[style['layout-footer-placement']]"
+ ></div>
+ </template>
+ </div>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/shared.ts b/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/shared.ts
new file mode 100755
index 0000000..940451e
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/admin-layout/shared.ts
@@ -0,0 +1,68 @@
+import type { AdminLayoutProps, LayoutCssVars, LayoutCssVarsProps } from '../../types';
+
+/** The id of the scroll element of the layout */
+export const LAYOUT_SCROLL_EL_ID = '__SCROLL_EL_ID__';
+
+/** The max z-index of the layout */
+export const LAYOUT_MAX_Z_INDEX = 100;
+
+/**
+ * Create layout css vars by css vars props
+ *
+ * @param props Css vars props
+ */
+function createLayoutCssVarsByCssVarsProps(props: LayoutCssVarsProps) {
+ const cssVars: LayoutCssVars = {
+ '--soy-header-height': `${props.headerHeight}px`,
+ '--soy-header-z-index': props.headerZIndex,
+ '--soy-tab-height': `${props.tabHeight}px`,
+ '--soy-tab-z-index': props.tabZIndex,
+ '--soy-sider-width': `${props.siderWidth}px`,
+ '--soy-sider-collapsed-width': `${props.siderCollapsedWidth}px`,
+ '--soy-sider-z-index': props.siderZIndex,
+ '--soy-mobile-sider-z-index': props.mobileSiderZIndex,
+ '--soy-footer-height': `${props.footerHeight}px`,
+ '--soy-footer-z-index': props.footerZIndex
+ };
+
+ return cssVars;
+}
+
+/**
+ * Create layout css vars
+ *
+ * @param props
+ */
+export function createLayoutCssVars(props: AdminLayoutProps) {
+ const {
+ mode,
+ isMobile,
+ maxZIndex = LAYOUT_MAX_Z_INDEX,
+ headerHeight,
+ tabHeight,
+ siderWidth,
+ siderCollapsedWidth,
+ footerHeight
+ } = props;
+
+ const headerZIndex = maxZIndex - 3;
+ const tabZIndex = maxZIndex - 5;
+ const siderZIndex = mode === 'vertical' || isMobile ? maxZIndex - 1 : maxZIndex - 4;
+ const mobileSiderZIndex = isMobile ? maxZIndex - 2 : 0;
+ const footerZIndex = maxZIndex - 5;
+
+ const cssProps: LayoutCssVarsProps = {
+ headerHeight,
+ headerZIndex,
+ tabHeight,
+ tabZIndex,
+ siderWidth,
+ siderZIndex,
+ mobileSiderZIndex,
+ siderCollapsedWidth,
+ footerHeight,
+ footerZIndex
+ };
+
+ return createLayoutCssVarsByCssVarsProps(cssProps);
+}
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/button-tab.vue b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/button-tab.vue
new file mode 100755
index 0000000..0826e71
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/button-tab.vue
@@ -0,0 +1,53 @@
+<script setup lang="ts">
+import type { PageTabProps } from '../../types';
+import style from './index.module.css';
+
+defineOptions({
+ name: 'ButtonTab'
+});
+
+defineProps<PageTabProps>();
+
+type SlotFn = (props?: Record<string, unknown>) => any;
+
+type Slots = {
+ /**
+ * Slot
+ *
+ * The center content of the tab
+ */
+ default?: SlotFn;
+ /**
+ * Slot
+ *
+ * The left content of the tab
+ */
+ prefix?: SlotFn;
+ /**
+ * Slot
+ *
+ * The right content of the tab
+ */
+ suffix?: SlotFn;
+};
+
+defineSlots<Slots>();
+</script>
+
+<template>
+ <div
+ class=":soy: relative inline-flex cursor-pointer items-center justify-center gap-12px whitespace-nowrap border-(1px solid) rounded-4px px-12px py-4px"
+ :class="[
+ style['button-tab'],
+ { [style['button-tab_dark']]: darkMode },
+ { [style['button-tab_active']]: active },
+ { [style['button-tab_active_dark']]: active && darkMode }
+ ]"
+ >
+ <slot name="prefix"></slot>
+ <slot></slot>
+ <slot name="suffix"></slot>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/chrome-tab-bg.vue b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/chrome-tab-bg.vue
new file mode 100755
index 0000000..151e03a
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/chrome-tab-bg.vue
@@ -0,0 +1,31 @@
+<script setup lang="ts">
+defineOptions({
+ name: 'ChromeTabBg'
+});
+</script>
+
+<template>
+ <svg class="size-full">
+ <defs>
+ <symbol id="geometry-left" viewBox="0 0 214 36">
+ <path d="M17 0h197v36H0v-2c4.5 0 9-3.5 9-8V8c0-4.5 3.5-8 8-8z" />
+ </symbol>
+ <symbol id="geometry-right" viewBox="0 0 214 36">
+ <use xlink:href="#geometry-left" />
+ </symbol>
+ <clipPath>
+ <rect width="100%" height="100%" x="0" />
+ </clipPath>
+ </defs>
+ <svg width="51%" height="100%">
+ <use xlink:href="#geometry-left" width="214" height="36" fill="currentColor" />
+ </svg>
+ <g transform="scale(-1, 1)">
+ <svg width="51%" height="100%" x="-100%" y="0">
+ <use xlink:href="#geometry-right" width="214" height="36" fill="currentColor" />
+ </svg>
+ </g>
+ </svg>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/chrome-tab.vue b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/chrome-tab.vue
new file mode 100755
index 0000000..e4509b0
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/chrome-tab.vue
@@ -0,0 +1,58 @@
+<script setup lang="ts">
+import type { PageTabProps } from '../../types';
+import ChromeTabBg from './chrome-tab-bg.vue';
+import style from './index.module.css';
+
+defineOptions({
+ name: 'ChromeTab'
+});
+
+defineProps<PageTabProps>();
+
+type SlotFn = (props?: Record<string, unknown>) => any;
+
+type Slots = {
+ /**
+ * Slot
+ *
+ * The center content of the tab
+ */
+ default?: SlotFn;
+ /**
+ * Slot
+ *
+ * The left content of the tab
+ */
+ prefix?: SlotFn;
+ /**
+ * Slot
+ *
+ * The right content of the tab
+ */
+ suffix?: SlotFn;
+};
+
+defineSlots<Slots>();
+</script>
+
+<template>
+ <div
+ class=":soy: relative inline-flex cursor-pointer items-center justify-center gap-16px whitespace-nowrap px-24px py-6px -mr-18px"
+ :class="[
+ style['chrome-tab'],
+ { [style['chrome-tab_dark']]: darkMode },
+ { [style['chrome-tab_active']]: active },
+ { [style['chrome-tab_active_dark']]: active && darkMode }
+ ]"
+ >
+ <div class=":soy: pointer-events-none absolute left-0 top-0 h-full w-full -z-1" :class="[style['chrome-tab__bg']]">
+ <ChromeTabBg />
+ </div>
+ <slot name="prefix"></slot>
+ <slot></slot>
+ <slot name="suffix"></slot>
+ <div class=":soy: absolute right-7px h-16px w-1px bg-#1f2225" :class="[style['chrome-tab-divider']]"></div>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.module.css b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.module.css
new file mode 100755
index 0000000..d2baf65
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.module.css
@@ -0,0 +1,121 @@
+/* @type */
+
+.button-tab {
+ border-color: #e5e7eb;
+}
+
+.button-tab_dark {
+ border-color: #ffffff3d;
+}
+
+.button-tab:hover {
+ color: var(--soy-primary-color);
+ border-color: var(--soy-primary-color-opacity3);
+}
+
+.button-tab_active {
+ color: var(--soy-primary-color);
+ border-color: var(--soy-primary-color-opacity3);
+ background-color: var(--soy-primary-color-opacity1);
+}
+
+.button-tab_active_dark {
+ background-color: var(--soy-primary-color-opacity2);
+}
+
+.button-tab .svg-close:hover {
+ font-size: 12px;
+ color: #ffffff;
+ background-color: var(--soy-primary-color);
+}
+
+.button-tab_dark .svg-close:hover {
+ color: #000000;
+}
+
+.chrome-tab:hover {
+ z-index: 9;
+}
+
+.chrome-tab_active {
+ z-index: 10;
+ color: var(--soy-primary-color);
+}
+
+.chrome-tab__bg {
+ color: transparent;
+}
+
+.chrome-tab_active .chrome-tab__bg {
+ color: var(--soy-primary-color1);
+}
+
+.chrome-tab_active_dark .chrome-tab__bg {
+ color: var(--soy-primary-color2);
+}
+
+.chrome-tab:hover .chrome-tab__bg {
+ color: #dee1e6;
+}
+
+.chrome-tab_active:hover .chrome-tab__bg {
+ color: var(--soy-primary-color1);
+}
+
+.chrome-tab_dark:hover .chrome-tab__bg {
+ color: #333333;
+}
+
+.chrome-tab_active_dark:hover .chrome-tab__bg {
+ color: var(--soy-primary-color2);
+}
+
+.chrome-tab .svg-close:hover {
+ font-size: 12px;
+ color: #ffffff;
+ background-color: #9ca3af;
+}
+
+.chrome-tab_active .svg-close:hover {
+ background-color: var(--soy-primary-color);
+}
+
+.chrome-tab_dark .svg-close:hover {
+ color: #000000;
+}
+
+.chrome-tab_active .chrome-tab-divider {
+ opacity: 0;
+}
+
+.chrome-tab:hover .chrome-tab-divider {
+ opacity: 0;
+}
+
+.chrome-tab_dark .chrome-tab-divider {
+ background-color: rgba(255, 255, 255, 0.9);
+}
+
+.slider-tab {
+ background-color: transparent;
+ height: 100%;
+ border-bottom: 2px solid transparent;
+}
+
+.slider-tab_dark {
+ background-color: transparent;
+}
+
+.slider-tab:hover {
+ color: var(--soy-primary-color);
+}
+
+.slider-tab_active {
+ color: var(--soy-primary-color);
+ background-color: var(--soy-primary-color-opacity1);
+ border-bottom-color: var(--soy-primary-color);
+}
+
+.slider-tab_active_dark {
+ background-color: var(--soy-primary-color-opacity2);
+}
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.module.css.d.ts b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.module.css.d.ts
new file mode 100755
index 0000000..47c5999
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.module.css.d.ts
@@ -0,0 +1,19 @@
+declare const styles: {
+ readonly 'button-tab': string;
+ readonly 'button-tab_dark': string;
+ readonly 'button-tab_active': string;
+ readonly 'button-tab_active_dark': string;
+ readonly 'chrome-tab': string;
+ readonly 'chrome-tab_active': string;
+ readonly 'chrome-tab__bg': string;
+ readonly 'chrome-tab_active_dark': string;
+ readonly 'chrome-tab_dark': string;
+ readonly 'chrome-tab-divider': string;
+ readonly 'svg-close': string;
+ readonly 'slider-tab': string;
+ readonly 'slider-tab_active': string;
+ readonly 'slider-tab_active_dark': string;
+ readonly 'slider-tab_dark': string;
+};
+
+export default styles;
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.ts b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.ts
new file mode 100755
index 0000000..b402adf
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.ts
@@ -0,0 +1,3 @@
+import PageTab from './index.vue';
+
+export default PageTab;
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.vue b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.vue
new file mode 100755
index 0000000..c1f6b62
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/index.vue
@@ -0,0 +1,77 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import type { Component } from 'vue';
+import type { PageTabMode, PageTabProps } from '../../types';
+import { ACTIVE_COLOR, createTabCssVars } from './shared';
+import ChromeTab from './chrome-tab.vue';
+import ButtonTab from './button-tab.vue';
+import SliderTab from './slider-tab.vue';
+import SvgClose from './svg-close.vue';
+import style from './index.module.css';
+
+defineOptions({
+ name: 'PageTab'
+});
+
+const props = withDefaults(defineProps<PageTabProps>(), {
+ mode: 'chrome',
+ commonClass: 'transition-all-300',
+ activeColor: ACTIVE_COLOR,
+ closable: true
+});
+
+interface Emits {
+ (e: 'close'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const activeTabComponent = computed(() => {
+ const { mode, chromeClass, buttonClass, sliderClass } = props;
+
+ const tabComponentMap = {
+ chrome: {
+ component: ChromeTab,
+ class: chromeClass
+ },
+ button: {
+ component: ButtonTab,
+ class: buttonClass
+ },
+ slider: {
+ component: SliderTab,
+ class: sliderClass
+ }
+ } satisfies Record<PageTabMode, { component: Component; class?: string }>;
+
+ return tabComponentMap[mode];
+});
+
+const cssVars = computed(() => createTabCssVars(props.activeColor));
+
+const bindProps = computed(() => {
+ const { chromeClass: _chromeCls, buttonClass: _btnCls, sliderClass: _sliderCls, ...rest } = props;
+
+ return rest;
+});
+
+function handleClose() {
+ emit('close');
+}
+</script>
+
+<template>
+ <component :is="activeTabComponent.component" :class="activeTabComponent.class" :style="cssVars" v-bind="bindProps">
+ <template #prefix>
+ <slot name="prefix"></slot>
+ </template>
+ <slot></slot>
+ <template #suffix>
+ <slot name="suffix">
+ <SvgClose v-if="closable" :class="[style['svg-close']]" @pointerdown.stop="handleClose" />
+ </slot>
+ </template>
+ </component>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/shared.ts b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/shared.ts
new file mode 100755
index 0000000..db71f5d
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/shared.ts
@@ -0,0 +1,31 @@
+import { addColorAlpha, transformColorWithOpacity } from '@sa/color';
+import type { PageTabCssVars, PageTabCssVarsProps } from '../../types';
+
+/** The active color of the tab */
+export const ACTIVE_COLOR = '#1890ff';
+
+function createCssVars(props: PageTabCssVarsProps) {
+ const cssVars: PageTabCssVars = {
+ '--soy-primary-color': props.primaryColor,
+ '--soy-primary-color1': props.primaryColor1,
+ '--soy-primary-color2': props.primaryColor2,
+ '--soy-primary-color-opacity1': props.primaryColorOpacity1,
+ '--soy-primary-color-opacity2': props.primaryColorOpacity2,
+ '--soy-primary-color-opacity3': props.primaryColorOpacity3
+ };
+
+ return cssVars;
+}
+
+export function createTabCssVars(primaryColor: string) {
+ const cssProps: PageTabCssVarsProps = {
+ primaryColor,
+ primaryColor1: transformColorWithOpacity(primaryColor, 0.1, '#ffffff'),
+ primaryColor2: transformColorWithOpacity(primaryColor, 0.3, '#000000'),
+ primaryColorOpacity1: addColorAlpha(primaryColor, 0.1),
+ primaryColorOpacity2: addColorAlpha(primaryColor, 0.15),
+ primaryColorOpacity3: addColorAlpha(primaryColor, 0.3)
+ };
+
+ return createCssVars(cssProps);
+}
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/slider-tab.vue b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/slider-tab.vue
new file mode 100755
index 0000000..3521e5f
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/slider-tab.vue
@@ -0,0 +1,53 @@
+<script setup lang="ts">
+import type { PageTabProps } from '../../types';
+import style from './index.module.css';
+
+defineOptions({
+ name: 'SliderTab'
+});
+
+defineProps<PageTabProps>();
+
+type SlotFn = (props?: Record<string, unknown>) => any;
+
+type Slots = {
+ /**
+ * Slot
+ *
+ * The center content of the tab
+ */
+ default?: SlotFn;
+ /**
+ * Slot
+ *
+ * The left content of the tab
+ */
+ prefix?: SlotFn;
+ /**
+ * Slot
+ *
+ * The right content of the tab
+ */
+ suffix?: SlotFn;
+};
+
+defineSlots<Slots>();
+</script>
+
+<template>
+ <div
+ class=":soy: relative inline-flex cursor-pointer items-center justify-center gap-6px whitespace-nowrap px-12px py-4px"
+ :class="[
+ style['slider-tab'],
+ { [style['slider-tab_dark']]: darkMode },
+ { [style['slider-tab_active']]: active },
+ { [style['slider-tab_active_dark']]: active && darkMode }
+ ]"
+ >
+ <slot name="prefix"></slot>
+ <slot></slot>
+ <slot name="suffix"></slot>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/svg-close.vue b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/svg-close.vue
new file mode 100755
index 0000000..26ba07a
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/page-tab/svg-close.vue
@@ -0,0 +1,18 @@
+<script setup lang="ts">
+defineOptions({
+ name: 'SvgClose'
+});
+</script>
+
+<template>
+ <div class=":soy: relative h-16px w-16px inline-flex items-center justify-center rd-50% text-14px">
+ <svg width="1em" height="1em" viewBox="0 0 1024 1024">
+ <path
+ fill="currentColor"
+ d="m563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8L295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512L196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1l216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
+ />
+ </svg>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/simple-scrollbar/index.ts b/ruoyi-plus-soybean/packages/materials/src/libs/simple-scrollbar/index.ts
new file mode 100755
index 0000000..1453a06
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/simple-scrollbar/index.ts
@@ -0,0 +1,3 @@
+import SimpleScrollbar from './index.vue';
+
+export default SimpleScrollbar;
diff --git a/ruoyi-plus-soybean/packages/materials/src/libs/simple-scrollbar/index.vue b/ruoyi-plus-soybean/packages/materials/src/libs/simple-scrollbar/index.vue
new file mode 100755
index 0000000..1e8076b
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/libs/simple-scrollbar/index.vue
@@ -0,0 +1,18 @@
+<script setup lang="ts">
+import Simplebar from 'simplebar-vue';
+import 'simplebar-vue/dist/simplebar.min.css';
+
+defineOptions({
+ name: 'SimpleScrollbar'
+});
+</script>
+
+<template>
+ <div class="h-full flex-1-hidden">
+ <Simplebar class="h-full">
+ <slot />
+ </Simplebar>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/packages/materials/src/types/index.ts b/ruoyi-plus-soybean/packages/materials/src/types/index.ts
new file mode 100755
index 0000000..e0649f1
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/src/types/index.ts
@@ -0,0 +1,291 @@
+/** Header config */
+interface AdminLayoutHeaderConfig {
+ /**
+ * Whether header is visible
+ *
+ * @default true
+ */
+ headerVisible?: boolean;
+ /**
+ * Header height
+ *
+ * @default 56px
+ */
+ headerHeight?: number;
+}
+
+/** Tab config */
+interface AdminLayoutTabConfig {
+ /**
+ * Whether tab is visible
+ *
+ * @default true
+ */
+ tabVisible?: boolean;
+ /**
+ * Tab class
+ *
+ * @default ''
+ */
+ tabClass?: string;
+ /**
+ * Tab height
+ *
+ * @default 48px
+ */
+ tabHeight?: number;
+}
+
+/** Sider config */
+interface AdminLayoutSiderConfig {
+ /**
+ * Whether sider is visible
+ *
+ * @default true
+ */
+ siderVisible?: boolean;
+ /**
+ * Sider class
+ *
+ * @default ''
+ */
+ siderClass?: string;
+ /**
+ * Mobile sider class
+ *
+ * @default ''
+ */
+ mobileSiderClass?: string;
+ /**
+ * Sider collapse status
+ *
+ * @default false
+ */
+ siderCollapse?: boolean;
+ /**
+ * Sider width when collapse is false
+ *
+ * @default '220px'
+ */
+ siderWidth?: number;
+ /**
+ * Sider width when collapse is true
+ *
+ * @default '64px'
+ */
+ siderCollapsedWidth?: number;
+}
+
+/** Content config */
+export interface AdminLayoutContentConfig {
+ /**
+ * Content class
+ *
+ * @default ''
+ */
+ contentClass?: string;
+ /**
+ * Whether content is full the page
+ *
+ * If true, other elements will be hidden by `display: none`
+ */
+ fullContent?: boolean;
+}
+
+/** Footer config */
+export interface AdminLayoutFooterConfig {
+ /**
+ * Whether footer is visible
+ *
+ * @default true
+ */
+ footerVisible?: boolean;
+ /**
+ * Whether footer is fixed
+ *
+ * @default true
+ */
+ fixedFooter?: boolean;
+ /**
+ * Footer class
+ *
+ * @default ''
+ */
+ footerClass?: string;
+ /**
+ * Footer height
+ *
+ * @default 48px
+ */
+ footerHeight?: number;
+ /**
+ * Whether footer is on the right side
+ *
+ * When the layout is vertical, the footer is on the right side
+ */
+ rightFooter?: boolean;
+}
+
+/**
+ * Layout mode
+ *
+ * - Horizontal
+ * - Vertical
+ */
+export type LayoutMode = 'horizontal' | 'vertical';
+
+/**
+ * The scroll mode when content overflow
+ *
+ * - Wrapper: the layout component's wrapper element has a scrollbar
+ * - Content: the layout component's content element has a scrollbar
+ *
+ * @default 'wrapper'
+ */
+export type LayoutScrollMode = 'wrapper' | 'content';
+
+/** Admin layout props */
+export interface AdminLayoutProps
+ extends
+ AdminLayoutHeaderConfig,
+ AdminLayoutTabConfig,
+ AdminLayoutSiderConfig,
+ AdminLayoutContentConfig,
+ AdminLayoutFooterConfig {
+ /**
+ * Layout mode
+ *
+ * - {@link LayoutMode}
+ */
+ mode?: LayoutMode;
+ /** Is mobile layout */
+ isMobile?: boolean;
+ /**
+ * Scroll mode
+ *
+ * - {@link ScrollMode}
+ */
+ scrollMode?: LayoutScrollMode;
+ /**
+ * The id of the scroll element of the layout
+ *
+ * It can be used to get the corresponding Dom and scroll it
+ *
+ * @example
+ * use the default id by import
+ * ```ts
+ * import { adminLayoutScrollElId } from '@sa/vue-materials';
+ * ```
+ *
+ * @default
+ * ```ts
+ * const adminLayoutScrollElId = '__ADMIN_LAYOUT_SCROLL_EL_ID__'
+ * ```
+ */
+ scrollElId?: string;
+ /** The class of the scroll element */
+ scrollElClass?: string;
+ /** The class of the scroll wrapper element */
+ scrollWrapperClass?: string;
+ /**
+ * The common class of the layout
+ *
+ * Is can be used to configure the transition animation
+ *
+ * @default 'transition-all-300'
+ */
+ commonClass?: string;
+ /**
+ * Whether fix the header and tab
+ *
+ * @default true
+ */
+ fixedTop?: boolean;
+ /**
+ * The max z-index of the layout
+ *
+ * The z-index of Header,Tab,Sider and Footer will not exceed this value
+ */
+ maxZIndex?: number;
+}
+
+type Kebab<S extends string> = S extends Uncapitalize<S> ? S : `-${Uncapitalize<S>}`;
+
+type KebabCase<S extends string> = S extends `${infer Start}${infer End}`
+ ? `${Uncapitalize<Start>}${KebabCase<Kebab<End>>}`
+ : S;
+
+type Prefix = '--soy-';
+
+export type LayoutCssVarsProps = Pick<
+ AdminLayoutProps,
+ 'headerHeight' | 'tabHeight' | 'siderWidth' | 'siderCollapsedWidth' | 'footerHeight'
+> & {
+ headerZIndex?: number;
+ tabZIndex?: number;
+ siderZIndex?: number;
+ mobileSiderZIndex?: number;
+ footerZIndex?: number;
+};
+
+export type LayoutCssVars = {
+ [K in keyof LayoutCssVarsProps as `${Prefix}${KebabCase<K>}`]: string | number;
+};
+
+/**
+ * The mode of the tab
+ *
+ * - Button: button style
+ * - Chrome: chrome style
+ *
+ * @default chrome
+ */
+export type PageTabMode = 'button' | 'chrome' | 'slider';
+
+export interface PageTabProps {
+ /** Whether is dark mode */
+ darkMode?: boolean;
+ /**
+ * The mode of the tab
+ *
+ * - {@link TabMode}
+ */
+ mode?: PageTabMode;
+ /**
+ * The common class of the layout
+ *
+ * Is can be used to configure the transition animation
+ *
+ * @default 'transition-all-300'
+ */
+ commonClass?: string;
+ /** The class of the button tab */
+ buttonClass?: string;
+ /** The class of the chrome tab */
+ chromeClass?: string;
+ /** The class of the title tab */
+ sliderClass?: string;
+ /** Whether the tab is active */
+ active?: boolean;
+ /** The color of the active tab */
+ activeColor?: string;
+ /**
+ * Whether the tab is closable
+ *
+ * Show the close icon when true
+ */
+ closable?: boolean;
+}
+
+export type PageTabCssVarsProps = {
+ primaryColor: string;
+ primaryColor1: string;
+ primaryColor2: string;
+ primaryColorOpacity1: string;
+ primaryColorOpacity2: string;
+ primaryColorOpacity3: string;
+};
+
+export type PageTabCssVars = {
+ [K in keyof PageTabCssVarsProps as `${Prefix}${KebabCase<K>}`]: string | number;
+};
diff --git a/ruoyi-plus-soybean/packages/materials/tsconfig.json b/ruoyi-plus-soybean/packages/materials/tsconfig.json
new file mode 100755
index 0000000..5823ed5
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/materials/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "jsx": "preserve",
+ "lib": ["DOM", "ESNext"],
+ "baseUrl": ".",
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "types": ["node"],
+ "strict": true,
+ "strictNullChecks": true,
+ "noUnusedLocals": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/ruoyi-plus-soybean/packages/scripts/bin.ts b/ruoyi-plus-soybean/packages/scripts/bin.ts
new file mode 100755
index 0000000..1a1817b
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/bin.ts
@@ -0,0 +1,3 @@
+#!/usr/bin/env tsx
+
+import './src/index.ts';
diff --git a/ruoyi-plus-soybean/packages/scripts/package.json b/ruoyi-plus-soybean/packages/scripts/package.json
new file mode 100755
index 0000000..60589a1
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "@sa/scripts",
+ "version": "2.0.2",
+ "bin": {
+ "sa": "./bin.ts"
+ },
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "typesVersions": {
+ "*": {
+ "*": ["./src/*"]
+ }
+ },
+ "devDependencies": {
+ "@soybeanjs/changelog": "0.3.25",
+ "bumpp": "10.3.2",
+ "c12": "3.3.3",
+ "cac": "6.7.14",
+ "consola": "3.4.2",
+ "enquirer": "2.4.1",
+ "execa": "9.6.1",
+ "kolorist": "1.8.0",
+ "npm-check-updates": "19.2.0",
+ "picomatch": "4.0.3",
+ "rimraf": "6.1.2"
+ }
+}
diff --git a/ruoyi-plus-soybean/packages/scripts/src/commands/changelog.ts b/ruoyi-plus-soybean/packages/scripts/src/commands/changelog.ts
new file mode 100755
index 0000000..e01ce40
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/src/commands/changelog.ts
@@ -0,0 +1,10 @@
+import { generateChangelog, generateTotalChangelog } from '@soybeanjs/changelog';
+import type { ChangelogOption } from '@soybeanjs/changelog';
+
+export async function genChangelog(options?: Partial<ChangelogOption>, total = false) {
+ if (total) {
+ await generateTotalChangelog(options);
+ } else {
+ await generateChangelog(options);
+ }
+}
diff --git a/ruoyi-plus-soybean/packages/scripts/src/commands/cleanup.ts b/ruoyi-plus-soybean/packages/scripts/src/commands/cleanup.ts
new file mode 100755
index 0000000..a9d3990
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/src/commands/cleanup.ts
@@ -0,0 +1,5 @@
+import { rimraf } from 'rimraf';
+
+export async function cleanup(paths: string[]) {
+ await rimraf(paths, { glob: true });
+}
diff --git a/ruoyi-plus-soybean/packages/scripts/src/commands/git-commit.ts b/ruoyi-plus-soybean/packages/scripts/src/commands/git-commit.ts
new file mode 100755
index 0000000..35c0df5
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/src/commands/git-commit.ts
@@ -0,0 +1,84 @@
+import path from 'node:path';
+import { readFileSync } from 'node:fs';
+import { prompt } from 'enquirer';
+import { execCommand } from '../shared';
+import { locales } from '../locales';
+import type { Lang } from '../locales';
+
+interface PromptObject {
+ types: string;
+ scopes: string;
+ description: string;
+}
+
+/**
+ * Git commit with Conventional Commits standard
+ *
+ * @param lang
+ */
+export async function gitCommit(lang: Lang = 'en-us') {
+ const { gitCommitMessages, gitCommitTypes, gitCommitScopes } = locales[lang];
+
+ const typesChoices = gitCommitTypes.map(([value, msg]) => {
+ const nameWithSuffix = `${value}:`;
+
+ const message = `${nameWithSuffix.padEnd(12)}${msg}`;
+
+ return {
+ name: value,
+ message
+ };
+ });
+
+ const scopesChoices = gitCommitScopes.map(([value, msg]) => ({
+ name: value,
+ message: `${value.padEnd(30)} (${msg})`
+ }));
+
+ const result = await prompt<PromptObject>([
+ {
+ name: 'types',
+ type: 'select',
+ message: gitCommitMessages.types,
+ choices: typesChoices
+ },
+ {
+ name: 'scopes',
+ type: 'select',
+ message: gitCommitMessages.scopes,
+ choices: scopesChoices
+ },
+ {
+ name: 'description',
+ type: 'text',
+ message: gitCommitMessages.description
+ }
+ ]);
+
+ const breaking = result.description.startsWith('!') ? '!' : '';
+
+ const description = result.description.replace(/^!/, '').trim();
+
+ const commitMsg = `${result.types}(${result.scopes})${breaking}: ${description}`;
+
+ await execCommand('git', ['commit', '-m', commitMsg], { stdio: 'inherit' });
+}
+
+/** Git commit message verify */
+export async function gitCommitVerify(lang: Lang = 'en-us', ignores: RegExp[] = []) {
+ const gitPath = await execCommand('git', ['rev-parse', '--show-toplevel']);
+
+ const gitMsgPath = path.join(gitPath, '.git', 'COMMIT_EDITMSG');
+
+ const commitMsg = readFileSync(gitMsgPath, 'utf8').trim();
+
+ if (ignores.some(regExp => regExp.test(commitMsg))) return;
+
+ const REG_EXP = /(?<type>[a-z]+)(?:\((?<scope>.+)\))?(?<breaking>!)?: (?<description>.+)/i;
+
+ if (!REG_EXP.test(commitMsg)) {
+ const errorMsg = locales[lang].gitCommitVerify;
+
+ throw new Error(errorMsg);
+ }
+}
diff --git a/ruoyi-plus-soybean/packages/scripts/src/commands/index.ts b/ruoyi-plus-soybean/packages/scripts/src/commands/index.ts
new file mode 100755
index 0000000..db4fc15
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/src/commands/index.ts
@@ -0,0 +1,6 @@
+export * from './git-commit';
+export * from './cleanup';
+export * from './update-pkg';
+export * from './changelog';
+export * from './release';
+export * from './router';
diff --git a/ruoyi-plus-soybean/packages/scripts/src/commands/release.ts b/ruoyi-plus-soybean/packages/scripts/src/commands/release.ts
new file mode 100755
index 0000000..1cf3bc3
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/src/commands/release.ts
@@ -0,0 +1,12 @@
+import { versionBump } from 'bumpp';
+
+export async function release(execute = 'pnpm sa changelog', push = true) {
+ await versionBump({
+ files: ['**/package.json', '!**/node_modules'],
+ execute,
+ all: true,
+ tag: true,
+ commit: 'chore(projects): release v%s',
+ push
+ });
+}
diff --git a/ruoyi-plus-soybean/packages/scripts/src/commands/router.ts b/ruoyi-plus-soybean/packages/scripts/src/commands/router.ts
new file mode 100755
index 0000000..f407589
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/src/commands/router.ts
@@ -0,0 +1,90 @@
+import process from 'node:process';
+import path from 'node:path';
+import { writeFile } from 'node:fs/promises';
+import { existsSync, mkdirSync } from 'node:fs';
+import { prompt } from 'enquirer';
+import { green, red } from 'kolorist';
+
+interface PromptObject {
+ routeName: string;
+ addRouteParams: boolean;
+ routeParams: string;
+}
+
+/** generate route */
+export async function generateRoute() {
+ const result = await prompt<PromptObject>([
+ {
+ name: 'routeName',
+ type: 'text',
+ message: 'please enter route name',
+ initial: 'demo-route_child'
+ },
+ {
+ name: 'addRouteParams',
+ type: 'confirm',
+ message: 'add route params?',
+ initial: false
+ }
+ ]);
+
+ if (result.addRouteParams) {
+ const answers = await prompt<PromptObject>({
+ name: 'routeParams',
+ type: 'text',
+ message: 'please enter route params',
+ initial: 'id'
+ });
+
+ Object.assign(result, answers);
+ }
+
+ const PAGE_DIR_NAME_PATTERN = /^[\w-]+[0-9a-zA-Z]+$/;
+
+ if (!PAGE_DIR_NAME_PATTERN.test(result.routeName)) {
+ throw new Error(`${red('route name is invalid, it only allow letters, numbers, "-" or "_"')}.
+For example:
+(1) one level route: ${green('demo-route')}
+(2) two level route: ${green('demo-route_child')}
+(3) multi level route: ${green('demo-route_child_child')}
+(4) group route: ${green('_ignore_demo-route')}'
+`);
+ }
+
+ const PARAM_REG = /^\w+$/g;
+
+ if (result.routeParams && !PARAM_REG.test(result.routeParams)) {
+ throw new Error(red('route params is invalid, it only allow letters, numbers or "_".'));
+ }
+
+ const cwd = process.cwd();
+
+ const [dir, ...rest] = result.routeName.split('_') as string[];
+
+ let routeDir = path.join(cwd, 'src', 'views', dir);
+
+ if (rest.length) {
+ routeDir = path.join(routeDir, rest.join('_'));
+ }
+
+ if (!existsSync(routeDir)) {
+ mkdirSync(routeDir, { recursive: true });
+ } else {
+ throw new Error(red('route already exists'));
+ }
+
+ const fileName = result.routeParams ? `[${result.routeParams}].vue` : 'index.vue';
+
+ const vueTemplate = `<script setup lang="ts"></script>
+
+<template>
+ <div>${result.routeName}</div>
+</template>
+
+<style scoped></style>
+`;
+
+ const filePath = path.join(routeDir, fileName);
+
+ await writeFile(filePath, vueTemplate);
+}
diff --git a/ruoyi-plus-soybean/packages/scripts/src/commands/update-pkg.ts b/ruoyi-plus-soybean/packages/scripts/src/commands/update-pkg.ts
new file mode 100755
index 0000000..25e168e
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/src/commands/update-pkg.ts
@@ -0,0 +1,5 @@
+import { execCommand } from '../shared';
+
+export async function updatePkg(args: string[] = ['--deep', '-u']) {
+ execCommand('npx', ['npm-check-updates', ...args], { stdio: 'inherit' });
+}
diff --git a/ruoyi-plus-soybean/packages/scripts/src/config/index.ts b/ruoyi-plus-soybean/packages/scripts/src/config/index.ts
new file mode 100755
index 0000000..02b0a58
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/src/config/index.ts
@@ -0,0 +1,39 @@
+import process from 'node:process';
+import { loadConfig } from 'c12';
+import type { CliOption } from '../types';
+
+const defaultOptions: CliOption = {
+ cwd: process.cwd(),
+ cleanupDirs: [
+ 'dist',
+ '**/package-lock.json',
+ '**/yarn.lock',
+ '**/pnpm-lock.yaml',
+ '**/node_modules',
+ '!node_modules/**'
+ ],
+ ncuCommandArgs: ['--deep', '-u'],
+ changelogOptions: {},
+ gitCommitVerifyIgnores: [
+ /^((Merge pull request)|(Merge (.*?) into (.*?)|(Merge branch (.*?)))(?:\r?\n)*$)/m,
+ /^(Merge tag (.*?))(?:\r?\n)*$/m,
+ /^(R|r)evert (.*)/,
+ /^(amend|fixup|squash)!/,
+ /^(Merged (.*?)(in|into) (.*)|Merged PR (.*): (.*))/,
+ /^Merge remote-tracking branch(\s*)(.*)/,
+ /^Automatic merge(.*)/,
+ /^Auto-merged (.*?) into (.*)/
+ ]
+};
+
+export async function loadCliOptions(overrides?: Partial<CliOption>, cwd = process.cwd()) {
+ const { config } = await loadConfig<Partial<CliOption>>({
+ name: 'soybean',
+ defaults: defaultOptions,
+ overrides,
+ cwd,
+ packageJson: true
+ });
+
+ return config as CliOption;
+}
diff --git a/ruoyi-plus-soybean/packages/scripts/src/index.ts b/ruoyi-plus-soybean/packages/scripts/src/index.ts
new file mode 100755
index 0000000..34367d7
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/src/index.ts
@@ -0,0 +1,109 @@
+import cac from 'cac';
+import { blue, lightGreen } from 'kolorist';
+import { version } from '../package.json';
+import { cleanup, genChangelog, generateRoute, gitCommit, gitCommitVerify, release, updatePkg } from './commands';
+import { loadCliOptions } from './config';
+import type { Lang } from './locales';
+
+type Command = 'cleanup' | 'update-pkg' | 'git-commit' | 'git-commit-verify' | 'changelog' | 'release' | 'gen-route';
+
+type CommandAction<A extends object> = (args?: A) => Promise<void> | void;
+
+type CommandWithAction<A extends object = object> = Record<Command, { desc: string; action: CommandAction<A> }>;
+
+interface CommandArg {
+ /** Execute additional command after bumping and before git commit. Defaults to 'pnpm sa changelog' */
+ execute?: string;
+ /** Indicates whether to push the git commit and tag. Defaults to true */
+ push?: boolean;
+ /** Generate changelog by total tags */
+ total?: boolean;
+ /**
+ * The glob pattern of dirs to clean up
+ *
+ * If not set, it will use the default value
+ *
+ * Multiple values use "," to separate them
+ */
+ cleanupDir?: string;
+ /**
+ * display lang of cli
+ *
+ * @default 'en-us'
+ */
+ lang?: Lang;
+}
+
+export async function setupCli() {
+ const cliOptions = await loadCliOptions();
+
+ const cli = cac(blue('soybean-admin'));
+
+ cli
+ .version(lightGreen(version))
+ .option(
+ '-e, --execute [command]',
+ "Execute additional command after bumping and before git commit. Defaults to 'npx soy changelog'"
+ )
+ .option('-p, --push', 'Indicates whether to push the git commit and tag')
+ .option('-t, --total', 'Generate changelog by total tags')
+ .option(
+ '-c, --cleanupDir <dir>',
+ 'The glob pattern of dirs to cleanup, If not set, it will use the default value, Multiple values use "," to separate them'
+ )
+ .option('-l, --lang <lang>', 'display lang of cli', { default: 'en-us', type: [String] })
+ .help();
+
+ const commands: CommandWithAction<CommandArg> = {
+ cleanup: {
+ desc: 'delete dirs: node_modules, dist, etc.',
+ action: async () => {
+ await cleanup(cliOptions.cleanupDirs);
+ }
+ },
+ 'update-pkg': {
+ desc: 'update package.json dependencies versions',
+ action: async () => {
+ await updatePkg(cliOptions.ncuCommandArgs);
+ }
+ },
+ 'git-commit': {
+ desc: 'git commit, generate commit message which match Conventional Commits standard',
+ action: async args => {
+ await gitCommit(args?.lang);
+ }
+ },
+ 'git-commit-verify': {
+ desc: 'verify git commit message, make sure it match Conventional Commits standard',
+ action: async args => {
+ await gitCommitVerify(args?.lang, cliOptions.gitCommitVerifyIgnores);
+ }
+ },
+ changelog: {
+ desc: 'generate changelog',
+ action: async args => {
+ await genChangelog(cliOptions.changelogOptions, args?.total);
+ }
+ },
+ release: {
+ desc: 'release: update version, generate changelog, commit code',
+ action: async args => {
+ await release(args?.execute, args?.push);
+ }
+ },
+ 'gen-route': {
+ desc: 'generate route',
+ action: async () => {
+ await generateRoute();
+ }
+ }
+ };
+
+ for (const [command, { desc, action }] of Object.entries(commands)) {
+ cli.command(command, lightGreen(desc)).action(action);
+ }
+
+ cli.parse();
+}
+
+setupCli();
diff --git a/ruoyi-plus-soybean/packages/scripts/src/locales/index.ts b/ruoyi-plus-soybean/packages/scripts/src/locales/index.ts
new file mode 100755
index 0000000..74321d0
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/src/locales/index.ts
@@ -0,0 +1,82 @@
+import { bgRed, green, red, yellow } from 'kolorist';
+
+export type Lang = 'zh-cn' | 'en-us';
+
+export const locales = {
+ 'zh-cn': {
+ gitCommitMessages: {
+ types: '璇烽�夋嫨鎻愪氦绫诲瀷',
+ scopes: '璇烽�夋嫨鎻愪氦鑼冨洿',
+ description: `璇疯緭鍏ユ弿杩颁俊鎭紙${yellow('!')}寮�澶磋〃绀虹牬鍧忔�ф敼鍔╜
+ },
+ gitCommitTypes: [
+ ['feat', '鏂板姛鑳�'],
+ ['feat-wip', '寮�鍙戜腑鐨勫姛鑳斤紝姣斿鏌愬姛鑳界殑閮ㄥ垎浠g爜'],
+ ['fix', '淇Bug'],
+ ['docs', '鍙秹鍙婃枃妗f洿鏂�'],
+ ['typo', '浠g爜鎴栨枃妗e嫎璇紝姣斿閿欒鎷煎啓'],
+ ['style', '淇敼浠g爜椋庢牸锛屼笉褰卞搷浠g爜鍚箟鐨勫彉鏇�'],
+ ['refactor', '浠g爜閲嶆瀯锛屾棦涓嶄慨澶� bug 涔熶笉娣诲姞鍔熻兘鐨勪唬鐮佸彉鏇�'],
+ ['perf', '鍙彁楂樻�ц兘鐨勪唬鐮佹洿鏀�'],
+ ['optimize', '浼樺寲浠g爜璐ㄩ噺鐨勪唬鐮佹洿鏀�'],
+ ['test', '娣诲姞缂哄け鐨勬祴璇曟垨鏇存鐜版湁娴嬭瘯'],
+ ['build', '褰卞搷鏋勫缓绯荤粺鎴栧閮ㄤ緷璧栭」鐨勬洿鏀�'],
+ ['ci', '瀵� CI 閰嶇疆鏂囦欢鍜岃剼鏈殑鏇存敼'],
+ ['chore', '娌℃湁淇敼src鎴栨祴璇曟枃浠剁殑鍏朵粬鍙樻洿'],
+ ['revert', '杩樺師鍏堝墠鐨勬彁浜�']
+ ] as [string, string][],
+ gitCommitScopes: [
+ ['projects', '椤圭洰'],
+ ['packages', '鍖�'],
+ ['components', '缁勪欢'],
+ ['hooks', '閽╁瓙鍑芥暟'],
+ ['utils', '宸ュ叿鍑芥暟'],
+ ['types', 'TS绫诲瀷澹版槑'],
+ ['styles', '浠g爜椋庢牸'],
+ ['deps', '椤圭洰渚濊禆'],
+ ['release', '鍙戝竷椤圭洰鏂扮増鏈�'],
+ ['other', '鍏朵粬鐨勫彉鏇�']
+ ] as [string, string][],
+ gitCommitVerify: `${bgRed(' 閿欒 ')} ${red('git 鎻愪氦淇℃伅蹇呴』绗﹀悎 Conventional Commits 鏍囧噯!')}\n\n${green(
+ '鎺ㄨ崘浣跨敤鍛戒护 `pnpm commit` 鐢熸垚绗﹀悎 Conventional Commits 鏍囧噯鐨勬彁浜や俊鎭�俓n鑾峰彇鏈夊叧 Conventional Commits 鐨勬洿澶氫俊鎭紝璇疯闂閾炬帴: https://conventionalcommits.org'
+ )}`
+ },
+ 'en-us': {
+ gitCommitMessages: {
+ types: 'Please select a type',
+ scopes: 'Please select a scope',
+ description: `Please enter a description (add prefix ${yellow('!')} to indicate breaking change)`
+ },
+ gitCommitTypes: [
+ ['feat', 'A new feature'],
+ ['feat-wip', 'Features in development, such as partial code for a certain feature'],
+ ['fix', 'A bug fix'],
+ ['docs', 'Documentation only changes'],
+ ['typo', 'Code or document corrections, such as spelling errors'],
+ ['style', 'Changes that do not affect the meaning of the code'],
+ ['refactor', 'A code change that neither fixes a bug nor adds a feature'],
+ ['perf', 'A code change that improves performance'],
+ ['optimize', 'A code change that optimizes code quality'],
+ ['test', 'Adding missing tests or correcting existing tests'],
+ ['build', 'Changes that affect the build system or external dependencies'],
+ ['ci', 'Changes to our CI configuration files and scripts'],
+ ['chore', "Other changes that don't modify src or test files"],
+ ['revert', 'Reverts a previous commit']
+ ] as [string, string][],
+ gitCommitScopes: [
+ ['projects', 'project'],
+ ['packages', 'packages'],
+ ['components', 'components'],
+ ['hooks', 'hook functions'],
+ ['utils', 'utils functions'],
+ ['types', 'TS declaration'],
+ ['styles', 'style'],
+ ['deps', 'project dependencies'],
+ ['release', 'release project'],
+ ['other', 'other changes']
+ ] as [string, string][],
+ gitCommitVerify: `${bgRed(' ERROR ')} ${red('git commit message must match the Conventional Commits standard!')}\n\n${green(
+ 'Recommended to use the command `pnpm commit` to generate Conventional Commits compliant commit information.\nGet more info about Conventional Commits, follow this link: https://conventionalcommits.org'
+ )}`
+ }
+} satisfies Record<Lang, Record<string, unknown>>;
diff --git a/ruoyi-plus-soybean/packages/scripts/src/shared/index.ts b/ruoyi-plus-soybean/packages/scripts/src/shared/index.ts
new file mode 100755
index 0000000..9b1763f
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/src/shared/index.ts
@@ -0,0 +1,7 @@
+import type { Options } from 'execa';
+
+export async function execCommand(cmd: string, args: string[], options?: Options) {
+ const { execa } = await import('execa');
+ const res = await execa(cmd, args, options);
+ return (res?.stdout as string)?.trim() || '';
+}
diff --git a/ruoyi-plus-soybean/packages/scripts/src/types/index.ts b/ruoyi-plus-soybean/packages/scripts/src/types/index.ts
new file mode 100755
index 0000000..32bc091
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/src/types/index.ts
@@ -0,0 +1,31 @@
+import type { ChangelogOption } from '@soybeanjs/changelog';
+
+export interface CliOption {
+ /** The project root directory */
+ cwd: string;
+ /**
+ * Cleanup dirs
+ *
+ * Glob pattern syntax {@link https://github.com/isaacs/minimatch}
+ *
+ * @default
+ * ```json
+ * ["** /dist", "** /pnpm-lock.yaml", "** /node_modules", "!node_modules/**"]
+ * ```
+ */
+ cleanupDirs: string[];
+ /**
+ * Npm-check-updates command args
+ *
+ * @default ['--deep', '-u']
+ */
+ ncuCommandArgs: string[];
+ /**
+ * Options of generate changelog
+ *
+ * @link https://github.com/soybeanjs/changelog
+ */
+ changelogOptions: Partial<ChangelogOption>;
+ /** The ignore pattern list of git commit verify */
+ gitCommitVerifyIgnores: RegExp[];
+}
diff --git a/ruoyi-plus-soybean/packages/scripts/tsconfig.json b/ruoyi-plus-soybean/packages/scripts/tsconfig.json
new file mode 100755
index 0000000..67ab8a4
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/scripts/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "jsx": "preserve",
+ "lib": ["DOM", "ESNext"],
+ "baseUrl": ".",
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "types": ["node"],
+ "strict": true,
+ "strictNullChecks": true,
+ "noUnusedLocals": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*", "typings/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/ruoyi-plus-soybean/packages/uno-preset/package.json b/ruoyi-plus-soybean/packages/uno-preset/package.json
new file mode 100755
index 0000000..5412cb2
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/uno-preset/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@sa/uno-preset",
+ "version": "2.0.2",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "typesVersions": {
+ "*": {
+ "*": ["./src/*"]
+ }
+ }
+}
diff --git a/ruoyi-plus-soybean/packages/uno-preset/src/index.ts b/ruoyi-plus-soybean/packages/uno-preset/src/index.ts
new file mode 100755
index 0000000..6a1ae42
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/uno-preset/src/index.ts
@@ -0,0 +1,55 @@
+// @unocss-include
+
+import type { Preset } from '@unocss/core';
+import type { Theme } from '@unocss/preset-uno';
+
+export function presetSoybeanAdmin(): Preset<Theme> {
+ const preset: Preset<Theme> = {
+ name: 'preset-soybean-admin',
+ shortcuts: [
+ {
+ 'flex-center': 'flex justify-center items-center',
+ 'flex-x-center': 'flex justify-center',
+ 'flex-y-center': 'flex items-center',
+ 'flex-col': 'flex flex-col',
+ 'flex-col-center': 'flex-center flex-col',
+ 'flex-col-stretch': 'flex-col items-stretch',
+ 'i-flex-center': 'inline-flex justify-center items-center',
+ 'i-flex-x-center': 'inline-flex justify-center',
+ 'i-flex-y-center': 'inline-flex items-center',
+ 'i-flex-col': 'flex-col inline-flex',
+ 'i-flex-col-center': 'flex-col i-flex-center',
+ 'i-flex-col-stretch': 'i-flex-col items-stretch',
+ 'flex-1-hidden': 'flex-1 overflow-hidden'
+ },
+ {
+ 'absolute-lt': 'absolute left-0 top-0',
+ 'absolute-lb': 'absolute left-0 bottom-0',
+ 'absolute-rt': 'absolute right-0 top-0',
+ 'absolute-rb': 'absolute right-0 bottom-0',
+ 'absolute-tl': 'absolute-lt',
+ 'absolute-tr': 'absolute-rt',
+ 'absolute-bl': 'absolute-lb',
+ 'absolute-br': 'absolute-rb',
+ 'absolute-center': 'absolute-lt flex-center size-full',
+ 'fixed-lt': 'fixed left-0 top-0',
+ 'fixed-lb': 'fixed left-0 bottom-0',
+ 'fixed-rt': 'fixed right-0 top-0',
+ 'fixed-rb': 'fixed right-0 bottom-0',
+ 'fixed-tl': 'fixed-lt',
+ 'fixed-tr': 'fixed-rt',
+ 'fixed-bl': 'fixed-lb',
+ 'fixed-br': 'fixed-rb',
+ 'fixed-center': 'fixed-lt flex-center size-full'
+ },
+ {
+ 'nowrap-hidden': 'overflow-hidden whitespace-nowrap',
+ 'ellipsis-text': 'nowrap-hidden text-ellipsis'
+ }
+ ]
+ };
+
+ return preset;
+}
+
+export default presetSoybeanAdmin;
diff --git a/ruoyi-plus-soybean/packages/uno-preset/tsconfig.json b/ruoyi-plus-soybean/packages/uno-preset/tsconfig.json
new file mode 100755
index 0000000..5823ed5
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/uno-preset/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "jsx": "preserve",
+ "lib": ["DOM", "ESNext"],
+ "baseUrl": ".",
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "types": ["node"],
+ "strict": true,
+ "strictNullChecks": true,
+ "noUnusedLocals": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/ruoyi-plus-soybean/packages/utils/package.json b/ruoyi-plus-soybean/packages/utils/package.json
new file mode 100755
index 0000000..7074bca
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/utils/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "@sa/utils",
+ "version": "2.0.2",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "typesVersions": {
+ "*": {
+ "*": ["./src/*"]
+ }
+ },
+ "dependencies": {
+ "colord": "2.9.3",
+ "crypto-js": "4.2.0",
+ "klona": "2.0.6",
+ "localforage": "1.10.0",
+ "nanoid": "5.1.6"
+ },
+ "devDependencies": {
+ "@types/crypto-js": "4.2.2"
+ }
+}
diff --git a/ruoyi-plus-soybean/packages/utils/src/crypto.ts b/ruoyi-plus-soybean/packages/utils/src/crypto.ts
new file mode 100755
index 0000000..dc173c8
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/utils/src/crypto.ts
@@ -0,0 +1,27 @@
+import CryptoJS from 'crypto-js';
+
+export class Crypto<T extends object> {
+ /** Secret */
+ secret: string;
+
+ constructor(secret: string) {
+ this.secret = secret;
+ }
+
+ encrypt(data: T): string {
+ const dataString = JSON.stringify(data);
+ const encrypted = CryptoJS.AES.encrypt(dataString, this.secret);
+ return encrypted.toString();
+ }
+
+ decrypt(encrypted: string) {
+ const decrypted = CryptoJS.AES.decrypt(encrypted, this.secret);
+ const dataString = decrypted.toString(CryptoJS.enc.Utf8);
+ try {
+ return JSON.parse(dataString) as T;
+ } catch {
+ // avoid parse error
+ return null;
+ }
+ }
+}
diff --git a/ruoyi-plus-soybean/packages/utils/src/index.ts b/ruoyi-plus-soybean/packages/utils/src/index.ts
new file mode 100755
index 0000000..079fba6
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/utils/src/index.ts
@@ -0,0 +1,4 @@
+export * from './crypto';
+export * from './storage';
+export * from './nanoid';
+export * from './klona';
diff --git a/ruoyi-plus-soybean/packages/utils/src/klona.ts b/ruoyi-plus-soybean/packages/utils/src/klona.ts
new file mode 100755
index 0000000..2fe5927
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/utils/src/klona.ts
@@ -0,0 +1,3 @@
+import { klona as jsonClone } from 'klona/json';
+
+export { jsonClone };
diff --git a/ruoyi-plus-soybean/packages/utils/src/nanoid.ts b/ruoyi-plus-soybean/packages/utils/src/nanoid.ts
new file mode 100755
index 0000000..5cc1d2e
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/utils/src/nanoid.ts
@@ -0,0 +1,3 @@
+import { nanoid } from 'nanoid';
+
+export { nanoid };
diff --git a/ruoyi-plus-soybean/packages/utils/src/storage.ts b/ruoyi-plus-soybean/packages/utils/src/storage.ts
new file mode 100755
index 0000000..e4aad14
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/utils/src/storage.ts
@@ -0,0 +1,77 @@
+import localforage from 'localforage';
+
+/** The storage type */
+export type StorageType = 'local' | 'session';
+
+export function createStorage<T extends object>(type: StorageType, storagePrefix: string) {
+ const stg = type === 'session' ? window.sessionStorage : window.localStorage;
+
+ const storage = {
+ /**
+ * Set session
+ *
+ * @param key Session key
+ * @param value Session value
+ */
+ set<K extends keyof T>(key: K, value: T[K]) {
+ const json = JSON.stringify(value);
+
+ stg.setItem(`${storagePrefix}${key as string}`, json);
+ },
+ /**
+ * Get session
+ *
+ * @param key Session key
+ */
+ get<K extends keyof T>(key: K): T[K] | null {
+ const json = stg.getItem(`${storagePrefix}${key as string}`);
+ if (json) {
+ let storageData: T[K] | null = null;
+
+ try {
+ storageData = JSON.parse(json);
+ } catch {}
+
+ // storageData may be `false` if it is boolean type
+ if (storageData !== null) {
+ return storageData as T[K];
+ }
+ }
+
+ stg.removeItem(`${storagePrefix}${key as string}`);
+
+ return null;
+ },
+ remove(key: keyof T) {
+ stg.removeItem(`${storagePrefix}${key as string}`);
+ },
+ clear() {
+ stg.clear();
+ }
+ };
+ return storage;
+}
+
+type LocalForage<T extends object> = Omit<typeof localforage, 'getItem' | 'setItem' | 'removeItem'> & {
+ getItem<K extends keyof T>(key: K, callback?: (err: any, value: T[K] | null) => void): Promise<T[K] | null>;
+
+ setItem<K extends keyof T>(key: K, value: T[K], callback?: (err: any, value: T[K]) => void): Promise<T[K]>;
+
+ removeItem(key: keyof T, callback?: (err: any) => void): Promise<void>;
+};
+
+type LocalforageDriver = 'local' | 'indexedDB' | 'webSQL';
+
+export function createLocalforage<T extends object>(driver: LocalforageDriver) {
+ const driverMap: Record<LocalforageDriver, string> = {
+ local: localforage.LOCALSTORAGE,
+ indexedDB: localforage.INDEXEDDB,
+ webSQL: localforage.WEBSQL
+ };
+
+ localforage.config({
+ driver: driverMap[driver]
+ });
+
+ return localforage as LocalForage<T>;
+}
diff --git a/ruoyi-plus-soybean/packages/utils/tsconfig.json b/ruoyi-plus-soybean/packages/utils/tsconfig.json
new file mode 100755
index 0000000..5823ed5
--- /dev/null
+++ b/ruoyi-plus-soybean/packages/utils/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "jsx": "preserve",
+ "lib": ["DOM", "ESNext"],
+ "baseUrl": ".",
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "types": ["node"],
+ "strict": true,
+ "strictNullChecks": true,
+ "noUnusedLocals": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/ruoyi-plus-soybean/pnpm-lock.yaml b/ruoyi-plus-soybean/pnpm-lock.yaml
new file mode 100755
index 0000000..185bc20
--- /dev/null
+++ b/ruoyi-plus-soybean/pnpm-lock.yaml
@@ -0,0 +1,10758 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@better-scroll/core':
+ specifier: 2.5.1
+ version: 2.5.1
+ '@iconify/vue':
+ specifier: 5.0.0
+ version: 5.0.0(vue@3.5.26(typescript@5.9.3))
+ '@sa/axios':
+ specifier: workspace:*
+ version: link:packages/axios
+ '@sa/color':
+ specifier: workspace:*
+ version: link:packages/color
+ '@sa/hooks':
+ specifier: workspace:*
+ version: link:packages/hooks
+ '@sa/materials':
+ specifier: workspace:*
+ version: link:packages/materials
+ '@sa/utils':
+ specifier: workspace:*
+ version: link:packages/utils
+ '@types/streamsaver':
+ specifier: ^2.0.5
+ version: 2.0.5
+ '@umoteam/editor':
+ specifier: ^9.0.1
+ version: 9.0.1(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
+ '@vueuse/core':
+ specifier: 14.1.0
+ version: 14.1.0(vue@3.5.26(typescript@5.9.3))
+ clipboard:
+ specifier: 2.0.11
+ version: 2.0.11
+ dayjs:
+ specifier: 1.11.19
+ version: 1.11.19
+ defu:
+ specifier: 6.1.4
+ version: 6.1.4
+ echarts:
+ specifier: 6.0.0
+ version: 6.0.0
+ highlight.js:
+ specifier: ^11.11.1
+ version: 11.11.1
+ jsencrypt:
+ specifier: ^3.5.4
+ version: 3.5.4
+ json5:
+ specifier: 2.2.3
+ version: 2.2.3
+ naive-ui:
+ specifier: 2.43.2
+ version: 2.43.2(vue@3.5.26(typescript@5.9.3))
+ nprogress:
+ specifier: 0.2.0
+ version: 0.2.0
+ pinia:
+ specifier: 3.0.4
+ version: 3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
+ streamsaver:
+ specifier: ^2.0.6
+ version: 2.0.6
+ tailwind-merge:
+ specifier: 3.4.0
+ version: 3.4.0
+ vue:
+ specifier: 3.5.26
+ version: 3.5.26(typescript@5.9.3)
+ vue-advanced-cropper:
+ specifier: ^2.8.9
+ version: 2.8.9(vue@3.5.26(typescript@5.9.3))
+ vue-draggable-plus:
+ specifier: 0.6.0
+ version: 0.6.0(@types/sortablejs@1.15.9)
+ vue-i18n:
+ specifier: 11.2.7
+ version: 11.2.7(vue@3.5.26(typescript@5.9.3))
+ vue-router:
+ specifier: 4.6.4
+ version: 4.6.4(vue@3.5.26(typescript@5.9.3))
+ devDependencies:
+ '@elegant-router/vue':
+ specifier: 0.3.8
+ version: 0.3.8
+ '@iconify/json':
+ specifier: 2.2.417
+ version: 2.2.417
+ '@sa/scripts':
+ specifier: workspace:*
+ version: link:packages/scripts
+ '@sa/uno-preset':
+ specifier: workspace:*
+ version: link:packages/uno-preset
+ '@soybeanjs/eslint-config':
+ specifier: 1.7.4
+ version: 1.7.4(@typescript-eslint/utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@unocss/eslint-config@66.5.10(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-plugin-vue@10.6.2(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))))(eslint@9.39.2(jiti@2.6.1))(svelte-eslint-parser@1.4.1)(typescript@5.9.3)(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1)))
+ '@types/node':
+ specifier: 25.0.3
+ version: 25.0.3
+ '@types/nprogress':
+ specifier: 0.2.3
+ version: 0.2.3
+ '@unocss/eslint-config':
+ specifier: 66.5.10
+ version: 66.5.10(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ '@unocss/preset-icons':
+ specifier: 66.5.10
+ version: 66.5.10
+ '@unocss/preset-uno':
+ specifier: 66.5.10
+ version: 66.5.10
+ '@unocss/transformer-directives':
+ specifier: 66.5.10
+ version: 66.5.10
+ '@unocss/transformer-variant-group':
+ specifier: 66.5.10
+ version: 66.5.10
+ '@unocss/vite':
+ specifier: 66.5.10
+ version: 66.5.10(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))
+ '@vitejs/plugin-vue':
+ specifier: 6.0.3
+ version: 6.0.3(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))
+ '@vitejs/plugin-vue-jsx':
+ specifier: 5.1.2
+ version: 5.1.2(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))
+ consola:
+ specifier: 3.4.2
+ version: 3.4.2
+ eslint:
+ specifier: 9.39.2
+ version: 9.39.2(jiti@2.6.1)
+ eslint-plugin-vue:
+ specifier: 10.6.2
+ version: 10.6.2(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1)))
+ kolorist:
+ specifier: 1.8.0
+ version: 1.8.0
+ sass:
+ specifier: 1.97.1
+ version: 1.97.1
+ simple-git-hooks:
+ specifier: 2.13.1
+ version: 2.13.1
+ tsx:
+ specifier: 4.21.0
+ version: 4.21.0
+ typescript:
+ specifier: 5.9.3
+ version: 5.9.3
+ unplugin-icons:
+ specifier: 22.5.0
+ version: 22.5.0(@vue/compiler-sfc@3.5.26)
+ unplugin-vue-components:
+ specifier: 30.0.0
+ version: 30.0.0(@babel/parser@7.28.5)(vue@3.5.26(typescript@5.9.3))
+ vite:
+ specifier: 7.3.0
+ version: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite-plugin-progress:
+ specifier: 0.0.7
+ version: 0.0.7(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))
+ vite-plugin-svg-icons:
+ specifier: 2.0.1
+ version: 2.0.1(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))
+ vite-plugin-vue-devtools:
+ specifier: 8.0.5
+ version: 8.0.5(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))
+ vue-eslint-parser:
+ specifier: 10.2.0
+ version: 10.2.0(eslint@9.39.2(jiti@2.6.1))
+ vue-tsc:
+ specifier: 3.2.1
+ version: 3.2.1(typescript@5.9.3)
+
+ packages/alova:
+ dependencies:
+ '@alova/mock':
+ specifier: 2.0.18
+ version: 2.0.18(alova@3.4.1)
+ '@sa/utils':
+ specifier: workspace:*
+ version: link:../utils
+ alova:
+ specifier: 3.4.1
+ version: 3.4.1
+
+ packages/axios:
+ dependencies:
+ '@sa/utils':
+ specifier: workspace:*
+ version: link:../utils
+ axios:
+ specifier: 1.13.2
+ version: 1.13.2
+ axios-retry:
+ specifier: 4.5.0
+ version: 4.5.0(axios@1.13.2)
+ qs:
+ specifier: 6.14.0
+ version: 6.14.0
+ devDependencies:
+ '@types/qs':
+ specifier: 6.14.0
+ version: 6.14.0
+
+ packages/color:
+ dependencies:
+ '@sa/utils':
+ specifier: workspace:*
+ version: link:../utils
+ colord:
+ specifier: 2.9.3
+ version: 2.9.3
+
+ packages/hooks:
+ dependencies:
+ '@sa/axios':
+ specifier: workspace:*
+ version: link:../axios
+ '@sa/utils':
+ specifier: workspace:*
+ version: link:../utils
+
+ packages/materials:
+ dependencies:
+ '@sa/utils':
+ specifier: workspace:*
+ version: link:../utils
+ simplebar-vue:
+ specifier: 2.4.2
+ version: 2.4.2(vue@3.5.26(typescript@5.9.3))
+ devDependencies:
+ typed-css-modules:
+ specifier: 0.9.1
+ version: 0.9.1
+
+ packages/scripts:
+ devDependencies:
+ '@soybeanjs/changelog':
+ specifier: 0.3.25
+ version: 0.3.25(@typescript-eslint/utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@unocss/eslint-config@66.5.10(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-plugin-vue@10.6.2(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))))(eslint@9.39.2(jiti@2.6.1))(svelte-eslint-parser@1.4.1)(typescript@5.9.3)(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1)))
+ bumpp:
+ specifier: 10.3.2
+ version: 10.3.2(magicast@0.3.4)
+ c12:
+ specifier: 3.3.3
+ version: 3.3.3(magicast@0.3.4)
+ cac:
+ specifier: 6.7.14
+ version: 6.7.14
+ consola:
+ specifier: 3.4.2
+ version: 3.4.2
+ enquirer:
+ specifier: 2.4.1
+ version: 2.4.1
+ execa:
+ specifier: 9.6.1
+ version: 9.6.1
+ kolorist:
+ specifier: 1.8.0
+ version: 1.8.0
+ npm-check-updates:
+ specifier: 19.2.0
+ version: 19.2.0
+ picomatch:
+ specifier: 4.0.3
+ version: 4.0.3
+ rimraf:
+ specifier: 6.1.2
+ version: 6.1.2
+
+ packages/uno-preset: {}
+
+ packages/utils:
+ dependencies:
+ colord:
+ specifier: 2.9.3
+ version: 2.9.3
+ crypto-js:
+ specifier: 4.2.0
+ version: 4.2.0
+ klona:
+ specifier: 2.0.6
+ version: 2.0.6
+ localforage:
+ specifier: 1.10.0
+ version: 1.10.0
+ nanoid:
+ specifier: 5.1.6
+ version: 5.1.6
+ devDependencies:
+ '@types/crypto-js':
+ specifier: 4.2.2
+ version: 4.2.2
+
+packages:
+
+ '@alova/mock@2.0.18':
+ resolution: {integrity: sha512-XMwn0Nsmd5pNz+/+iHYy5UQCkb3PobbsMj5of1wlRqRymxyPBc00NImExchkE1wJkTt0sT3BDHnv1jDP9SJE6w==}
+ peerDependencies:
+ alova: ^3.0.20
+
+ '@alova/shared@1.3.2':
+ resolution: {integrity: sha512-1XvDLWgYpVZ99MmLl1f3Fw4T6S6pPYk5afz5cwRVjuq8JXEGsDn9IygDKfvRyWqkqCBx7Jif07LIct1O+MVEow==}
+
+ '@antfu/eslint-define-config@1.23.0-2':
+ resolution: {integrity: sha512-LvxY21+ZhpuBf/aHeBUtGQhSEfad4PkNKXKvDOSvukaM3XVTfBhwmHX2EKwAsdq5DlfjbT3qqYyMiueBIO5iDQ==}
+ engines: {node: '>=18.0.0', npm: '>=9.0.0', pnpm: '>= 8.6.0'}
+
+ '@antfu/install-pkg@1.1.0':
+ resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
+
+ '@babel/code-frame@7.27.1':
+ resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.28.5':
+ resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.28.5':
+ resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.28.5':
+ resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-annotate-as-pure@7.27.3':
+ resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.27.2':
+ resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-create-class-features-plugin@7.28.5':
+ resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-member-expression-to-functions@7.28.5':
+ resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.27.1':
+ resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.28.3':
+ resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-optimise-call-expression@7.27.1':
+ resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-plugin-utils@7.27.1':
+ resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-replace-supers@7.27.1':
+ resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.28.4':
+ resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.28.5':
+ resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-proposal-decorators@7.28.0':
+ resolution: {integrity: sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-decorators@7.27.1':
+ resolution: {integrity: sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-import-attributes@7.27.1':
+ resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-import-meta@7.10.4':
+ resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-jsx@7.27.1':
+ resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-typescript@7.27.1':
+ resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-typescript@7.28.5':
+ resolution: {integrity: sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/template@7.27.2':
+ resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.28.5':
+ resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.28.5':
+ resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
+ engines: {node: '>=6.9.0'}
+
+ '@better-scroll/core@2.5.1':
+ resolution: {integrity: sha512-koKOuYA55dQ04FJRIVUpMGDr1hbCfWmfX0MGp1hKagkQSWSRpwblqACiwtggVauoj9aaJRJZ9hDsTM4weaavlg==}
+
+ '@better-scroll/shared-utils@2.5.1':
+ resolution: {integrity: sha512-AplkfSjXVYP9LZiD6JsKgmgQJ/mG4uuLmBuwLz8W5OsYc7AYTfN8kw6GqZ5OwCGoXkVhBGyd8NeC4xwYItp0aw==}
+
+ '@braintree/sanitize-url@7.1.1':
+ resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==}
+
+ '@chevrotain/cst-dts-gen@11.0.3':
+ resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==}
+
+ '@chevrotain/gast@11.0.3':
+ resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==}
+
+ '@chevrotain/regexp-to-ast@11.0.3':
+ resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==}
+
+ '@chevrotain/types@11.0.3':
+ resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==}
+
+ '@chevrotain/utils@11.0.3':
+ resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==}
+
+ '@css-render/plugin-bem@0.15.14':
+ resolution: {integrity: sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg==}
+ peerDependencies:
+ css-render: ~0.15.14
+
+ '@css-render/vue3-ssr@0.15.14':
+ resolution: {integrity: sha512-//8027GSbxE9n3QlD73xFY6z4ZbHbvrOVB7AO6hsmrEzGbg+h2A09HboUyDgu+xsmj7JnvJD39Irt+2D0+iV8g==}
+ peerDependencies:
+ vue: ^3.0.11
+
+ '@elegant-router/core@0.3.8':
+ resolution: {integrity: sha512-q8CihD9la9V2H+/OYIzMLftXSBkbT234UMwhMxDL1Gq7BGKU3kEIEJvifRM7htbiRD77bkQhwMGBY3WZacqw8A==}
+
+ '@elegant-router/vue@0.3.8':
+ resolution: {integrity: sha512-K9x2275vw9kQB25WnZ7ROTLsT3o8bxu8acvwF09Do8hexIKG2i6elV0+pWxaufNZ4XCuBxT+lKHfHeyBbRhtYQ==}
+
+ '@emnapi/core@1.7.1':
+ resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==}
+
+ '@emnapi/runtime@1.7.1':
+ resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==}
+
+ '@emnapi/wasi-threads@1.1.0':
+ resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
+
+ '@emotion/hash@0.8.0':
+ resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==}
+
+ '@esbuild/aix-ppc64@0.27.2':
+ resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.27.2':
+ resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.27.2':
+ resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.27.2':
+ resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.27.2':
+ resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.27.2':
+ resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.27.2':
+ resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.27.2':
+ resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.27.2':
+ resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.27.2':
+ resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.27.2':
+ resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.27.2':
+ resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.27.2':
+ resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.27.2':
+ resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.27.2':
+ resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.27.2':
+ resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.27.2':
+ resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.27.2':
+ resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.27.2':
+ resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.27.2':
+ resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.27.2':
+ resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.27.2':
+ resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.27.2':
+ resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.27.2':
+ resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.27.2':
+ resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.27.2':
+ resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@eslint-community/eslint-utils@4.9.0':
+ resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.2':
+ resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/compat@1.4.1':
+ resolution: {integrity: sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.40 || 9
+ peerDependenciesMeta:
+ eslint:
+ optional: true
+
+ '@eslint/config-array@0.21.1':
+ resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/config-helpers@0.4.2':
+ resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@0.17.0':
+ resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/eslintrc@3.3.3':
+ resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/js@9.39.2':
+ resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/object-schema@2.1.7':
+ resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/plugin-kit@0.4.1':
+ resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@humanfs/core@0.19.1':
+ resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.7':
+ resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/retry@0.4.3':
+ resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
+ engines: {node: '>=18.18'}
+
+ '@iconify/json@2.2.417':
+ resolution: {integrity: sha512-/MzthgckJ4vEwdHmAbAn6Bph5WnR4tzVcHMs/nZl3v5hOVRw80SK28UPnG7jjsCB41WWjWPnWdMEdOZfUMZS5w==}
+
+ '@iconify/types@2.0.0':
+ resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
+
+ '@iconify/utils@3.1.0':
+ resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==}
+
+ '@iconify/vue@5.0.0':
+ resolution: {integrity: sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg==}
+ peerDependencies:
+ vue: '>=3'
+
+ '@imgly/background-removal@1.5.5':
+ resolution: {integrity: sha512-tULjwGmuPTUCWVQsP2KpSOEv7/mNGQhULM3WEe+eap1nmobGkQp3Gwj3gmVK7mw/b9FSzoM5nD4pJdvffYmr5A==}
+ bundledDependencies: []
+
+ '@intlify/core-base@10.0.8':
+ resolution: {integrity: sha512-FoHslNWSoHjdUBLy35bpm9PV/0LVI/DSv9L6Km6J2ad8r/mm0VaGg06C40FqlE8u2ADcGUM60lyoU7Myo4WNZQ==}
+ engines: {node: '>= 16'}
+
+ '@intlify/core-base@11.2.7':
+ resolution: {integrity: sha512-+Ra9I/LAzXDnmv/IrTO03WMCiLya7pHRmGJvNl9fKwx/W4REJ0xaMk2PxCRqnxcBsX443amEMdebQ3R1geiuIw==}
+ engines: {node: '>= 16'}
+
+ '@intlify/message-compiler@10.0.8':
+ resolution: {integrity: sha512-DV+sYXIkHVd5yVb2mL7br/NEUwzUoLBsMkV3H0InefWgmYa34NLZUvMCGi5oWX+Hqr2Y2qUxnVrnOWF4aBlgWg==}
+ engines: {node: '>= 16'}
+
+ '@intlify/message-compiler@11.2.7':
+ resolution: {integrity: sha512-TFamC+GzJAotAFwUNvbtRVBgvuSn2nCwKNresmPUHv3IIVMmXJt7QQJj/DORI1h8hs46ZF6L0Fs2xBohSOE4iQ==}
+ engines: {node: '>= 16'}
+
+ '@intlify/shared@10.0.8':
+ resolution: {integrity: sha512-BcmHpb5bQyeVNrptC3UhzpBZB/YHHDoEREOUERrmF2BRxsyOEuRrq+Z96C/D4+2KJb8kuHiouzAei7BXlG0YYw==}
+ engines: {node: '>= 16'}
+
+ '@intlify/shared@11.2.7':
+ resolution: {integrity: sha512-uvlkvc/0uQ4FDlHQZccpUnmcOwNcaI3i+69ck2YJ+GqM35AoVbuS63b+YfirV4G0SZh64Ij2UMcFRMmB4nr95w==}
+ engines: {node: '>= 16'}
+
+ '@isaacs/balanced-match@4.0.1':
+ resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
+ engines: {node: 20 || >=22}
+
+ '@isaacs/brace-expansion@5.0.0':
+ resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==}
+ engines: {node: 20 || >=22}
+
+ '@isaacs/cliui@8.0.2':
+ resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+ engines: {node: '>=12'}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@juggle/resize-observer@3.4.0':
+ resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
+
+ '@mermaid-js/parser@0.6.3':
+ resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==}
+
+ '@napi-rs/canvas-android-arm64@0.1.86':
+ resolution: {integrity: sha512-IjkZFKUr6GzMzzrawJaN3v+yY3Fvpa71e0DcbePfxWelFKnESIir+XUcdAbim29JOd0JE0/hQJdfUCb5t/Fjrw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@napi-rs/canvas-darwin-arm64@0.1.86':
+ resolution: {integrity: sha512-PUCxDq0wSSJbtaOqoKj3+t5tyDbtxWumziOTykdn3T839hu6koMaBFpGk9lXpsGaPNgyFpPqjxhtsPljBGnDHg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@napi-rs/canvas-darwin-x64@0.1.86':
+ resolution: {integrity: sha512-rlCFLv4Rrg45qFZq7mysrKnsUbMhwdNg3YPuVfo9u4RkOqm7ooAJvdyDFxiqfSsJJTqupYqa9VQCUt8WKxKhNQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@napi-rs/canvas-linux-arm-gnueabihf@0.1.86':
+ resolution: {integrity: sha512-6xWwyMc9BlDBt+9XHN/GzUo3MozHta/2fxQHMb80x0K2zpZuAdDKUYHmYzx9dFWDY3SbPYnx6iRlQl6wxnwS1w==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@napi-rs/canvas-linux-arm64-gnu@0.1.86':
+ resolution: {integrity: sha512-r2OX3w50xHxrToTovOSQWwkVfSq752CUzH9dzlVXyr8UDKFV8dMjfa9hePXvAJhN3NBp4TkHcGx15QCdaCIwnA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@napi-rs/canvas-linux-arm64-musl@0.1.86':
+ resolution: {integrity: sha512-jbXuh8zVFUPw6a9SGpgc6EC+fRbGGyP1NFfeQiVqGLs6bN93ROtPLPL6MH9Bp6yt0CXUFallk2vgKdWDbmW+bw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@napi-rs/canvas-linux-riscv64-gnu@0.1.86':
+ resolution: {integrity: sha512-9IwHR2qbq2HceM9fgwyL7x37Jy3ptt1uxvikQEuWR0FisIx9QEdt7F3huljCky76aoouF2vSd0R2fHo3ESRoPw==}
+ engines: {node: '>= 10'}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@napi-rs/canvas-linux-x64-gnu@0.1.86':
+ resolution: {integrity: sha512-Jor+rhRN6ubix+D2QkNn9XlPPVAYl+2qFrkZ4oZN9UgtqIUZ+n+HljxhlkkDFRaX1mlxXOXPQjxaZg17zDSFcQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@napi-rs/canvas-linux-x64-musl@0.1.86':
+ resolution: {integrity: sha512-A28VTy91DbclopSGZ2tIon3p8hcVI1JhnNpDpJ5N9rYlUnVz1WQo4waEMh+FICTZF07O3coxBNZc4Vu4doFw7A==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@napi-rs/canvas-win32-arm64-msvc@0.1.86':
+ resolution: {integrity: sha512-q6G1YXUt3gBCAS2bcDMCaBL4y20di8eVVBi1XhjUqZSVyZZxxwIuRQHy31NlPJUCMiyNiMuc6zeI0uqgkWwAmA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@napi-rs/canvas-win32-x64-msvc@0.1.86':
+ resolution: {integrity: sha512-X0g46uRVgnvCM1cOjRXAOSFSG63ktUFIf/TIfbKCUc7QpmYUcHmSP9iR6DGOYfk+SggLsXoJCIhPTotYeZEAmg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@napi-rs/canvas@0.1.86':
+ resolution: {integrity: sha512-hOkywnrkdFdVpsuaNsZWfEY7kc96eROV2DuMTTvGF15AZfwobzdG2w0eDlU5UBx3Lg/XlWUnqVT5zLUWyo5h6A==}
+ engines: {node: '>= 10'}
+
+ '@napi-rs/wasm-runtime@0.2.12':
+ resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@parcel/watcher-android-arm64@2.5.1':
+ resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ '@parcel/watcher-darwin-arm64@2.5.1':
+ resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@parcel/watcher-darwin-x64@2.5.1':
+ resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@parcel/watcher-freebsd-x64@2.5.1':
+ resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@parcel/watcher-linux-arm-glibc@2.5.1':
+ resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@parcel/watcher-linux-arm-musl@2.5.1':
+ resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+ libc: [musl]
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.1':
+ resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@parcel/watcher-linux-arm64-musl@2.5.1':
+ resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@parcel/watcher-linux-x64-glibc@2.5.1':
+ resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@parcel/watcher-linux-x64-musl@2.5.1':
+ resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@parcel/watcher-win32-arm64@2.5.1':
+ resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@parcel/watcher-win32-ia32@2.5.1':
+ resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@parcel/watcher-win32-x64@2.5.1':
+ resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@parcel/watcher@2.5.1':
+ resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
+ engines: {node: '>= 10.0.0'}
+
+ '@pkgjs/parseargs@0.11.0':
+ resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
+ engines: {node: '>=14'}
+
+ '@pkgr/core@0.2.9':
+ resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+
+ '@polka/url@1.0.0-next.29':
+ resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
+
+ '@popperjs/core@2.11.8':
+ resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
+
+ '@protobufjs/aspromise@1.1.2':
+ resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
+
+ '@protobufjs/base64@1.1.2':
+ resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
+
+ '@protobufjs/codegen@2.0.4':
+ resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==}
+
+ '@protobufjs/eventemitter@1.1.0':
+ resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
+
+ '@protobufjs/fetch@1.1.0':
+ resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
+
+ '@protobufjs/float@1.0.2':
+ resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
+
+ '@protobufjs/inquire@1.1.0':
+ resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==}
+
+ '@protobufjs/path@1.1.2':
+ resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
+
+ '@protobufjs/pool@1.1.0':
+ resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
+
+ '@protobufjs/utf8@1.1.0':
+ resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
+
+ '@quansync/fs@1.0.0':
+ resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==}
+
+ '@remirror/core-constants@3.0.0':
+ resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
+
+ '@rolldown/pluginutils@1.0.0-beta.53':
+ resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==}
+
+ '@rolldown/pluginutils@1.0.0-beta.56':
+ resolution: {integrity: sha512-cw9jwAgCs024Nic4OB8PeFDLBHLD1Athcv3bRvyYATIVD9B/gL5X5cJkezT94Y7m7Dk9HXaUMcvb7ypvSX46sA==}
+
+ '@rollup/rollup-android-arm-eabi@4.54.0':
+ resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.54.0':
+ resolution: {integrity: sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.54.0':
+ resolution: {integrity: sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.54.0':
+ resolution: {integrity: sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.54.0':
+ resolution: {integrity: sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.54.0':
+ resolution: {integrity: sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.54.0':
+ resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.54.0':
+ resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==}
+ cpu: [arm]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-arm64-gnu@4.54.0':
+ resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-arm64-musl@4.54.0':
+ resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-loong64-gnu@4.54.0':
+ resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==}
+ cpu: [loong64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.54.0':
+ resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.54.0':
+ resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-riscv64-musl@4.54.0':
+ resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-s390x-gnu@4.54.0':
+ resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-x64-gnu@4.54.0':
+ resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-x64-musl@4.54.0':
+ resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-openharmony-arm64@4.54.0':
+ resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.54.0':
+ resolution: {integrity: sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.54.0':
+ resolution: {integrity: sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.54.0':
+ resolution: {integrity: sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.54.0':
+ resolution: {integrity: sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==}
+ cpu: [x64]
+ os: [win32]
+
+ '@sec-ant/readable-stream@0.4.1':
+ resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
+
+ '@sindresorhus/merge-streams@4.0.0':
+ resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
+ engines: {node: '>=18'}
+
+ '@soybeanjs/changelog@0.3.25':
+ resolution: {integrity: sha512-WtntSBlZ8w4i5pW95GShb6JFwZ2+8LzDYtRa7448vlZBge/mUa4ZekA4Mk8G8isy/VYiBMF/cB3aalWMtSPPTg==}
+ engines: {node: '>=16', pnpm: '>=9'}
+
+ '@soybeanjs/eslint-config@1.7.4':
+ resolution: {integrity: sha512-N+CQLLN72cahzuCpEult61d66w7OD6Kz1RTlNEhrfvdqPZzs7xlQJTaJvLN9dbG091nY6opatGvaGuZ+zHO3Aw==}
+ peerDependencies:
+ '@toml-tools/parser': '*'
+ '@unocss/eslint-config': '>=0.58.0'
+ eslint: '>=8.40.0'
+ eslint-plugin-astro: '>=0.30.0'
+ eslint-plugin-react: '>=7.0.0'
+ eslint-plugin-react-hooks: '>=4.0.0'
+ eslint-plugin-react-native: '>=4.0.0'
+ eslint-plugin-react-refresh: '>=0.4.0'
+ eslint-plugin-solid: '>=0.10.0'
+ eslint-plugin-svelte: '>=2.0.0'
+ eslint-plugin-vue: '>=9.19.0'
+ prettier-plugin-astro: '>=0.12.0'
+ prettier-plugin-svelte: '>=3.0.0'
+ prettier-plugin-toml: '>=2.0.0'
+ svelte-eslint-parser: '>=1.0.0'
+ typescript: '>=5.0.0'
+ vue-eslint-parser: '>=9.3.2'
+ peerDependenciesMeta:
+ '@toml-tools/parser':
+ optional: true
+ '@unocss/eslint-config':
+ optional: true
+ eslint-plugin-astro:
+ optional: true
+ eslint-plugin-react:
+ optional: true
+ eslint-plugin-react-hooks:
+ optional: true
+ eslint-plugin-react-native:
+ optional: true
+ eslint-plugin-react-refresh:
+ optional: true
+ eslint-plugin-solid:
+ optional: true
+ eslint-plugin-svelte:
+ optional: true
+ eslint-plugin-vue:
+ optional: true
+ prettier-plugin-astro:
+ optional: true
+ prettier-plugin-svelte:
+ optional: true
+ prettier-plugin-toml:
+ optional: true
+ vue-eslint-parser:
+ optional: true
+
+ '@tato30/vue-pdf@1.11.5':
+ resolution: {integrity: sha512-SV6GQx9sD0C0uO/bnAdG1k5D8OtM3lHtYKIgodCq2MbhOaHHpQoyhlSaec9Mo5JifSG4zYDFVNvbcZiMOBkyeQ==}
+ peerDependencies:
+ vue: ^3.2.33
+
+ '@tiptap-extend/columns@2.1.6':
+ resolution: {integrity: sha512-Wfox+cC3I2Qf4xwaTib+uamHnyrDPWCFIax4ipIn0z/MOtwUFTq4xEq06FlyO0catD8ES9MCXpAOendJV8f0/Q==}
+ peerDependencies:
+ '@tiptap/core': ^2.0.0
+ prosemirror-model: ^1.0.0
+ prosemirror-state: ^1.0.0
+
+ '@tiptap/core@2.11.5':
+ resolution: {integrity: sha512-jb0KTdUJaJY53JaN7ooY3XAxHQNoMYti/H6ANo707PsLXVeEqJ9o8+eBup1JU5CuwzrgnDc2dECt2WIGX9f8Jw==}
+ peerDependencies:
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-blockquote@2.27.1':
+ resolution: {integrity: sha512-QrUX3muElDrNjKM3nqCSAtm3H3pT33c6ON8kwRiQboOAjT/9D57Cs7XEVY7r6rMaJPeKztrRUrNVF9w/w/6B0A==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-bold@2.11.5':
+ resolution: {integrity: sha512-OAq03MHEbl7MtYCUzGuwb0VpOPnM0k5ekMbEaRILFU5ZC7cEAQ36XmPIw1dQayrcuE8GZL35BKub2qtRxyC9iA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-bubble-menu@2.11.5':
+ resolution: {integrity: sha512-rx+rMd7EEdht5EHLWldpkzJ56SWYA9799b33ustePqhXd6linnokJCzBqY13AfZ9+xp3RsR6C0ZHI9GGea0tIA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-bullet-list@2.11.5':
+ resolution: {integrity: sha512-VXwHlX6A/T6FAspnyjbKDO0TQ+oetXuat6RY1/JxbXphH42nLuBaGWJ6pgy6xMl6XY8/9oPkTNrfJw/8/eeRwA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-character-count@2.11.5':
+ resolution: {integrity: sha512-Da2VGb7ClmKwXdQdQC2735qylYD8/MQAPA0skPEcHxcDTDuI8ibyIDnMPnczgS/hR5g0TYE2DQp/dkhJXeovkQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-code-block-lowlight@2.11.5':
+ resolution: {integrity: sha512-EIE+mAGsp8C69dI0Yyg+VH1x36rgyPJc93SfA7h4xFF6Oth18z4YhJtiLaZcwCMyOOVs2efApZ0R3/Fnz2VlqA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/extension-code-block': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+ highlight.js: ^11
+ lowlight: ^2 || ^3
+
+ '@tiptap/extension-code-block@2.11.5':
+ resolution: {integrity: sha512-ksxMMvqLDlC+ftcQLynqZMdlJT1iHYZorXsXw/n+wuRd7YElkRkd6YWUX/Pq/njFY6lDjKiqFLEXBJB8nrzzBA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-code@2.27.1':
+ resolution: {integrity: sha512-i65wUGJevzBTIIUBHBc1ggVa27bgemvGl/tY1/89fEuS/0Xmre+OQjw8rCtSLevoHSiYYLgLRlvjtUSUhE4kgg==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-collaboration@2.11.5':
+ resolution: {integrity: sha512-3tMMq0E+FM3/3YBUMq5rLvks2DC/t1XLH2Kz/VcuVCxqg1Zg5s9nKOl6CcUZ8gbdvZoEd/GYoQyROJ957v9wzw==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+ y-prosemirror: ^1.2.11
+
+ '@tiptap/extension-color@2.11.5':
+ resolution: {integrity: sha512-9gZF6EIpfOJYUt1TtFY37e8iqwKcOmBl8CkFaxq+4mWVvYd2D7KbA0r4tYTxSO0fOBJ5fA/1qJrpvgRlyocp/A==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/extension-text-style': ^2.7.0
+
+ '@tiptap/extension-document@2.11.5':
+ resolution: {integrity: sha512-7I4BRTpIux2a0O2qS3BDmyZ5LGp3pszKbix32CmeVh7lN9dV7W5reDqtJJ9FCZEEF+pZ6e1/DQA362dflwZw2g==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-dropcursor@2.11.5':
+ resolution: {integrity: sha512-uIN7L3FU0904ec7FFFbndO7RQE/yiON4VzAMhNn587LFMyWO8US139HXIL4O8dpZeYwYL3d1FnDTflZl6CwLlg==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-floating-menu@2.27.1':
+ resolution: {integrity: sha512-nUk/8DbiXO69l6FDwkWso94BTf52IBoWALo+YGWT6o+FO6cI9LbUGghEX2CdmQYXCvSvwvISF2jXeLQWNZvPZQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-focus@2.11.5':
+ resolution: {integrity: sha512-syXFVCaXKOeSkMMozurM5y7clIXqGt2xbi7q2Fnkrm7PqACeZU6Ybv+r6uxeAvP0s3dCQLwS2KT8sUmB3i7vsw==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-font-family@2.11.5':
+ resolution: {integrity: sha512-QIuJNGyVkUjKVuJaTNYXs2ISoHRXszNsOQxwc7HcU9WZoKBbZE8ZsrFXI7CVKEvdpV04NYuBa47TGeW717fVCA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/extension-text-style': ^2.7.0
+
+ '@tiptap/extension-gapcursor@2.27.1':
+ resolution: {integrity: sha512-A9e1jr+jGhDWzNSXtIO6PYVYhf5j/udjbZwMja+wCE/3KvZU9V3IrnGKz1xNW+2Q2BDOe1QO7j5uVL9ElR6nTA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-hard-break@2.27.1':
+ resolution: {integrity: sha512-W4hHa4Io6QCTwpyTlN6UAvqMIQ7t56kIUByZhyY9EWrg/+JpbfpxE1kXFLPB4ZGgwBknFOw+e4bJ1j3oAbTJFw==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-heading@2.11.5':
+ resolution: {integrity: sha512-x/MV53psJ9baRcZ4k4WjnCUBMt8zCX7mPlKVT+9C/o+DEs/j/qxPLs95nHeQv70chZpSwCQCt93xMmuF0kPoAg==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-highlight@2.11.5':
+ resolution: {integrity: sha512-VBZfT869L9CiTLF8qr+3FBUtJcmlyUTECORNo0ceEiNDg4H6V9uNPwaROMXrWiQCc+DYVCOkx541QrXwNMzxlg==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-history@2.11.5':
+ resolution: {integrity: sha512-b+wOS33Dz1azw6F1i9LFTEIJ/gUui0Jwz5ZvmVDpL2ZHBhq1Ui0/spTT+tuZOXq7Y/uCbKL8Liu4WoedIvhboQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-horizontal-rule@2.11.5':
+ resolution: {integrity: sha512-3up2r1Du8/5/4ZYzTC0DjTwhgPI3dn8jhOCLu73m5F3OGvK/9whcXoeWoX103hYMnGDxBlfOje71yQuN35FL4A==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-image@2.11.5':
+ resolution: {integrity: sha512-HbUq9AL8gb8eSuQfY/QKkvMc66ZFN/b6jvQAILGArNOgalUfGizoC6baKTJShaExMSPjBZlaAHtJiQKPaGRHaA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-italic@2.27.1':
+ resolution: {integrity: sha512-rcm0GyniWW0UhcNI9+1eIK64GqWQLyIIrWGINslvqSUoBc+WkfocLvv4CMpRkzKlfsAxwVIBuH2eLxHKDtAREA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-link@2.11.5':
+ resolution: {integrity: sha512-4Iu/aPzevbYpe50xDI0ZkqRa6nkZ9eF270Ue2qaF3Ab47nehj+9Jl78XXzo8+LTyFMnrETI73TAs1aC/IGySeQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-list-item@2.27.1':
+ resolution: {integrity: sha512-dtsxvtzxfwOJP6dKGf0vb2MJAoDF2NxoiWzpq0XTvo7NGGYUHfuHjX07Zp0dYqb4seaDXjwsi5BIQUOp3+WMFQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-mention@2.11.5':
+ resolution: {integrity: sha512-xj0/P4WSQWiDHzQLSIqdPUEu8LlC+ptSYA+y9IDChG51j1jVqcmolnS4sxpyrfr/t0ug0smNmJ4PDjQtXaG63A==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+ '@tiptap/suggestion': ^2.7.0
+
+ '@tiptap/extension-ordered-list@2.11.5':
+ resolution: {integrity: sha512-Cu8KwruBNWAaEfshRQR0yOSaUKAeEwxW7UgbvF9cN/zZuKgK5uZosPCPTehIFCcRe+TBpRtZQh+06f/gNYpYYg==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-paragraph@2.27.1':
+ resolution: {integrity: sha512-R3QdrHcUdFAsdsn2UAIvhY0yWyHjqGyP/Rv8RRdN0OyFiTKtwTPqreKMHKJOflgX4sMJl/OpHTpNG1Kaf7Lo2A==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-placeholder@2.11.5':
+ resolution: {integrity: sha512-Pr+0Ju/l2ZvXMd9VQxtaoSZbs0BBp1jbBDqwms88ctpyvQFRfLSfSkqudQcSHyw2ROOz2E31p/7I7fpI8Y0CLA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-strike@2.27.1':
+ resolution: {integrity: sha512-S9I//K8KPgfFTC5I5lorClzXk0g4lrAv9y5qHzHO5EOWt7AFl0YTg2oN8NKSIBK4bHRnPIrjJJKv+dDFnUp5jQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-subscript@2.11.5':
+ resolution: {integrity: sha512-VpaSzxku/Bcvf4SgDB2K5d0E+FNA/56iJHMygg/WXsq2F4tMMUEivQHI/n+17ndUEO4Wybz0wItnM1G2JfRuLQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-superscript@2.11.5':
+ resolution: {integrity: sha512-sK6v2G0zFfGW+j9CmYp2e+tyZ3FTa3dP0xY4kJzefgZcHhMJLlLnjxBRwHCSi/jj5ie6WdZT4KoEooxnPs1Vzw==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-table-cell@2.11.5':
+ resolution: {integrity: sha512-S967Au0pgeULstP3FaasOf/LEh72p61Ooh1PcUMF/az4x8EeGgpcEUARpVUxsGxLFvogv6LmhPHZdtcGgdHcBw==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-table-header@2.11.5':
+ resolution: {integrity: sha512-O1iBtzZP1XZDi4h1Xmgq1T63il+fpKPvBIMZ0JJH9TyCw5i5rcrMLL2dyy5zaWK3BFRJuYBNSke4c+VWnr/g6w==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-table-row@2.11.5':
+ resolution: {integrity: sha512-+/VWhCuW24BcM5aaIc/f0bC6ZR1Q5gnuqw13MIo7gyPx7iIY6BXK8roGiZSs8wYAN4uBEf3EKFm0bSZwQuAeyg==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-table@2.11.5':
+ resolution: {integrity: sha512-NKXLhKWdAdURklm98YkCd2ai4fh8jY8HS/+X2s/2QiQt8Z98CU1keCm35fJEEExM234iB/hCqG5vY4JgTc0Tvw==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-task-item@2.11.5':
+ resolution: {integrity: sha512-Xvvww8cleM6fcNejP916vzhL31yJnAmuBvABsOV0kHGtAqkqUVCSDcN/1qjI4ihVNeIWEJBHDczjDfZzvLy1xA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-task-list@2.11.5':
+ resolution: {integrity: sha512-DJpIrBu/bjXYmyYoWQDH2GkpvRmizT9Fvbx5MscFYyfmSsYryD3vRBtCDz08gviwoRII+pFBrG4Ynb0XuQ7DsA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-text-align@2.11.5':
+ resolution: {integrity: sha512-Ei0zDpH5N9EV59ogydK4HTKa4lCPicCsQllM5n/Nf2tUJPir3aiYxzJ73FzhComD4Hpo1ANYnmssBhy8QeoPZA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-text-style@2.11.5':
+ resolution: {integrity: sha512-YUmYl0gILSd/u/ZkOmNxjNXVw+mu8fpC2f8G4I4tLODm0zCx09j9DDEJXSrM5XX72nxJQqtSQsCpNKnL0hfeEQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-text@2.27.1':
+ resolution: {integrity: sha512-a4GCT+GZ9tUwl82F4CEum9/+WsuW0/De9Be/NqrMmi7eNfAwbUTbLCTFU0gEvv25WMHCoUzaeNk/qGmzeVPJ1Q==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-typography@2.11.5':
+ resolution: {integrity: sha512-K+mwkyyH3bhnw8f6dKt0AIIh7ipPPVTY5XiWxm1ZMnS6p7TkXeqSJRU6mT1a47YLX4IGBEMlTQdvDVvJ1hwTjA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-underline@2.11.5':
+ resolution: {integrity: sha512-YpWHXNIkSoRSuzT2cvgKpyJ2tTz3LzqkTM64uC+uTJ8cUkvXIWUWejJR42q8ma/mTlQe4lHff4IQ0Sf58Digtw==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/pm@2.11.5':
+ resolution: {integrity: sha512-z9JFtqc5ZOsdQLd9vRnXfTCQ8v5ADAfRt9Nm7SqP6FUHII8E1hs38ACzf5xursmth/VonJYb5+73Pqxk1hGIPw==}
+
+ '@tiptap/starter-kit@2.11.5':
+ resolution: {integrity: sha512-SLI7Aj2ruU1t//6Mk8f+fqW+18uTqpdfLUJYgwu0CkqBckrkRZYZh6GVLk/02k3H2ki7QkFxiFbZrdbZdng0JA==}
+
+ '@tiptap/suggestion@2.11.5':
+ resolution: {integrity: sha512-uafwGgB5YuKX/xLRjnt2H5eA21I8HcNXpdbH4Du2gg3KM71RpUbkyjaV7KEMA/5qwCEo+sddlpuErj4wBycZ5Q==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/vue-3@2.11.5':
+ resolution: {integrity: sha512-etOURQq85MpfkMPeTG0r79s7wmLM7PqLD4aj19m7Fk5rkWuoEZyBinnA+9jfIKEttnhQ1G8PQnHNaqJhfIHQfA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+ vue: ^3.0.0
+
+ '@tool-belt/type-predicates@1.4.1':
+ resolution: {integrity: sha512-hxVezXhUV8bNOggi2ypjK9ZoAQlAdgiOc48b7Kr8qD9tAHjZBHgAt2cSs5HsbhZIjApZONPnSfXW7/HvQZ1UPA==}
+ engines: {node: '>=18.0.0'}
+
+ '@trysound/sax@0.2.0':
+ resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
+ engines: {node: '>=10.13.0'}
+
+ '@tybys/wasm-util@0.10.1':
+ resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
+
+ '@types/crypto-js@4.2.2':
+ resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
+
+ '@types/d3-array@3.2.2':
+ resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
+
+ '@types/d3-axis@3.0.6':
+ resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}
+
+ '@types/d3-brush@3.0.6':
+ resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}
+
+ '@types/d3-chord@3.0.6':
+ resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}
+
+ '@types/d3-color@3.1.3':
+ resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+ '@types/d3-contour@3.0.6':
+ resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
+
+ '@types/d3-delaunay@6.0.4':
+ resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
+
+ '@types/d3-dispatch@3.0.7':
+ resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==}
+
+ '@types/d3-drag@3.0.7':
+ resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
+
+ '@types/d3-dsv@3.0.7':
+ resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}
+
+ '@types/d3-ease@3.0.2':
+ resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
+
+ '@types/d3-fetch@3.0.7':
+ resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}
+
+ '@types/d3-force@3.0.10':
+ resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==}
+
+ '@types/d3-format@3.0.4':
+ resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
+
+ '@types/d3-geo@3.1.0':
+ resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
+
+ '@types/d3-hierarchy@3.1.7':
+ resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==}
+
+ '@types/d3-interpolate@3.0.4':
+ resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+ '@types/d3-path@3.1.1':
+ resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
+
+ '@types/d3-polygon@3.0.2':
+ resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}
+
+ '@types/d3-quadtree@3.0.6':
+ resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}
+
+ '@types/d3-random@3.0.3':
+ resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==}
+
+ '@types/d3-scale-chromatic@3.1.0':
+ resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==}
+
+ '@types/d3-scale@4.0.9':
+ resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
+
+ '@types/d3-selection@3.0.11':
+ resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
+
+ '@types/d3-shape@3.1.7':
+ resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==}
+
+ '@types/d3-time-format@4.0.3':
+ resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
+
+ '@types/d3-time@3.0.4':
+ resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
+
+ '@types/d3-timer@3.0.2':
+ resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+
+ '@types/d3-transition@3.0.9':
+ resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
+
+ '@types/d3-zoom@3.0.8':
+ resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
+
+ '@types/d3@7.4.3':
+ resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/geojson@7946.0.16':
+ resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
+
+ '@types/hast@3.0.4':
+ resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/katex@0.16.7':
+ resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
+
+ '@types/linkify-it@5.0.0':
+ resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
+
+ '@types/lodash-es@4.17.12':
+ resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
+
+ '@types/lodash@4.17.21':
+ resolution: {integrity: sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==}
+
+ '@types/markdown-it@14.1.2':
+ resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
+
+ '@types/mdurl@2.0.0':
+ resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
+
+ '@types/ndarray@1.0.14':
+ resolution: {integrity: sha512-oANmFZMnFQvb219SSBIhI1Ih/r4CvHDOzkWyJS/XRqkMrGH5/kaPSA1hQhdIBzouaE+5KpE/f5ylI9cujmckQg==}
+
+ '@types/node@10.17.60':
+ resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==}
+
+ '@types/node@20.3.3':
+ resolution: {integrity: sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==}
+
+ '@types/node@25.0.3':
+ resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==}
+
+ '@types/nprogress@0.2.3':
+ resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==}
+
+ '@types/qs@6.14.0':
+ resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==}
+
+ '@types/sortablejs@1.15.9':
+ resolution: {integrity: sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ==}
+
+ '@types/streamsaver@2.0.5':
+ resolution: {integrity: sha512-93o0zjV8swEhR2YI57h/2ytbJF8bJh7sI9GNB02TLJHdM4fWDxZuChwfWhyD8vt2ub4kw4rsfZ0C0yAUX+3gcg==}
+
+ '@types/svg64@1.1.2':
+ resolution: {integrity: sha512-kIsgEdYo12kMpR7bD0r3eGEp3Pen9c8HH5H7w+2iWxanwrt7h3QWua8ujjkDE8BpKAatAYI9lE9k7rF4USMUAg==}
+
+ '@types/svgo@2.6.4':
+ resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==}
+
+ '@types/trusted-types@2.0.7':
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
+ '@types/unist@3.0.3':
+ resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
+
+ '@types/web-bluetooth@0.0.20':
+ resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
+
+ '@types/web-bluetooth@0.0.21':
+ resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
+
+ '@typescript-eslint/eslint-plugin@8.50.1':
+ resolution: {integrity: sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^8.50.1
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/parser@8.50.1':
+ resolution: {integrity: sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/project-service@8.50.1':
+ resolution: {integrity: sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/scope-manager@8.50.1':
+ resolution: {integrity: sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/tsconfig-utils@8.50.1':
+ resolution: {integrity: sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/type-utils@8.50.1':
+ resolution: {integrity: sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/types@8.50.1':
+ resolution: {integrity: sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/typescript-estree@8.50.1':
+ resolution: {integrity: sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/utils@8.50.1':
+ resolution: {integrity: sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/visitor-keys@8.50.1':
+ resolution: {integrity: sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@umoteam/editor-external@8.1.0':
+ resolution: {integrity: sha512-YySP20GgOl9bqDu5F7fekSBGAZnR6O2I5omC140jB38uvswTVIMRE4fGL1E5fAI3rOocQqW5UbJvKIZYjAkoGQ==}
+
+ '@umoteam/editor-external@9.0.0':
+ resolution: {integrity: sha512-cAioi0SK3qPrIm9c7eqJimSBGztxd+la6OLihVCVoH6dawA9KCuZ62SHQ7lsl/rdRHp0VL0aYbW5NxFlEUpbJA==}
+
+ '@umoteam/editor@8.1.0':
+ resolution: {integrity: sha512-yvHXn5lOgeplS39lpjNh5nhCMYEq35jY4qyymQF5eavqtD5uLBLxZ7QWTvbPyAHtAiMCpNLXoRdOVeeZ4Oht3w==}
+ engines: {node: '>=18.0.0'}
+
+ '@umoteam/editor@9.0.1':
+ resolution: {integrity: sha512-KReeYw6g9/BpwZXlJPr8+fZd4IuG1jS9a9dFTqgjRp/tx4q6A9gMod8RKqc3dZScpuaRb61vN6PO7300rbv35w==}
+ engines: {node: '>=18.0.0'}
+
+ '@umoteam/viewer@0.1.9':
+ resolution: {integrity: sha512-aQ3DDbWm/V/wC3SFhCanqKl1XTW2xMzdSbEmS2vE8y1dwI18bgIeiKZnyWcj2aXXsxVmWuQMcnJ367DDengozg==}
+
+ '@umoteam/viewer@0.3.0':
+ resolution: {integrity: sha512-0YJMnzN86AdXWbHUWluHmfQZpzUNcxHIDfwr/Qw+OTwkeQsTHLicabqm4t66MdCIAtgMeybAejQawRwBxbrknQ==}
+
+ '@unocss/config@66.5.10':
+ resolution: {integrity: sha512-udBhfMe+2MU70ZdjnRLnwLQ+0EHYJ4f5JjjvHsfmQ0If4KeYmSStWBuX+/LHNQidhl487JiwW1lBDQ8pKHmbiw==}
+ engines: {node: '>=14'}
+
+ '@unocss/core@66.5.10':
+ resolution: {integrity: sha512-SEmPE4pWNn9VcCvZqovPwFGuG/j69W3zh+x1Ky4z/I2pnyoB0Y0lBmq22KVu/dwExe+ZKKTQpxa0j5rbE27rDQ==}
+
+ '@unocss/eslint-config@66.5.10':
+ resolution: {integrity: sha512-kDoXTBZcI7RCdWPekKrjgiuRcNYfdwjEkG6HtS1++jM0LhK6QgaMfu4p+4j0gfAz86ZNotghM3u8aWO6Fu0nRA==}
+ engines: {node: '>=14'}
+
+ '@unocss/eslint-plugin@66.5.10':
+ resolution: {integrity: sha512-Fzvl5ISMoGnALo9tqI15nNNWZza2ICqmzyujQCyzsxDZEVZzajNvt8wACVHoEz+dUZykjMPJqqdmX5ZijcPZ1w==}
+ engines: {node: '>=14'}
+
+ '@unocss/extractor-arbitrary-variants@66.5.10':
+ resolution: {integrity: sha512-9JsAY1a68WZaIbSiwQa7LLAO+t4T5nnhgmNxY3MGaK58k6Qa9ayZb4AG4fqOpw+Zn8tmKd7yXJ0s+27sx1n2BA==}
+
+ '@unocss/inspector@66.5.10':
+ resolution: {integrity: sha512-L/Nvi4bkXFxbGNOi7TPNnIIDfY1zKghfJ+cF7To/WrXplP1Y4nEZa2kGwcVBcsaysACri0whU19Dh3yf+bG+Pg==}
+
+ '@unocss/preset-icons@66.5.10':
+ resolution: {integrity: sha512-zf4Sev/F2QQgVjGjKBCw3BKc15HQAtvUrNX2zymXXbAjt83Lf27ofYzTAUVUO9mi/oQhXcP5sQrIGIe7iQX3hw==}
+
+ '@unocss/preset-mini@66.5.10':
+ resolution: {integrity: sha512-jRmweaPhaTGBSDKFuhEGayGyuGr66rTRRqzv5EAdHH4x43TFlJ1RO5SVlzzJdo1zJy4vyGSINIVKeI49FYhEKQ==}
+
+ '@unocss/preset-uno@66.5.10':
+ resolution: {integrity: sha512-O3R99td+Jt3XAJh1pVbOSTu3z7jUosg80y90iu6JQIpvXI/pGanWJEhoEz95SgJmRV+vXNEn4f6tIvfUXkTd/w==}
+
+ '@unocss/preset-wind3@66.5.10':
+ resolution: {integrity: sha512-N2Wgu+AnTSr4jIEAfajOfUtwESE/Zzr0GxwW88+MHIw6Tzj6tZeCEKNNKFzsgwfGkoNjvwIeIbkaIrIGJ7SveA==}
+
+ '@unocss/rule-utils@66.5.10':
+ resolution: {integrity: sha512-497GPWZpArNG25cto0Yq3/Yw+i0x7/N/ySq1HHeE3lB43sdmCv6+m6QEv14I/9/e5WJhQOmrY5LmHZYXC7xxMw==}
+ engines: {node: '>=14'}
+
+ '@unocss/transformer-directives@66.5.10':
+ resolution: {integrity: sha512-EDak3DGW+rSYjoZNwU8xJIXbwif+q9e3cjhCZy48ll1nfyg2E1Znqtwv/X8vLRr8fJ0gWn75P2uGi4jfGLZzMg==}
+
+ '@unocss/transformer-variant-group@66.5.10':
+ resolution: {integrity: sha512-9DWi9bLOGwdw6whCTdywVD9+lA5lkeqcgy9sMoizfUa4CfT1bSdMT27VoAbYhxeEznV92BCW2jCYt0I8M00phw==}
+
+ '@unocss/vite@66.5.10':
+ resolution: {integrity: sha512-GegFDmcWe0V2CR/uN1f+iQuDh2R1vA6EAwSvl1nyL+6ue0/zLyF9yhdVnypIVlJnS6RK/xaLPOP6vWJnqRGhZg==}
+ peerDependencies:
+ vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
+
+ '@unrs/resolver-binding-android-arm-eabi@1.11.1':
+ resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
+ cpu: [arm]
+ os: [android]
+
+ '@unrs/resolver-binding-android-arm64@1.11.1':
+ resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==}
+ cpu: [arm64]
+ os: [android]
+
+ '@unrs/resolver-binding-darwin-arm64@1.11.1':
+ resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-darwin-x64@1.11.1':
+ resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-freebsd-x64@1.11.1':
+ resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1':
+ resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1':
+ resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm64-gnu@1.11.1':
+ resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-arm64-musl@1.11.1':
+ resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
+ resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
+ resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
+ resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
+ resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-x64-gnu@1.11.1':
+ resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-x64-musl@1.11.1':
+ resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-wasm32-wasi@1.11.1':
+ resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@unrs/resolver-binding-win32-arm64-msvc@1.11.1':
+ resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@unrs/resolver-binding-win32-ia32-msvc@1.11.1':
+ resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
+ resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==}
+ cpu: [x64]
+ os: [win32]
+
+ '@vitejs/plugin-vue-jsx@5.1.2':
+ resolution: {integrity: sha512-3a2BOryRjG/Iih87x87YXz5c8nw27eSlHytvSKYfp8ZIsp5+FgFQoKeA7k2PnqWpjJrv6AoVTMnvmuKUXb771A==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ vite: ^5.0.0 || ^6.0.0 || ^7.0.0
+ vue: ^3.0.0
+
+ '@vitejs/plugin-vue@6.0.3':
+ resolution: {integrity: sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
+ vue: ^3.2.25
+
+ '@volar/language-core@2.4.27':
+ resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==}
+
+ '@volar/source-map@2.4.27':
+ resolution: {integrity: sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==}
+
+ '@volar/typescript@2.4.27':
+ resolution: {integrity: sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==}
+
+ '@vue/babel-helper-vue-transform-on@1.5.0':
+ resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==}
+
+ '@vue/babel-helper-vue-transform-on@2.0.1':
+ resolution: {integrity: sha512-uZ66EaFbnnZSYqYEyplWvn46GhZ1KuYSThdT68p+am7MgBNbQ3hphTL9L+xSIsWkdktwhPYLwPgVWqo96jDdRA==}
+
+ '@vue/babel-plugin-jsx@1.5.0':
+ resolution: {integrity: sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+
+ '@vue/babel-plugin-jsx@2.0.1':
+ resolution: {integrity: sha512-a8CaLQjD/s4PVdhrLD/zT574ZNPnZBOY+IhdtKWRB4HRZ0I2tXBi5ne7d9eCfaYwp5gU5+4KIyFTV1W1YL9xZA==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+
+ '@vue/babel-plugin-resolve-type@1.5.0':
+ resolution: {integrity: sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@vue/babel-plugin-resolve-type@2.0.1':
+ resolution: {integrity: sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@vue/compiler-core@3.5.26':
+ resolution: {integrity: sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==}
+
+ '@vue/compiler-dom@3.5.26':
+ resolution: {integrity: sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==}
+
+ '@vue/compiler-sfc@3.5.26':
+ resolution: {integrity: sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==}
+
+ '@vue/compiler-ssr@3.5.26':
+ resolution: {integrity: sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==}
+
+ '@vue/devtools-api@6.6.4':
+ resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
+
+ '@vue/devtools-api@7.7.9':
+ resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==}
+
+ '@vue/devtools-core@8.0.5':
+ resolution: {integrity: sha512-dpCw8nl0GDBuiL9SaY0mtDxoGIEmU38w+TQiYEPOLhW03VDC0lfNMYXS/qhl4I0YlysGp04NLY4UNn6xgD0VIQ==}
+ peerDependencies:
+ vue: ^3.0.0
+
+ '@vue/devtools-kit@7.7.9':
+ resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==}
+
+ '@vue/devtools-kit@8.0.5':
+ resolution: {integrity: sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==}
+
+ '@vue/devtools-shared@7.7.9':
+ resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==}
+
+ '@vue/devtools-shared@8.0.5':
+ resolution: {integrity: sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==}
+
+ '@vue/language-core@3.2.1':
+ resolution: {integrity: sha512-g6oSenpnGMtpxHGAwKuu7HJJkNZpemK/zg3vZzZbJ6cnnXq1ssxuNrXSsAHYM3NvH8p4IkTw+NLmuxyeYz4r8A==}
+
+ '@vue/reactivity@3.5.26':
+ resolution: {integrity: sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==}
+
+ '@vue/runtime-core@3.5.26':
+ resolution: {integrity: sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==}
+
+ '@vue/runtime-dom@3.5.26':
+ resolution: {integrity: sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==}
+
+ '@vue/server-renderer@3.5.26':
+ resolution: {integrity: sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==}
+ peerDependencies:
+ vue: 3.5.26
+
+ '@vue/shared@3.5.26':
+ resolution: {integrity: sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==}
+
+ '@vueuse/core@11.3.0':
+ resolution: {integrity: sha512-7OC4Rl1f9G8IT6rUfi9JrKiXy4bfmHhZ5x2Ceojy0jnd3mHNEvV4JaRygH362ror6/NZ+Nl+n13LPzGiPN8cKA==}
+
+ '@vueuse/core@13.9.0':
+ resolution: {integrity: sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==}
+ peerDependencies:
+ vue: ^3.5.0
+
+ '@vueuse/core@14.1.0':
+ resolution: {integrity: sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==}
+ peerDependencies:
+ vue: ^3.5.0
+
+ '@vueuse/metadata@11.3.0':
+ resolution: {integrity: sha512-pwDnDspTqtTo2HwfLw4Rp6yywuuBdYnPYDq+mO38ZYKGebCUQC/nVj/PXSiK9HX5otxLz8Fn7ECPbjiRz2CC3g==}
+
+ '@vueuse/metadata@13.9.0':
+ resolution: {integrity: sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==}
+
+ '@vueuse/metadata@14.1.0':
+ resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==}
+
+ '@vueuse/shared@11.3.0':
+ resolution: {integrity: sha512-P8gSSWQeucH5821ek2mn/ciCk+MS/zoRKqdQIM3bHq6p7GXDAJLmnRRKmF5F65sAVJIfzQlwR3aDzwCn10s8hA==}
+
+ '@vueuse/shared@13.9.0':
+ resolution: {integrity: sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==}
+ peerDependencies:
+ vue: ^3.5.0
+
+ '@vueuse/shared@14.1.0':
+ resolution: {integrity: sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==}
+ peerDependencies:
+ vue: ^3.5.0
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn@8.15.0:
+ resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ ajv@6.12.6:
+ resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+ alien-signals@3.1.1:
+ resolution: {integrity: sha512-ogkIWbVrLwKtHY6oOAXaYkAxP+cTH7V5FZ5+Tm4NZFd8VDZ6uNMDrfzqctTZ42eTMCSR3ne3otpcxmqSnFfPYA==}
+
+ alova@3.4.1:
+ resolution: {integrity: sha512-xGChKIiCHDqcMglF9RyuWAsk+ltwnja8Dp4MgRIRbUlhOi8XFiQnaoWwIQzJcODKeblmmiFAK7YrYnpskFZWLA==}
+ engines: {node: '>= 18.0.0'}
+
+ ansi-colors@4.1.3:
+ resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
+ engines: {node: '>=6'}
+
+ ansi-regex@2.1.1:
+ resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==}
+ engines: {node: '>=0.10.0'}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@6.2.2:
+ resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
+ engines: {node: '>=12'}
+
+ ansi-styles@2.2.1:
+ resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==}
+ engines: {node: '>=0.10.0'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ ansi-styles@6.2.3:
+ resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
+ engines: {node: '>=12'}
+
+ ansis@4.2.0:
+ resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==}
+ engines: {node: '>=14'}
+
+ anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ args-tokenizer@0.3.0:
+ resolution: {integrity: sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q==}
+
+ arr-diff@4.0.0:
+ resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==}
+ engines: {node: '>=0.10.0'}
+
+ arr-flatten@1.1.0:
+ resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==}
+ engines: {node: '>=0.10.0'}
+
+ arr-union@3.1.0:
+ resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==}
+ engines: {node: '>=0.10.0'}
+
+ array-buffer-byte-length@1.0.2:
+ resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
+ engines: {node: '>= 0.4'}
+
+ array-unique@0.3.2:
+ resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==}
+ engines: {node: '>=0.10.0'}
+
+ arraybuffer.prototype.slice@1.0.4:
+ resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
+ engines: {node: '>= 0.4'}
+
+ assign-symbols@1.0.0:
+ resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==}
+ engines: {node: '>=0.10.0'}
+
+ ast-types@0.16.1:
+ resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
+ engines: {node: '>=4'}
+
+ async-function@1.0.0:
+ resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
+ engines: {node: '>= 0.4'}
+
+ async-validator@4.2.5:
+ resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
+
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ atob@2.1.2:
+ resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
+ engines: {node: '>= 4.5.0'}
+ hasBin: true
+
+ available-typed-arrays@1.0.7:
+ resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+ engines: {node: '>= 0.4'}
+
+ axios-retry@4.5.0:
+ resolution: {integrity: sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==}
+ peerDependencies:
+ axios: 0.x || 1.x
+
+ axios@1.13.2:
+ resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ base@0.11.2:
+ resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
+ engines: {node: '>=0.10.0'}
+
+ baseline-browser-mapping@2.9.11:
+ resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==}
+ hasBin: true
+
+ big.js@5.2.2:
+ resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
+
+ binary-extensions@2.3.0:
+ resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
+ engines: {node: '>=8'}
+
+ birpc@2.9.0:
+ resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
+
+ bluebird@3.7.2:
+ resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
+
+ boolbase@1.0.0:
+ resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+
+ brace-expansion@1.1.12:
+ resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
+
+ brace-expansion@2.0.2:
+ resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
+
+ braces@2.3.2:
+ resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==}
+ engines: {node: '>=0.10.0'}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ browserslist@4.28.1:
+ resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ buffer-image-size@0.6.4:
+ resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==}
+ engines: {node: '>=4.0'}
+
+ builtin-modules@5.0.0:
+ resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==}
+ engines: {node: '>=18.20'}
+
+ bumpp@10.3.2:
+ resolution: {integrity: sha512-yUUkVx5zpTywLNX97MlrqtpanI7eMMwFwLntWR2EBVDw3/Pm3aRIzCoDEGHATLIiHK9PuJC7xWI4XNWqXItSPg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ bundle-name@4.1.0:
+ resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
+ engines: {node: '>=18'}
+
+ c12@3.3.3:
+ resolution: {integrity: sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==}
+ peerDependencies:
+ magicast: '*'
+ peerDependenciesMeta:
+ magicast:
+ optional: true
+
+ cac@6.7.14:
+ resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+ engines: {node: '>=8'}
+
+ cache-base@1.0.1:
+ resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==}
+ engines: {node: '>=0.10.0'}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bind@1.0.8:
+ resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
+ engines: {node: '>= 0.4'}
+
+ call-bound@1.0.4:
+ resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+ engines: {node: '>= 0.4'}
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ camelcase@6.3.0:
+ resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
+ engines: {node: '>=10'}
+
+ caniuse-lite@1.0.30001761:
+ resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==}
+
+ chalk@1.1.3:
+ resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
+ engines: {node: '>=0.10.0'}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ change-case@5.4.4:
+ resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==}
+
+ chevrotain-allstar@0.3.1:
+ resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==}
+ peerDependencies:
+ chevrotain: ^11.0.0
+
+ chevrotain@11.0.3:
+ resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==}
+
+ chokidar@3.6.0:
+ resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+ engines: {node: '>= 8.10.0'}
+
+ chokidar@4.0.3:
+ resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
+ engines: {node: '>= 14.16.0'}
+
+ chokidar@5.0.0:
+ resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
+ engines: {node: '>= 20.19.0'}
+
+ ci-info@4.3.1:
+ resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==}
+ engines: {node: '>=8'}
+
+ citty@0.1.6:
+ resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
+
+ class-utils@0.3.6:
+ resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==}
+ engines: {node: '>=0.10.0'}
+
+ classnames@2.5.1:
+ resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
+
+ clean-regexp@1.0.0:
+ resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
+ engines: {node: '>=4'}
+
+ cli-progress@3.12.0:
+ resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==}
+ engines: {node: '>=4'}
+
+ clipboard@2.0.11:
+ resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==}
+
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
+ clone@2.1.2:
+ resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
+ engines: {node: '>=0.8'}
+
+ collection-visit@1.0.0:
+ resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==}
+ engines: {node: '>=0.10.0'}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ colord@2.9.3:
+ resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
+
+ colorette@2.0.20:
+ resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
+
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
+ commander@7.2.0:
+ resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
+ engines: {node: '>= 10'}
+
+ commander@8.3.0:
+ resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
+ engines: {node: '>= 12'}
+
+ comment-parser@1.4.1:
+ resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==}
+ engines: {node: '>= 12.0.0'}
+
+ component-emitter@1.3.1:
+ resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ confbox@0.1.8:
+ resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
+
+ confbox@0.2.2:
+ resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
+
+ consola@3.2.3:
+ resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==}
+ engines: {node: ^14.18.0 || >=16.10.0}
+
+ consola@3.4.2:
+ resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
+ engines: {node: ^14.18.0 || >=16.10.0}
+
+ convert-gitmoji@0.1.5:
+ resolution: {integrity: sha512-4wqOafJdk2tqZC++cjcbGcaJ13BZ3kwldf06PTiAQRAB76Z1KJwZNL1SaRZMi2w1FM9RYTgZ6QErS8NUl/GBmQ==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ copy-anything@4.0.5:
+ resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==}
+ engines: {node: '>=18'}
+
+ copy-descriptor@0.1.1:
+ resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==}
+ engines: {node: '>=0.10.0'}
+
+ core-js-compat@3.47.0:
+ resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==}
+
+ core-js@3.47.0:
+ resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==}
+
+ cors@2.8.5:
+ resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
+ engines: {node: '>= 0.10'}
+
+ cose-base@1.0.3:
+ resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
+
+ cose-base@2.2.0:
+ resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}
+
+ crelt@1.0.6:
+ resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ crypto-js@4.2.0:
+ resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
+
+ css-render@0.15.14:
+ resolution: {integrity: sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg==}
+
+ css-select@4.3.0:
+ resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
+
+ css-tree@1.1.3:
+ resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==}
+ engines: {node: '>=8.0.0'}
+
+ css-tree@3.1.0:
+ resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
+
+ css-what@6.2.2:
+ resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
+ engines: {node: '>= 6'}
+
+ cssesc@3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ csso@4.2.0:
+ resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==}
+ engines: {node: '>=8.0.0'}
+
+ csstype@3.0.11:
+ resolution: {integrity: sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ custom-event-polyfill@1.0.7:
+ resolution: {integrity: sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==}
+
+ cytoscape-cose-bilkent@4.1.0:
+ resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==}
+ peerDependencies:
+ cytoscape: ^3.2.0
+
+ cytoscape-fcose@2.2.0:
+ resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==}
+ peerDependencies:
+ cytoscape: ^3.2.0
+
+ cytoscape@3.33.1:
+ resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==}
+ engines: {node: '>=0.10'}
+
+ d3-array@2.12.1:
+ resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==}
+
+ d3-array@3.2.4:
+ resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+ engines: {node: '>=12'}
+
+ d3-axis@3.0.0:
+ resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
+ engines: {node: '>=12'}
+
+ d3-brush@3.0.0:
+ resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
+ engines: {node: '>=12'}
+
+ d3-chord@3.0.1:
+ resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
+ engines: {node: '>=12'}
+
+ d3-color@3.1.0:
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+
+ d3-contour@4.0.2:
+ resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
+ engines: {node: '>=12'}
+
+ d3-delaunay@6.0.4:
+ resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
+ engines: {node: '>=12'}
+
+ d3-dispatch@3.0.1:
+ resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+ engines: {node: '>=12'}
+
+ d3-drag@3.0.0:
+ resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+ engines: {node: '>=12'}
+
+ d3-dsv@3.0.1:
+ resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ d3-ease@3.0.1:
+ resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+ engines: {node: '>=12'}
+
+ d3-fetch@3.0.1:
+ resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
+ engines: {node: '>=12'}
+
+ d3-force@3.0.0:
+ resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
+ engines: {node: '>=12'}
+
+ d3-format@3.1.0:
+ resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
+ engines: {node: '>=12'}
+
+ d3-geo@3.1.1:
+ resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
+ engines: {node: '>=12'}
+
+ d3-hierarchy@3.1.2:
+ resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
+ engines: {node: '>=12'}
+
+ d3-interpolate@3.0.1:
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+
+ d3-path@1.0.9:
+ resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==}
+
+ d3-path@3.1.0:
+ resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+ engines: {node: '>=12'}
+
+ d3-polygon@3.0.1:
+ resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
+ engines: {node: '>=12'}
+
+ d3-quadtree@3.0.1:
+ resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
+ engines: {node: '>=12'}
+
+ d3-random@3.0.1:
+ resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
+ engines: {node: '>=12'}
+
+ d3-sankey@0.12.3:
+ resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==}
+
+ d3-scale-chromatic@3.1.0:
+ resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
+ engines: {node: '>=12'}
+
+ d3-scale@4.0.2:
+ resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+ engines: {node: '>=12'}
+
+ d3-selection@3.0.0:
+ resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+ engines: {node: '>=12'}
+
+ d3-shape@1.3.7:
+ resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==}
+
+ d3-shape@3.2.0:
+ resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+ engines: {node: '>=12'}
+
+ d3-time-format@4.1.0:
+ resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+ engines: {node: '>=12'}
+
+ d3-time@3.1.0:
+ resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+ engines: {node: '>=12'}
+
+ d3-timer@3.0.1:
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+
+ d3-transition@3.0.1:
+ resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ d3-selection: 2 - 3
+
+ d3-zoom@3.0.0:
+ resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+ engines: {node: '>=12'}
+
+ d3@7.9.0:
+ resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
+ engines: {node: '>=12'}
+
+ dagre-d3-es@7.0.13:
+ resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==}
+
+ data-view-buffer@1.0.2:
+ resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-length@1.0.2:
+ resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-offset@1.0.1:
+ resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
+ engines: {node: '>= 0.4'}
+
+ date-fns-tz@3.2.0:
+ resolution: {integrity: sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==}
+ peerDependencies:
+ date-fns: ^3.0.0 || ^4.0.0
+
+ date-fns@4.1.0:
+ resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+
+ dayjs@1.11.15:
+ resolution: {integrity: sha512-MC+DfnSWiM9APs7fpiurHGCoeIx0Gdl6QZBy+5lu8MbYKN5FZEXqOgrundfibdfhGZ15o9hzmZ2xJjZnbvgKXQ==}
+
+ dayjs@1.11.19:
+ resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
+
+ debounce@1.2.1:
+ resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
+
+ debug@2.6.9:
+ resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decode-uri-component@0.2.2:
+ resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
+ engines: {node: '>=0.10'}
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ default-browser-id@5.0.1:
+ resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==}
+ engines: {node: '>=18'}
+
+ default-browser@5.4.0:
+ resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==}
+ engines: {node: '>=18'}
+
+ default-passive-events@4.0.0:
+ resolution: {integrity: sha512-0whk/GqfDOjc0AJIpacXUSqX6kV9TjL3GFSXIxFvuXQYcK+bEdJ6rpJnAEfP4YYMYWibM+jhlwmdlVrlifoepg==}
+
+ define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+
+ define-lazy-prop@3.0.0:
+ resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
+ engines: {node: '>=12'}
+
+ define-properties@1.2.1:
+ resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ engines: {node: '>= 0.4'}
+
+ define-property@0.2.5:
+ resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==}
+ engines: {node: '>=0.10.0'}
+
+ define-property@1.0.0:
+ resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==}
+ engines: {node: '>=0.10.0'}
+
+ define-property@2.0.2:
+ resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==}
+ engines: {node: '>=0.10.0'}
+
+ defu@6.1.4:
+ resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
+
+ delaunator@5.0.1:
+ resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
+
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ delegate@3.2.0:
+ resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==}
+
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
+ destr@2.0.5:
+ resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
+
+ detect-libc@1.0.3:
+ resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
+ engines: {node: '>=0.10'}
+ hasBin: true
+
+ devlop@1.1.0:
+ resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+
+ dom-serializer@0.2.2:
+ resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==}
+
+ dom-serializer@1.4.1:
+ resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
+
+ dom-to-image-more@3.7.2:
+ resolution: {integrity: sha512-uQf+pHv6eQhgfI8t2bFuinV0KsPyT8TZgCLwcSU8uBVgN9v6leb0mMpvp6HQAlAcplP3NCcGjxbdqef6pTzvmw==}
+
+ domelementtype@1.3.1:
+ resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==}
+
+ domelementtype@2.3.0:
+ resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
+ domhandler@2.4.2:
+ resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==}
+
+ domhandler@4.3.1:
+ resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
+ engines: {node: '>= 4'}
+
+ dompurify@3.3.1:
+ resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==}
+
+ domutils@1.7.0:
+ resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==}
+
+ domutils@2.8.0:
+ resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
+
+ dotenv@17.2.3:
+ resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
+ engines: {node: '>=12'}
+
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
+ duplexer@0.1.2:
+ resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
+
+ eastasianwidth@0.2.0:
+ resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+
+ easy-bem@1.1.1:
+ resolution: {integrity: sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A==}
+
+ echarts@5.6.0:
+ resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==}
+
+ echarts@6.0.0:
+ resolution: {integrity: sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==}
+
+ electron-to-chromium@1.5.267:
+ resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+ emojis-list@3.0.0:
+ resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
+ engines: {node: '>= 4'}
+
+ enhanced-resolve@5.18.4:
+ resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==}
+ engines: {node: '>=10.13.0'}
+
+ enquirer@2.4.1:
+ resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==}
+ engines: {node: '>=8.6'}
+
+ entities@1.1.2:
+ resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==}
+
+ entities@2.2.0:
+ resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
+
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
+ entities@7.0.0:
+ resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==}
+ engines: {node: '>=0.12'}
+
+ error-stack-parser-es@1.0.5:
+ resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
+
+ es-abstract@1.24.1:
+ resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
+ engines: {node: '>= 0.4'}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-drager@1.2.11:
+ resolution: {integrity: sha512-36c7+Tz+Z+3NoUzk82M6PL84yA0fIEbz2eRC+qX6TymRLq3ApGdQXdtLZTcPtvXTKjeedJSMgYwb51oRISvrLA==}
+ peerDependencies:
+ vue: '>=3.2.0'
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ es-object-atoms@1.1.1:
+ resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+ engines: {node: '>= 0.4'}
+
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
+
+ es-to-primitive@1.3.0:
+ resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
+ engines: {node: '>= 0.4'}
+
+ esbuild@0.27.2:
+ resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-string-regexp@1.0.5:
+ resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
+ engines: {node: '>=0.8.0'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ eslint-compat-utils@0.5.1:
+ resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ eslint: '>=6.0.0'
+
+ eslint-config-flat-gitignore@2.1.0:
+ resolution: {integrity: sha512-cJzNJ7L+psWp5mXM7jBX+fjHtBvvh06RBlcweMhKD8jWqQw0G78hOW5tpVALGHGFPsBV+ot2H+pdDGJy6CV8pA==}
+ peerDependencies:
+ eslint: ^9.5.0
+
+ eslint-config-prettier@10.1.8:
+ resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==}
+ hasBin: true
+ peerDependencies:
+ eslint: '>=7.0.0'
+
+ eslint-import-context@0.1.9:
+ resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ unrs-resolver: ^1.0.0
+ peerDependenciesMeta:
+ unrs-resolver:
+ optional: true
+
+ eslint-parser-plain@0.1.1:
+ resolution: {integrity: sha512-KRgd6wuxH4U8kczqPp+Oyk4irThIhHWxgFgLDtpgjUGVIS3wGrJntvZW/p6hHq1T4FOwnOtCNkvAI4Kr+mQ/Hw==}
+
+ eslint-plugin-es-x@7.8.0:
+ resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ eslint: '>=8'
+
+ eslint-plugin-import-x@4.16.1:
+ resolution: {integrity: sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/utils': ^8.0.0
+ eslint: ^8.57.0 || ^9.0.0
+ eslint-import-resolver-node: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/utils':
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+
+ eslint-plugin-n@17.23.1:
+ resolution: {integrity: sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: '>=8.23.0'
+
+ eslint-plugin-prettier@5.5.4:
+ resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ '@types/eslint': '>=8.0.0'
+ eslint: '>=8.0.0'
+ eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0'
+ prettier: '>=3.0.0'
+ peerDependenciesMeta:
+ '@types/eslint':
+ optional: true
+ eslint-config-prettier:
+ optional: true
+
+ eslint-plugin-unicorn@62.0.0:
+ resolution: {integrity: sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g==}
+ engines: {node: ^20.10.0 || >=21.0.0}
+ peerDependencies:
+ eslint: '>=9.38.0'
+
+ eslint-plugin-vue@10.6.2:
+ resolution: {integrity: sha512-nA5yUs/B1KmKzvC42fyD0+l9Yd+LtEpVhWRbXuDj0e+ZURcTtyRbMDWUeJmTAh2wC6jC83raS63anNM2YT3NPw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
+ '@typescript-eslint/parser': ^7.0.0 || ^8.0.0
+ eslint: ^8.57.0 || ^9.0.0
+ vue-eslint-parser: ^10.0.0
+ peerDependenciesMeta:
+ '@stylistic/eslint-plugin':
+ optional: true
+ '@typescript-eslint/parser':
+ optional: true
+
+ eslint-scope@8.4.0:
+ resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-visitor-keys@4.2.1:
+ resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint@9.39.2:
+ resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
+ espree@10.4.0:
+ resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ esprima@4.0.1:
+ resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ esquery@1.6.0:
+ resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ estree-walker@2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ etag@1.8.1:
+ resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+ engines: {node: '>= 0.6'}
+
+ evtd@0.2.4:
+ resolution: {integrity: sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==}
+
+ execa@9.6.0:
+ resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==}
+ engines: {node: ^18.19.0 || >=20.5.0}
+
+ execa@9.6.1:
+ resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==}
+ engines: {node: ^18.19.0 || >=20.5.0}
+
+ expand-brackets@2.1.4:
+ resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==}
+ engines: {node: '>=0.10.0'}
+
+ exsolve@1.0.8:
+ resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
+
+ extend-shallow@2.0.1:
+ resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
+ engines: {node: '>=0.10.0'}
+
+ extend-shallow@3.0.2:
+ resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==}
+ engines: {node: '>=0.10.0'}
+
+ extglob@2.0.4:
+ resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==}
+ engines: {node: '>=0.10.0'}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-diff@1.3.0:
+ resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
+
+ fast-glob@3.3.2:
+ resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
+ engines: {node: '>=8.6.0'}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fastq@1.19.1:
+ resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ figures@6.1.0:
+ resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
+ engines: {node: '>=18'}
+
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
+
+ file-saver@2.0.5:
+ resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
+
+ file64@1.0.5:
+ resolution: {integrity: sha512-GQLvUJk6RIMUJhULultcZ5cQfKUIgcBWmiYdVMXAAljhNJAmEj+H16LGiaR5ZbF+XEBm4CGjVFv5FzuL6EeceA==}
+ engines: {node: '>=18'}
+
+ fill-range@4.0.0:
+ resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==}
+ engines: {node: '>=0.10.0'}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ find-up-simple@1.0.1:
+ resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==}
+ engines: {node: '>=18'}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
+
+ flatbuffers@1.12.0:
+ resolution: {integrity: sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==}
+
+ flatted@3.3.3:
+ resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+
+ follow-redirects@1.15.11:
+ resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+
+ for-each@0.3.5:
+ resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
+ engines: {node: '>= 0.4'}
+
+ for-in@1.0.2:
+ resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==}
+ engines: {node: '>=0.10.0'}
+
+ foreground-child@3.3.1:
+ resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
+ engines: {node: '>=14'}
+
+ form-data@4.0.5:
+ resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
+ engines: {node: '>= 6'}
+
+ fragment-cache@0.2.1:
+ resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==}
+ engines: {node: '>=0.10.0'}
+
+ fs-extra@10.1.0:
+ resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
+ engines: {node: '>=12'}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ function.prototype.name@1.1.8:
+ resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
+ engines: {node: '>= 0.4'}
+
+ functions-have-names@1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+
+ generator-function@2.0.1:
+ resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
+ engines: {node: '>= 0.4'}
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
+ get-stream@9.0.1:
+ resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
+ engines: {node: '>=18'}
+
+ get-symbol-description@1.1.0:
+ resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
+ engines: {node: '>= 0.4'}
+
+ get-tsconfig@4.13.0:
+ resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
+
+ get-value@2.0.6:
+ resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==}
+ engines: {node: '>=0.10.0'}
+
+ giget@2.0.0:
+ resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==}
+ hasBin: true
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ glob@10.5.0:
+ resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
+ hasBin: true
+
+ glob@13.0.0:
+ resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==}
+ engines: {node: 20 || >=22}
+
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
+
+ globals@15.15.0:
+ resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
+ engines: {node: '>=18'}
+
+ globals@16.5.0:
+ resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==}
+ engines: {node: '>=18'}
+
+ globalthis@1.0.4:
+ resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
+ engines: {node: '>= 0.4'}
+
+ globrex@0.1.2:
+ resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
+
+ good-listener@1.2.2:
+ resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==}
+
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ guid-typescript@1.0.9:
+ resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==}
+
+ gzip-size@6.0.0:
+ resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
+ engines: {node: '>=10'}
+
+ hachure-fill@0.5.2:
+ resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
+
+ has-ansi@2.0.0:
+ resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==}
+ engines: {node: '>=0.10.0'}
+
+ has-bigints@1.1.0:
+ resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
+ engines: {node: '>= 0.4'}
+
+ has-flag@1.0.0:
+ resolution: {integrity: sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==}
+ engines: {node: '>=0.10.0'}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+ has-proto@1.2.0:
+ resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
+ engines: {node: '>= 0.4'}
+
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
+ has-value@0.3.1:
+ resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==}
+ engines: {node: '>=0.10.0'}
+
+ has-value@1.0.0:
+ resolution: {integrity: sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==}
+ engines: {node: '>=0.10.0'}
+
+ has-values@0.1.4:
+ resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==}
+ engines: {node: '>=0.10.0'}
+
+ has-values@1.0.0:
+ resolution: {integrity: sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==}
+ engines: {node: '>=0.10.0'}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ he@1.2.0:
+ resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
+ hasBin: true
+
+ highlight.js@11.11.1:
+ resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
+ engines: {node: '>=12.0.0'}
+
+ hookable@5.5.3:
+ resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
+
+ hotkeys-js@3.13.15:
+ resolution: {integrity: sha512-gHh8a/cPTCpanraePpjRxyIlxDFrIhYqjuh01UHWEwDpglJKCnvLW8kqSx5gQtOuSsJogNZXLhOdbSExpgUiqg==}
+
+ htmlparser2@3.10.1:
+ resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
+
+ human-signals@8.0.1:
+ resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
+ engines: {node: '>=18.18.0'}
+
+ iconv-lite@0.6.3:
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+
+ icss-replace-symbols@1.1.0:
+ resolution: {integrity: sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==}
+
+ icss-utils@5.1.0:
+ resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==}
+ engines: {node: ^10 || ^12 || >= 14}
+ peerDependencies:
+ postcss: ^8.1.0
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ ignore@7.0.5:
+ resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
+ engines: {node: '>= 4'}
+
+ image-size@0.5.5:
+ resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==}
+ engines: {node: '>=0.10.0'}
+ hasBin: true
+
+ immediate@3.0.6:
+ resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
+
+ immutable@5.1.4:
+ resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ indent-string@5.0.0:
+ resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
+ engines: {node: '>=12'}
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ internal-slot@1.1.0:
+ resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
+ engines: {node: '>= 0.4'}
+
+ internmap@1.0.1:
+ resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==}
+
+ internmap@2.0.3:
+ resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+ engines: {node: '>=12'}
+
+ iota-array@1.0.0:
+ resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==}
+
+ is-accessor-descriptor@1.0.1:
+ resolution: {integrity: sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==}
+ engines: {node: '>= 0.10'}
+
+ is-array-buffer@3.0.5:
+ resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
+ engines: {node: '>= 0.4'}
+
+ is-async-function@2.1.1:
+ resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
+ engines: {node: '>= 0.4'}
+
+ is-bigint@1.1.0:
+ resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+ engines: {node: '>= 0.4'}
+
+ is-binary-path@2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+
+ is-boolean-object@1.2.2:
+ resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
+ engines: {node: '>= 0.4'}
+
+ is-buffer@1.1.6:
+ resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
+
+ is-builtin-module@5.0.0:
+ resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==}
+ engines: {node: '>=18.20'}
+
+ is-callable@1.2.7:
+ resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ engines: {node: '>= 0.4'}
+
+ is-data-descriptor@1.0.1:
+ resolution: {integrity: sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==}
+ engines: {node: '>= 0.4'}
+
+ is-data-view@1.0.2:
+ resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
+ engines: {node: '>= 0.4'}
+
+ is-date-object@1.1.0:
+ resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
+ engines: {node: '>= 0.4'}
+
+ is-descriptor@0.1.7:
+ resolution: {integrity: sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==}
+ engines: {node: '>= 0.4'}
+
+ is-descriptor@1.0.3:
+ resolution: {integrity: sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==}
+ engines: {node: '>= 0.4'}
+
+ is-docker@3.0.0:
+ resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ hasBin: true
+
+ is-extendable@0.1.1:
+ resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
+ engines: {node: '>=0.10.0'}
+
+ is-extendable@1.0.1:
+ resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==}
+ engines: {node: '>=0.10.0'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-finalizationregistry@1.1.1:
+ resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
+ engines: {node: '>= 0.4'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-generator-function@1.1.2:
+ resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
+ engines: {node: '>= 0.4'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-inside-container@1.0.0:
+ resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
+ engines: {node: '>=14.16'}
+ hasBin: true
+
+ is-map@2.0.3:
+ resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
+ engines: {node: '>= 0.4'}
+
+ is-negative-zero@2.0.3:
+ resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
+ engines: {node: '>= 0.4'}
+
+ is-number-object@1.1.1:
+ resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
+ engines: {node: '>= 0.4'}
+
+ is-number@3.0.0:
+ resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==}
+ engines: {node: '>=0.10.0'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-plain-obj@1.1.0:
+ resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
+ engines: {node: '>=0.10.0'}
+
+ is-plain-obj@4.1.0:
+ resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
+ engines: {node: '>=12'}
+
+ is-plain-object@2.0.4:
+ resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
+ engines: {node: '>=0.10.0'}
+
+ is-regex@1.2.1:
+ resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
+ engines: {node: '>= 0.4'}
+
+ is-retry-allowed@2.2.0:
+ resolution: {integrity: sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==}
+ engines: {node: '>=10'}
+
+ is-set@2.0.3:
+ resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
+ engines: {node: '>= 0.4'}
+
+ is-shared-array-buffer@1.0.4:
+ resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
+ engines: {node: '>= 0.4'}
+
+ is-stream@4.0.1:
+ resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
+ engines: {node: '>=18'}
+
+ is-string@1.1.1:
+ resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
+ engines: {node: '>= 0.4'}
+
+ is-symbol@1.1.1:
+ resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
+ engines: {node: '>= 0.4'}
+
+ is-there@4.5.2:
+ resolution: {integrity: sha512-ixMkfz3rtS1vEsLf0TjgjqUn96Q0ukpUVDMnPYVocJyTzu2G/QgEtqYddcHZawHO+R31cKVPggJmBLrm1vJCOg==}
+
+ is-typed-array@1.1.15:
+ resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
+ engines: {node: '>= 0.4'}
+
+ is-unicode-supported@2.1.0:
+ resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
+ engines: {node: '>=18'}
+
+ is-weakmap@2.0.2:
+ resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
+ engines: {node: '>= 0.4'}
+
+ is-weakref@1.1.1:
+ resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
+ engines: {node: '>= 0.4'}
+
+ is-weakset@2.0.4:
+ resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
+ engines: {node: '>= 0.4'}
+
+ is-what@5.5.0:
+ resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==}
+ engines: {node: '>=18'}
+
+ is-windows@1.0.2:
+ resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
+ engines: {node: '>=0.10.0'}
+
+ is-wsl@3.1.0:
+ resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
+ engines: {node: '>=16'}
+
+ isarray@1.0.0:
+ resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
+
+ isarray@2.0.5:
+ resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ isobject@2.1.0:
+ resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==}
+ engines: {node: '>=0.10.0'}
+
+ isobject@3.0.1:
+ resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
+ engines: {node: '>=0.10.0'}
+
+ isomorphic.js@0.2.5:
+ resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
+
+ jackspeak@3.4.3:
+ resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
+ js-base64@2.6.4:
+ resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-yaml@4.1.1:
+ resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
+ hasBin: true
+
+ jsbarcode@3.12.1:
+ resolution: {integrity: sha512-QZQSqIknC2Rr/YOUyOkCBqsoiBAOTYK+7yNN3JsqfoUtJtkazxNw1dmPpxuv7VVvqW13kA3/mKiLq+s/e3o9hQ==}
+
+ jsencrypt@3.5.4:
+ resolution: {integrity: sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==}
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ json5@1.0.2:
+ resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
+ hasBin: true
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ jsonc-parser@3.3.1:
+ resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
+
+ jsonfile@6.2.0:
+ resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
+
+ katex@0.16.27:
+ resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==}
+ hasBin: true
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ khroma@2.1.0:
+ resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
+
+ kind-of@3.2.2:
+ resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==}
+ engines: {node: '>=0.10.0'}
+
+ kind-of@4.0.0:
+ resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==}
+ engines: {node: '>=0.10.0'}
+
+ kind-of@5.1.0:
+ resolution: {integrity: sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==}
+ engines: {node: '>=0.10.0'}
+
+ kind-of@6.0.3:
+ resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
+ engines: {node: '>=0.10.0'}
+
+ kleur@3.0.3:
+ resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
+ engines: {node: '>=6'}
+
+ klona@2.0.6:
+ resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==}
+ engines: {node: '>= 8'}
+
+ kolorist@1.8.0:
+ resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
+
+ langium@3.3.1:
+ resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==}
+ engines: {node: '>=16.0.0'}
+
+ layout-base@1.0.2:
+ resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
+
+ layout-base@2.0.1:
+ resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==}
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
+ lib0@0.2.115:
+ resolution: {integrity: sha512-noaW4yNp6hCjOgDnWWxW0vGXE3kZQI5Kqiwz+jIWXavI9J9WyfJ9zjsbQlQlgjIbHBrvlA/x3TSIXBUJj+0L6g==}
+ engines: {node: '>=16'}
+ hasBin: true
+
+ lie@3.1.1:
+ resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
+
+ linkify-it@5.0.0:
+ resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
+
+ linkifyjs@4.3.2:
+ resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==}
+
+ loader-utils@1.4.2:
+ resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==}
+ engines: {node: '>=4.0.0'}
+
+ loadjs@4.3.0:
+ resolution: {integrity: sha512-vNX4ZZLJBeDEOBvdr2v/F+0aN5oMuPu7JTqrMwp+DtgK+AryOlpy6Xtm2/HpNr+azEa828oQjOtWsB6iDtSfSQ==}
+
+ local-pkg@1.1.2:
+ resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
+ engines: {node: '>=14'}
+
+ localforage@1.10.0:
+ resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
+
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash-es@4.17.21:
+ resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
+
+ lodash-es@4.17.22:
+ resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+ long@5.3.2:
+ resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
+
+ lowlight@3.3.0:
+ resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==}
+
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+ lru-cache@11.2.4:
+ resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==}
+ engines: {node: 20 || >=22}
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ magic-string@0.30.11:
+ resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ magicast@0.3.4:
+ resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==}
+
+ map-cache@0.2.2:
+ resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==}
+ engines: {node: '>=0.10.0'}
+
+ map-visit@1.0.0:
+ resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==}
+ engines: {node: '>=0.10.0'}
+
+ markdown-it@14.1.0:
+ resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
+ hasBin: true
+
+ marked@16.4.2:
+ resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==}
+ engines: {node: '>= 20'}
+ hasBin: true
+
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
+ mdn-data@2.0.14:
+ resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
+
+ mdn-data@2.12.2:
+ resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
+
+ mdurl@2.0.0:
+ resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
+
+ merge-options@1.0.1:
+ resolution: {integrity: sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==}
+ engines: {node: '>=4'}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ mermaid@11.12.2:
+ resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==}
+
+ micromatch@3.1.0:
+ resolution: {integrity: sha512-3StSelAE+hnRvMs8IdVW7Uhk8CVed5tp+kLLGlBP6WiRAXS21GPGu/Nat4WNPXj2Eoc24B02SaeoyozPMfj0/g==}
+ engines: {node: '>=0.10.0'}
+
+ micromatch@4.0.7:
+ resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
+ engines: {node: '>=8.6'}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ minimatch@10.1.1:
+ resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==}
+ engines: {node: 20 || >=22}
+
+ minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+ minimatch@9.0.5:
+ resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ minipass@7.1.2:
+ resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ mitt@3.0.1:
+ resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
+
+ mixin-deep@1.3.2:
+ resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
+ engines: {node: '>=0.10.0'}
+
+ mkdirp@3.0.1:
+ resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ mlly@1.8.0:
+ resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
+
+ mrmime@2.0.1:
+ resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
+ engines: {node: '>=10'}
+
+ ms@2.0.0:
+ resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ muggle-string@0.4.1:
+ resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
+
+ naive-ui@2.43.2:
+ resolution: {integrity: sha512-YlLMnGrwGTOc+zMj90sG3ubaH5/7czsgLgGcjTLA981IUaz8r6t4WIujNt8r9PNr+dqv6XNEr0vxkARgPPjfBQ==}
+ peerDependencies:
+ vue: ^3.0.0
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ nanoid@5.1.6:
+ resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==}
+ engines: {node: ^18 || >=20}
+ hasBin: true
+
+ nanomatch@1.2.13:
+ resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
+ engines: {node: '>=0.10.0'}
+
+ napi-postinstall@0.3.4:
+ resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ hasBin: true
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ ndarray@1.0.19:
+ resolution: {integrity: sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==}
+
+ node-addon-api@7.1.1:
+ resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
+
+ node-fetch-native@1.6.7:
+ resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
+
+ node-releases@2.0.27:
+ resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+
+ normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+
+ npm-check-updates@19.2.0:
+ resolution: {integrity: sha512-XSIuL0FNgzXPDZa4lje7+OwHjiyEt84qQm6QMsQRbixNY5EHEM9nhgOjxjlK9jIbN+ysvSqOV8DKNS0zydwbdg==}
+ engines: {node: '>=20.0.0', npm: '>=8.12.1'}
+ hasBin: true
+
+ npm-run-path@6.0.0:
+ resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
+ engines: {node: '>=18'}
+
+ nprogress@0.2.0:
+ resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==}
+
+ nth-check@2.1.1:
+ resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+
+ nypm@0.6.2:
+ resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==}
+ engines: {node: ^14.16.0 || >=16.10.0}
+ hasBin: true
+
+ nzh@1.0.14:
+ resolution: {integrity: sha512-wKgaqCSZdrySvB4RWop5g+v6IDv2IErsT6rjq06Bg0yiT9hiHYZO12GMGx/xweGVLcO2lDjX5RqWD0S/Jy9z5Q==}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-copy@0.1.0:
+ resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==}
+ engines: {node: '>=0.10.0'}
+
+ object-inspect@1.13.4:
+ resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+ engines: {node: '>= 0.4'}
+
+ object-keys@1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+
+ object-visit@1.0.1:
+ resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==}
+ engines: {node: '>=0.10.0'}
+
+ object.assign@4.1.7:
+ resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
+ engines: {node: '>= 0.4'}
+
+ object.pick@1.3.0:
+ resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==}
+ engines: {node: '>=0.10.0'}
+
+ ofetch@1.4.1:
+ resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==}
+
+ ofetch@1.5.1:
+ resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==}
+
+ ohash@2.0.11:
+ resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
+
+ onnxruntime-common@1.18.0:
+ resolution: {integrity: sha512-lufrSzX6QdKrktAELG5x5VkBpapbCeS3dQwrXbN0eD9rHvU0yAWl7Ztju9FvgAKWvwd/teEKJNj3OwM6eTZh3Q==}
+
+ onnxruntime-web@1.18.0:
+ resolution: {integrity: sha512-o1UKj4ABIj1gmG7ae0RKJ3/GT+3yoF0RRpfDfeoe0huzRW4FDRLfbkDETmdFAvnJEXuYDE0YT+hhkia0352StQ==}
+
+ open@10.2.0:
+ resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==}
+ engines: {node: '>=18'}
+
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
+ orderedmap@2.1.1:
+ resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
+
+ own-keys@1.0.1:
+ resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
+ engines: {node: '>= 0.4'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ package-json-from-dist@1.0.1:
+ resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+
+ package-manager-detector@1.6.0:
+ resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ parse-ms@4.0.0:
+ resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==}
+ engines: {node: '>=18'}
+
+ pascalcase@0.1.1:
+ resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==}
+ engines: {node: '>=0.10.0'}
+
+ path-browserify@1.0.1:
+ resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
+
+ path-data-parser@0.1.0:
+ resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-key@4.0.0:
+ resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
+ engines: {node: '>=12'}
+
+ path-scurry@1.11.1:
+ resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+ engines: {node: '>=16 || 14 >=14.18'}
+
+ path-scurry@2.0.1:
+ resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==}
+ engines: {node: 20 || >=22}
+
+ pathe@0.2.0:
+ resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ pdfjs-dist@5.4.296:
+ resolution: {integrity: sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==}
+ engines: {node: '>=20.16.0 || >=22.3.0'}
+
+ perfect-debounce@1.0.0:
+ resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
+
+ perfect-debounce@2.0.0:
+ resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+
+ pinia@3.0.4:
+ resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==}
+ peerDependencies:
+ typescript: '>=4.5.0'
+ vue: ^3.5.11
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ pkg-types@1.3.1:
+ resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
+
+ pkg-types@2.3.0:
+ resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
+
+ platform@1.3.6:
+ resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}
+
+ pluralize@8.0.0:
+ resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
+ engines: {node: '>=4'}
+
+ plyr@3.8.3:
+ resolution: {integrity: sha512-0+iI5uw0WRvtKBpgPCkmQQv7ucHVQKTEo6UFJjgJ8cy/JZhy0dQqshHQVitHXV6l2O3MzhgnuvQ95VSkWcWeSw==}
+
+ points-on-curve@0.2.0:
+ resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
+
+ points-on-path@0.2.1:
+ resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==}
+
+ posix-character-classes@0.1.1:
+ resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==}
+ engines: {node: '>=0.10.0'}
+
+ possible-typed-array-names@1.1.0:
+ resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
+ engines: {node: '>= 0.4'}
+
+ postcss-modules-extract-imports@3.1.0:
+ resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==}
+ engines: {node: ^10 || ^12 || >= 14}
+ peerDependencies:
+ postcss: ^8.1.0
+
+ postcss-modules-local-by-default@4.2.0:
+ resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==}
+ engines: {node: ^10 || ^12 || >= 14}
+ peerDependencies:
+ postcss: ^8.1.0
+
+ postcss-modules-scope@3.2.1:
+ resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==}
+ engines: {node: ^10 || ^12 || >= 14}
+ peerDependencies:
+ postcss: ^8.1.0
+
+ postcss-modules-values@4.0.0:
+ resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==}
+ engines: {node: ^10 || ^12 || >= 14}
+ peerDependencies:
+ postcss: ^8.1.0
+
+ postcss-prefix-selector@1.16.1:
+ resolution: {integrity: sha512-Umxu+FvKMwlY6TyDzGFoSUnzW+NOfMBLyC1tAkIjgX+Z/qGspJeRjVC903D7mx7TuBpJlwti2ibXtWuA7fKMeQ==}
+ peerDependencies:
+ postcss: '>4 <9'
+
+ postcss-scss@4.0.9:
+ resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.4.29
+
+ postcss-selector-parser@7.1.1:
+ resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
+ engines: {node: '>=4'}
+
+ postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
+ postcss@5.2.18:
+ resolution: {integrity: sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==}
+ engines: {node: '>=0.12'}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ posthtml-parser@0.2.1:
+ resolution: {integrity: sha512-nPC53YMqJnc/+1x4fRYFfm81KV2V+G9NZY+hTohpYg64Ay7NemWWcV4UWuy/SgMupqQ3kJ88M/iRfZmSnxT+pw==}
+
+ posthtml-rename-id@1.0.12:
+ resolution: {integrity: sha512-UKXf9OF/no8WZo9edRzvuMenb6AD5hDLzIepJW+a4oJT+T/Lx7vfMYWT4aWlGNQh0WMhnUx1ipN9OkZ9q+ddEw==}
+
+ posthtml-render@1.4.0:
+ resolution: {integrity: sha512-W1779iVHGfq0Fvh2PROhCe2QhB8mEErgqzo1wpIt36tCgChafP+hbXIhLDOM8ePJrZcFs0vkNEtdibEWVqChqw==}
+ engines: {node: '>=10'}
+
+ posthtml-svg-mode@1.0.3:
+ resolution: {integrity: sha512-hEqw9NHZ9YgJ2/0G7CECOeuLQKZi8HjWLkBaSVtOWjygQ9ZD8P7tqeowYs7WrFdKsWEKG7o+IlsPY8jrr0CJpQ==}
+
+ posthtml@0.9.2:
+ resolution: {integrity: sha512-spBB5sgC4cv2YcW03f/IAUN1pgDJWNWD8FzkyY4mArLUMJW+KlQhlmUdKAHQuPfb00Jl5xIfImeOsf6YL8QK7Q==}
+ engines: {node: '>=0.10.0'}
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ prettier-linter-helpers@1.0.0:
+ resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
+ engines: {node: '>=6.0.0'}
+
+ prettier-plugin-json-sort@0.0.2:
+ resolution: {integrity: sha512-xd5VVfneeUBdWhTm5uh0rAto3qnkkosbte6poO5WVTZEAiQdndMQMRPv1SROXx968zfyAlS+Z+C6rkr4jbVOgg==}
+ peerDependencies:
+ prettier: '>=2.0.0'
+
+ prettier@3.3.3:
+ resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ prettier@3.7.4:
+ resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ pretty-bytes@6.1.1:
+ resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
+ engines: {node: ^14.13.1 || >=16.0.0}
+
+ pretty-ms@9.3.0:
+ resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}
+ engines: {node: '>=18'}
+
+ progress@2.0.3:
+ resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
+ engines: {node: '>=0.4.0'}
+
+ prompts@2.4.2:
+ resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
+ engines: {node: '>= 6'}
+
+ prosemirror-changeset@2.3.1:
+ resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==}
+
+ prosemirror-collab@1.3.1:
+ resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==}
+
+ prosemirror-commands@1.7.1:
+ resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==}
+
+ prosemirror-dropcursor@1.8.2:
+ resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==}
+
+ prosemirror-gapcursor@1.4.0:
+ resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==}
+
+ prosemirror-history@1.5.0:
+ resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==}
+
+ prosemirror-inputrules@1.5.1:
+ resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==}
+
+ prosemirror-keymap@1.2.3:
+ resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==}
+
+ prosemirror-markdown@1.13.2:
+ resolution: {integrity: sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==}
+
+ prosemirror-menu@1.2.5:
+ resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==}
+
+ prosemirror-model@1.25.4:
+ resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==}
+
+ prosemirror-schema-basic@1.2.4:
+ resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==}
+
+ prosemirror-schema-list@1.5.1:
+ resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==}
+
+ prosemirror-state@1.4.4:
+ resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==}
+
+ prosemirror-tables@1.8.4:
+ resolution: {integrity: sha512-CGr2BK5sLdZx+ARbeLO4HBZYa3qSG3FmwOVmzYs0Zp7n5SkrGqj+1CeNuubFNZEr64yMAQ20SanbFyIyHWZc8w==}
+
+ prosemirror-trailing-node@3.0.0:
+ resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==}
+ peerDependencies:
+ prosemirror-model: ^1.22.1
+ prosemirror-state: ^1.4.2
+ prosemirror-view: ^1.33.8
+
+ prosemirror-transform@1.10.5:
+ resolution: {integrity: sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw==}
+
+ prosemirror-view@1.41.4:
+ resolution: {integrity: sha512-WkKgnyjNncri03Gjaz3IFWvCAE94XoiEgvtr0/r2Xw7R8/IjK3sKLSiDoCHWcsXSAinVaKlGRZDvMCsF1kbzjA==}
+
+ protobufjs@7.5.4:
+ resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==}
+ engines: {node: '>=12.0.0'}
+
+ proxy-from-env@1.1.0:
+ resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+
+ punycode.js@2.3.1:
+ resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
+ engines: {node: '>=6'}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ pure-svg-code@1.0.6:
+ resolution: {integrity: sha512-uxq2BMTdnKW7jDghpLJrczCd9KDOdyghFtEEpfomqMJkUM83/N+W7sFJPJ3AxBf0mJ3xtxAycl6NW8p6F53jEw==}
+
+ qrcode-svg@1.1.0:
+ resolution: {integrity: sha512-XyQCIXux1zEIA3NPb0AeR8UMYvXZzWEhgdBgBjH9gO7M48H9uoHzviNz8pXw3UzrAcxRRRn9gxHewAVK7bn9qw==}
+ hasBin: true
+
+ qs@6.14.0:
+ resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
+ engines: {node: '>=0.6'}
+
+ quansync@0.2.11:
+ resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
+
+ quansync@1.0.0:
+ resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==}
+
+ query-string@4.3.4:
+ resolution: {integrity: sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==}
+ engines: {node: '>=0.10.0'}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ rangetouch@2.0.1:
+ resolution: {integrity: sha512-sln+pNSc8NGaHoLzwNBssFSf/rSYkqeBXzX1AtJlkJiUaVSJSbRAWJk+4omsXkN+EJalzkZhWQ3th1m0FpR5xA==}
+
+ rate-limiter-flexible@5.0.5:
+ resolution: {integrity: sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ==}
+
+ rc9@2.1.2:
+ resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
+
+ rd@2.0.1:
+ resolution: {integrity: sha512-/XdKU4UazUZTXFmI0dpABt8jSXPWcEyaGdk340KdHnsEOdkTctlX23aAK7ChQDn39YGNlAJr1M5uvaKt4QnpNw==}
+
+ readable-stream@3.6.2:
+ resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
+ engines: {node: '>= 6'}
+
+ readdirp@3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+
+ readdirp@4.1.2:
+ resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
+ engines: {node: '>= 14.18.0'}
+
+ readdirp@5.0.0:
+ resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
+ engines: {node: '>= 20.19.0'}
+
+ recast@0.23.9:
+ resolution: {integrity: sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==}
+ engines: {node: '>= 4'}
+
+ reflect.getprototypeof@1.0.10:
+ resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
+ engines: {node: '>= 0.4'}
+
+ regex-not@1.0.2:
+ resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==}
+ engines: {node: '>=0.10.0'}
+
+ regexp-tree@0.1.27:
+ resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==}
+ hasBin: true
+
+ regexp.prototype.flags@1.5.4:
+ resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
+ engines: {node: '>= 0.4'}
+
+ regjsparser@0.13.0:
+ resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==}
+ hasBin: true
+
+ repeat-element@1.1.4:
+ resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==}
+ engines: {node: '>=0.10.0'}
+
+ repeat-string@1.6.1:
+ resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
+ engines: {node: '>=0.10'}
+
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ resolve-url@0.2.1:
+ resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==}
+ deprecated: https://github.com/lydell/resolve-url#deprecated
+
+ ret@0.1.15:
+ resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==}
+ engines: {node: '>=0.12'}
+
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ rfdc@1.4.1:
+ resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
+
+ rimraf@6.1.2:
+ resolution: {integrity: sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==}
+ engines: {node: 20 || >=22}
+ hasBin: true
+
+ robust-predicates@3.0.2:
+ resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
+
+ rollup@4.54.0:
+ resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ rope-sequence@1.3.4:
+ resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
+
+ roughjs@4.6.6:
+ resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==}
+
+ run-applescript@7.1.0:
+ resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
+ engines: {node: '>=18'}
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ rw@1.3.3:
+ resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
+
+ safe-array-concat@1.1.3:
+ resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
+ engines: {node: '>=0.4'}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ safe-push-apply@1.0.0:
+ resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
+ engines: {node: '>= 0.4'}
+
+ safe-regex-test@1.1.0:
+ resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
+ engines: {node: '>= 0.4'}
+
+ safe-regex@1.1.0:
+ resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ sass@1.97.1:
+ resolution: {integrity: sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+
+ seemly@0.3.10:
+ resolution: {integrity: sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==}
+
+ select@1.1.2:
+ resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.7.2:
+ resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ semver@7.7.3:
+ resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ set-function-length@1.2.2:
+ resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+ engines: {node: '>= 0.4'}
+
+ set-function-name@2.0.2:
+ resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+ engines: {node: '>= 0.4'}
+
+ set-proto@1.0.0:
+ resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
+ engines: {node: '>= 0.4'}
+
+ set-value@2.0.1:
+ resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
+ engines: {node: '>=0.10.0'}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ side-channel-list@1.0.0:
+ resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-map@1.0.1:
+ resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-weakmap@1.0.2:
+ resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+ engines: {node: '>= 0.4'}
+
+ side-channel@1.1.0:
+ resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
+ engines: {node: '>= 0.4'}
+
+ sign-canvas-plus@2.0.3:
+ resolution: {integrity: sha512-EHcE9Z8SATs5UvQC/G5zLGmdznlepaar552i/w6W81+893NizjNEMUxXPaB46fEpwq/M+8T1C4BnTSk4NtpJsA==}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ simple-git-hooks@2.13.1:
+ resolution: {integrity: sha512-WszCLXwT4h2k1ufIXAgsbiTOazqqevFCIncOuUBZJ91DdvWcC5+OFkluWRQPrcuSYd8fjq+o2y1QfWqYMoAToQ==}
+ hasBin: true
+
+ simplebar-core@1.3.2:
+ resolution: {integrity: sha512-qKgTTuTqapjsFGkNhCjyPhysnbZGpQqNmjk0nOYjFN5ordC/Wjvg+RbYCyMSnW60l/Z0ZS82GbNltly6PMUH1w==}
+
+ simplebar-vue@2.4.2:
+ resolution: {integrity: sha512-QQKzQ6wQdld5Nwop4Bfd7hitzmWqwfhRaJbtgan/xu7V6WcvspXrAH9fapJNH2Lqny34P43m4rRREp97NcdiiQ==}
+ peerDependencies:
+ vue: '>=2.5.17'
+
+ sirv@3.0.2:
+ resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==}
+ engines: {node: '>=18'}
+
+ sisteransi@1.0.5:
+ resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
+
+ smooth-signature@1.1.0:
+ resolution: {integrity: sha512-wXBsbWmGxtNPftE7RoCWl5VrFkTT2hxT6/rJHQLOMviVeHB3B38+rh7F41Vd1Zwy9P+568ZDyutN/GWk1VfD+w==}
+
+ snapdragon-node@2.1.1:
+ resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==}
+ engines: {node: '>=0.10.0'}
+
+ snapdragon-util@3.0.1:
+ resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==}
+ engines: {node: '>=0.10.0'}
+
+ snapdragon@0.8.2:
+ resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==}
+ engines: {node: '>=0.10.0'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ source-map-resolve@0.5.3:
+ resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==}
+ deprecated: See https://github.com/lydell/source-map-resolve#deprecated
+
+ source-map-url@0.4.1:
+ resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==}
+ deprecated: See https://github.com/lydell/source-map-url#deprecated
+
+ source-map@0.5.7:
+ resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
+ engines: {node: '>=0.10.0'}
+
+ source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+
+ speakingurl@14.0.1:
+ resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
+ engines: {node: '>=0.10.0'}
+
+ split-string@3.1.0:
+ resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==}
+ engines: {node: '>=0.10.0'}
+
+ stable-hash-x@0.2.0:
+ resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==}
+ engines: {node: '>=12.0.0'}
+
+ stable@0.1.8:
+ resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==}
+ deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
+
+ static-extend@0.1.2:
+ resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
+ engines: {node: '>=0.10.0'}
+
+ stop-iteration-iterator@1.1.0:
+ resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
+ engines: {node: '>= 0.4'}
+
+ streamsaver@2.0.6:
+ resolution: {integrity: sha512-LK4e7TfCV8HzuM0PKXuVUfKyCB1FtT9L0EGxsFk5Up8njj0bXK8pJM9+Wq2Nya7/jslmCQwRK39LFm55h7NBTw==}
+
+ strict-uri-encode@1.1.0:
+ resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==}
+ engines: {node: '>=0.10.0'}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string-width@5.1.2:
+ resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+ engines: {node: '>=12'}
+
+ string.prototype.trim@1.2.10:
+ resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimend@1.0.9:
+ resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimstart@1.0.8:
+ resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
+ engines: {node: '>= 0.4'}
+
+ string_decoder@1.3.0:
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+
+ strip-ansi@3.0.1:
+ resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
+ engines: {node: '>=0.10.0'}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-ansi@7.1.2:
+ resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
+ engines: {node: '>=12'}
+
+ strip-final-newline@4.0.0:
+ resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
+ engines: {node: '>=18'}
+
+ strip-indent@4.1.1:
+ resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==}
+ engines: {node: '>=12'}
+
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ stylis@4.3.6:
+ resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
+
+ superjson@2.2.6:
+ resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==}
+ engines: {node: '>=16'}
+
+ supports-color@2.0.0:
+ resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==}
+ engines: {node: '>=0.8.0'}
+
+ supports-color@3.2.3:
+ resolution: {integrity: sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==}
+ engines: {node: '>=0.8.0'}
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ svelte-eslint-parser@1.4.1:
+ resolution: {integrity: sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.24.0}
+ peerDependencies:
+ svelte: ^3.37.0 || ^4.0.0 || ^5.0.0
+ peerDependenciesMeta:
+ svelte:
+ optional: true
+
+ svg-baker@1.7.0:
+ resolution: {integrity: sha512-nibslMbkXOIkqKVrfcncwha45f97fGuAOn1G99YwnwTj8kF9YiM6XexPcUso97NxOm6GsP0SIvYVIosBis1xLg==}
+
+ svg64@2.0.0:
+ resolution: {integrity: sha512-EVgAisxMctUNDSjKGFcx4tkcFrvdqtLIy/MdbBdqcwfpPwsBcwoSKQi+WYoc82c4XWFNVVIwpCup3rpY+M9KJw==}
+
+ svgo@2.8.0:
+ resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==}
+ engines: {node: '>=10.13.0'}
+ hasBin: true
+
+ synckit@0.11.11:
+ resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+
+ tailwind-merge@3.4.0:
+ resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
+
+ tapable@2.3.0:
+ resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
+ engines: {node: '>=6'}
+
+ tiny-emitter@2.1.0:
+ resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
+
+ tiny-invariant@1.3.3:
+ resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
+
+ tinyexec@1.0.2:
+ resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
+ engines: {node: '>=18'}
+
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
+ tippy.js@6.3.7:
+ resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==}
+
+ to-object-path@0.3.0:
+ resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==}
+ engines: {node: '>=0.10.0'}
+
+ to-regex-range@2.1.1:
+ resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==}
+ engines: {node: '>=0.10.0'}
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ to-regex@3.0.2:
+ resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==}
+ engines: {node: '>=0.10.0'}
+
+ totalist@3.0.1:
+ resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
+ engines: {node: '>=6'}
+
+ traverse@0.6.11:
+ resolution: {integrity: sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==}
+ engines: {node: '>= 0.4'}
+
+ treemate@0.3.11:
+ resolution: {integrity: sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==}
+
+ ts-api-utils@2.1.0:
+ resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ typescript: '>=4.8.4'
+
+ ts-declaration-location@1.0.7:
+ resolution: {integrity: sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==}
+ peerDependencies:
+ typescript: '>=4.0.0'
+
+ ts-dedent@2.2.0:
+ resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
+ engines: {node: '>=6.10'}
+
+ tslib@2.3.0:
+ resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ tsx@4.21.0:
+ resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
+ typed-array-buffer@1.0.3:
+ resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-length@1.0.3:
+ resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-offset@1.0.4:
+ resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-length@1.0.7:
+ resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
+ engines: {node: '>= 0.4'}
+
+ typed-css-modules@0.9.1:
+ resolution: {integrity: sha512-W2HWKncdKd+bLWsnuWB2EyuQBzZ7KJ9Byr/67KLiiyGegcN52rOveun9JR8yAvuL5IXunRMxt0eORMtAUj5bmA==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
+ typedarray.prototype.slice@1.0.5:
+ resolution: {integrity: sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==}
+ engines: {node: '>= 0.4'}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ uc.micro@2.1.0:
+ resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
+
+ ufo@1.6.1:
+ resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
+
+ unbox-primitive@1.1.0:
+ resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
+ engines: {node: '>= 0.4'}
+
+ unconfig-core@7.4.2:
+ resolution: {integrity: sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg==}
+
+ unconfig@7.4.2:
+ resolution: {integrity: sha512-nrMlWRQ1xdTjSnSUqvYqJzbTBFugoqHobQj58B2bc8qxHKBBHMNNsWQFP3Cd3/JZK907voM2geYPWqD4VK3MPQ==}
+
+ undici-types@7.16.0:
+ resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+
+ unicorn-magic@0.3.0:
+ resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
+ engines: {node: '>=18'}
+
+ union-value@1.0.1:
+ resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==}
+ engines: {node: '>=0.10.0'}
+
+ universalify@2.0.1:
+ resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+ engines: {node: '>= 10.0.0'}
+
+ unplugin-icons@22.5.0:
+ resolution: {integrity: sha512-MBlMtT5RuMYZy4TZgqUL2OTtOdTUVsS1Mhj6G1pEzMlFJlEnq6mhUfoIt45gBWxHcsOdXJDWLg3pRZ+YmvAVWQ==}
+ peerDependencies:
+ '@svgr/core': '>=7.0.0'
+ '@svgx/core': ^1.0.1
+ '@vue/compiler-sfc': ^3.0.2 || ^2.7.0
+ svelte: ^3.0.0 || ^4.0.0 || ^5.0.0
+ vue-template-compiler: ^2.6.12
+ vue-template-es2015-compiler: ^1.9.0
+ peerDependenciesMeta:
+ '@svgr/core':
+ optional: true
+ '@svgx/core':
+ optional: true
+ '@vue/compiler-sfc':
+ optional: true
+ svelte:
+ optional: true
+ vue-template-compiler:
+ optional: true
+ vue-template-es2015-compiler:
+ optional: true
+
+ unplugin-utils@0.3.1:
+ resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==}
+ engines: {node: '>=20.19.0'}
+
+ unplugin-vue-components@30.0.0:
+ resolution: {integrity: sha512-4qVE/lwCgmdPTp6h0qsRN2u642tt4boBQtcpn4wQcWZAsr8TQwq+SPT3NDu/6kBFxzo/sSEK4ioXhOOBrXc3iw==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/parser': ^7.15.8
+ '@nuxt/kit': ^3.2.2 || ^4.0.0
+ vue: 2 || 3
+ peerDependenciesMeta:
+ '@babel/parser':
+ optional: true
+ '@nuxt/kit':
+ optional: true
+
+ unplugin@1.12.0:
+ resolution: {integrity: sha512-KeczzHl2sATPQUx1gzo+EnUkmN4VmGBYRRVOZSGvGITE9rGHRDGqft6ONceP3vgXcyJ2XjX5axG5jMWUwNCYLw==}
+ engines: {node: '>=14.0.0'}
+
+ unplugin@2.3.11:
+ resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==}
+ engines: {node: '>=18.12.0'}
+
+ unrs-resolver@1.11.1:
+ resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}
+
+ unset-value@1.0.0:
+ resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==}
+ engines: {node: '>=0.10.0'}
+
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+ urix@0.1.0:
+ resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==}
+ deprecated: Please see https://github.com/lydell/urix#deprecated
+
+ url-polyfill@1.1.14:
+ resolution: {integrity: sha512-p4f3TTAG6ADVF3mwbXw7hGw+QJyw5CnNGvYh5fCuQQZIiuKUswqcznyV3pGDP9j0TSmC4UvRKm8kl1QsX1diiQ==}
+
+ use@3.1.1:
+ resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
+ engines: {node: '>=0.10.0'}
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ uuid@11.1.0:
+ resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
+ hasBin: true
+
+ vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+
+ vdirs@0.1.8:
+ resolution: {integrity: sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==}
+ peerDependencies:
+ vue: ^3.0.11
+
+ vite-dev-rpc@1.1.0:
+ resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==}
+ peerDependencies:
+ vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0
+
+ vite-hot-client@2.1.0:
+ resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==}
+ peerDependencies:
+ vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
+
+ vite-plugin-inspect@11.3.3:
+ resolution: {integrity: sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@nuxt/kit': '*'
+ vite: ^6.0.0 || ^7.0.0-0
+ peerDependenciesMeta:
+ '@nuxt/kit':
+ optional: true
+
+ vite-plugin-progress@0.0.7:
+ resolution: {integrity: sha512-zyvKdcc/X+6hnw3J1HVV1TKrlFKC4Rh8GnDnWG/2qhRXjqytTcM++xZ+SAPnoDsSyWl8O93ymK0wZRgHAoglEQ==}
+ engines: {node: '>=14', pnpm: '>=7.0.0'}
+ peerDependencies:
+ vite: '>2.0.0-0'
+
+ vite-plugin-svg-icons@2.0.1:
+ resolution: {integrity: sha512-6ktD+DhV6Rz3VtedYvBKKVA2eXF+sAQVaKkKLDSqGUfnhqXl3bj5PPkVTl3VexfTuZy66PmINi8Q6eFnVfRUmA==}
+ peerDependencies:
+ vite: '>=2.0.0'
+
+ vite-plugin-vue-devtools@8.0.5:
+ resolution: {integrity: sha512-p619BlKFOqQXJ6uDWS1vUPQzuJOD6xJTfftj57JXBGoBD/yeQCowR7pnWcr/FEX4/HVkFbreI6w2uuGBmQOh6A==}
+ engines: {node: '>=v14.21.3'}
+ peerDependencies:
+ vite: ^6.0.0 || ^7.0.0-0
+
+ vite-plugin-vue-inspector@5.3.2:
+ resolution: {integrity: sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==}
+ peerDependencies:
+ vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
+
+ vite@7.3.0:
+ resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ lightningcss: ^1.21.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ vooks@0.2.12:
+ resolution: {integrity: sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==}
+ peerDependencies:
+ vue: ^3.0.0
+
+ vscode-jsonrpc@8.2.0:
+ resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==}
+ engines: {node: '>=14.0.0'}
+
+ vscode-languageserver-protocol@3.17.5:
+ resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==}
+
+ vscode-languageserver-textdocument@1.0.12:
+ resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==}
+
+ vscode-languageserver-types@3.17.5:
+ resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==}
+
+ vscode-languageserver@9.0.1:
+ resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==}
+ hasBin: true
+
+ vscode-uri@3.0.8:
+ resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
+
+ vscode-uri@3.1.0:
+ resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
+
+ vue-advanced-cropper@2.8.9:
+ resolution: {integrity: sha512-1jc5gO674kVGpJKekoaol6ZlwaF5VYDLSBwBOUpViW0IOrrRsyLw6XNszjEqgbavvqinlKNS6Kqlom3B5M72Tw==}
+ engines: {node: '>=8', npm: '>=5'}
+ peerDependencies:
+ vue: ^3.0.0
+
+ vue-demi@0.13.11:
+ resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==}
+ engines: {node: '>=12'}
+ hasBin: true
+ peerDependencies:
+ '@vue/composition-api': ^1.0.0-rc.1
+ vue: ^3.0.0-0 || ^2.6.0
+ peerDependenciesMeta:
+ '@vue/composition-api':
+ optional: true
+
+ vue-demi@0.14.10:
+ resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
+ engines: {node: '>=12'}
+ hasBin: true
+ peerDependencies:
+ '@vue/composition-api': ^1.0.0-rc.1
+ vue: ^3.0.0-0 || ^2.6.0
+ peerDependenciesMeta:
+ '@vue/composition-api':
+ optional: true
+
+ vue-draggable-plus@0.6.0:
+ resolution: {integrity: sha512-G5TSfHrt9tX9EjdG49InoFJbt2NYk0h3kgjgKxkFWr3ulIUays0oFObr5KZ8qzD4+QnhtALiRwIqY6qul4egqw==}
+ peerDependencies:
+ '@types/sortablejs': ^1.15.0
+ '@vue/composition-api': '*'
+ peerDependenciesMeta:
+ '@vue/composition-api':
+ optional: true
+
+ vue-eslint-parser@10.2.0:
+ resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+
+ vue-flow-layout@0.2.0:
+ resolution: {integrity: sha512-zKgsWWkXq0xrus7H4Mc+uFs1ESrmdTXlO0YNbR6wMdPaFvosL3fMB8N7uTV308UhGy9UvTrGhIY7mVz9eN+L0Q==}
+
+ vue-i18n@10.0.8:
+ resolution: {integrity: sha512-mIjy4utxMz9lMMo6G9vYePv7gUFt4ztOMhY9/4czDJxZ26xPeJ49MAGa9wBAE3XuXbYCrtVPmPxNjej7JJJkZQ==}
+ engines: {node: '>= 16'}
+ peerDependencies:
+ vue: ^3.0.0
+
+ vue-i18n@11.2.7:
+ resolution: {integrity: sha512-LPv8bAY5OA0UvFEXl4vBQOBqJzRrlExy92tWgRuwW7tbykHf7CH71G2Y4TM2OwGcIS4+hyqKHS2EVBqaYwPY9Q==}
+ engines: {node: '>= 16'}
+ peerDependencies:
+ vue: ^3.0.0
+
+ vue-router@4.6.4:
+ resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==}
+ peerDependencies:
+ vue: ^3.5.0
+
+ vue-tsc@3.2.1:
+ resolution: {integrity: sha512-I23Rk8dkQfmcSbxDO0dmg9ioMLjKA1pjlU3Lz6Jfk2pMGu3Uryu9810XkcZH24IzPbhzPCnkKo2rEMRX0skSrw==}
+ hasBin: true
+ peerDependencies:
+ typescript: '>=5.0.0'
+
+ vue@3.5.26:
+ resolution: {integrity: sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ vueuc@0.4.65:
+ resolution: {integrity: sha512-lXuMl+8gsBmruudfxnMF9HW4be8rFziylXFu1VHVNbLVhRTXXV4njvpRuJapD/8q+oFEMSfQMH16E/85VoWRyQ==}
+ peerDependencies:
+ vue: ^3.0.11
+
+ w3c-keyname@2.2.8:
+ resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
+
+ webpack-sources@3.3.3:
+ resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==}
+ engines: {node: '>=10.13.0'}
+
+ webpack-virtual-modules@0.6.2:
+ resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
+
+ which-boxed-primitive@1.1.1:
+ resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
+ engines: {node: '>= 0.4'}
+
+ which-builtin-type@1.2.1:
+ resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
+ engines: {node: '>= 0.4'}
+
+ which-collection@1.0.2:
+ resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
+ engines: {node: '>= 0.4'}
+
+ which-typed-array@1.1.19:
+ resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
+ engines: {node: '>= 0.4'}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrap-ansi@8.1.0:
+ resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+ engines: {node: '>=12'}
+
+ wsl-utils@0.1.0:
+ resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==}
+ engines: {node: '>=18'}
+
+ xml-name-validator@4.0.0:
+ resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
+ engines: {node: '>=12'}
+
+ y-prosemirror@1.3.7:
+ resolution: {integrity: sha512-NpM99WSdD4Fx4if5xOMDpPtU3oAmTSjlzh5U4353ABbRHl1HtAFUx6HlebLZfyFxXN9jzKMDkVbcRjqOZVkYQg==}
+ engines: {node: '>=16.0.0', npm: '>=8.0.0'}
+ peerDependencies:
+ prosemirror-model: ^1.7.1
+ prosemirror-state: ^1.2.3
+ prosemirror-view: ^1.9.10
+ y-protocols: ^1.0.1
+ yjs: ^13.5.38
+
+ y-protocols@1.0.7:
+ resolution: {integrity: sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==}
+ engines: {node: '>=16.0.0', npm: '>=8.0.0'}
+ peerDependencies:
+ yjs: ^13.0.0
+
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yaml@2.8.2:
+ resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
+
+ yjs@13.6.28:
+ resolution: {integrity: sha512-EgnDOXs8+hBVm6mq3/S89Kiwzh5JRbn7w2wXwbrMRyKy/8dOFsLvuIfC+x19ZdtaDc0tA9rQmdZzbqqNHG44wA==}
+ engines: {node: '>=16.0.0', npm: '>=8.0.0'}
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+ yoctocolors@2.1.2:
+ resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
+ engines: {node: '>=18'}
+
+ zod@3.25.76:
+ resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+
+ zod@4.2.1:
+ resolution: {integrity: sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==}
+
+ zrender@5.6.1:
+ resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==}
+
+ zrender@6.0.0:
+ resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==}
+
+snapshots:
+
+ '@alova/mock@2.0.18(alova@3.4.1)':
+ dependencies:
+ '@alova/shared': 1.3.2
+ alova: 3.4.1
+
+ '@alova/shared@1.3.2': {}
+
+ '@antfu/eslint-define-config@1.23.0-2': {}
+
+ '@antfu/install-pkg@1.1.0':
+ dependencies:
+ package-manager-detector: 1.6.0
+ tinyexec: 1.0.2
+
+ '@babel/code-frame@7.27.1':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.28.5': {}
+
+ '@babel/core@7.28.5':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.5
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5)
+ '@babel/helpers': 7.28.4
+ '@babel/parser': 7.28.5
+ '@babel/template': 7.27.2
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.28.5':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-annotate-as-pure@7.27.3':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@babel/helper-compilation-targets@7.27.2':
+ dependencies:
+ '@babel/compat-data': 7.28.5
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.28.1
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-member-expression-to-functions': 7.28.5
+ '@babel/helper-optimise-call-expression': 7.27.1
+ '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5)
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/traverse': 7.28.5
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-member-expression-to-functions@7.28.5':
+ dependencies:
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-imports@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-optimise-call-expression@7.27.1':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@babel/helper-plugin-utils@7.27.1': {}
+
+ '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-member-expression-to-functions': 7.28.5
+ '@babel/helper-optimise-call-expression': 7.27.1
+ '@babel/traverse': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.28.5': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.28.4':
+ dependencies:
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.5
+
+ '@babel/parser@7.28.5':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5)
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.5)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5)
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/template@7.27.2':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+
+ '@babel/traverse@7.28.5':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.5
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.28.5
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.5
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.28.5':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
+ '@better-scroll/core@2.5.1':
+ dependencies:
+ '@better-scroll/shared-utils': 2.5.1
+
+ '@better-scroll/shared-utils@2.5.1': {}
+
+ '@braintree/sanitize-url@7.1.1': {}
+
+ '@chevrotain/cst-dts-gen@11.0.3':
+ dependencies:
+ '@chevrotain/gast': 11.0.3
+ '@chevrotain/types': 11.0.3
+ lodash-es: 4.17.21
+
+ '@chevrotain/gast@11.0.3':
+ dependencies:
+ '@chevrotain/types': 11.0.3
+ lodash-es: 4.17.21
+
+ '@chevrotain/regexp-to-ast@11.0.3': {}
+
+ '@chevrotain/types@11.0.3': {}
+
+ '@chevrotain/utils@11.0.3': {}
+
+ '@css-render/plugin-bem@0.15.14(css-render@0.15.14)':
+ dependencies:
+ css-render: 0.15.14
+
+ '@css-render/vue3-ssr@0.15.14(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ vue: 3.5.26(typescript@5.9.3)
+
+ '@elegant-router/core@0.3.8':
+ dependencies:
+ chokidar: 3.6.0
+ consola: 3.2.3
+ fast-glob: 3.3.2
+ kolorist: 1.8.0
+ micromatch: 4.0.7
+
+ '@elegant-router/vue@0.3.8':
+ dependencies:
+ '@elegant-router/core': 0.3.8
+ consola: 3.2.3
+ kolorist: 1.8.0
+ magic-string: 0.30.11
+ magicast: 0.3.4
+ prettier: 3.3.3
+ recast: 0.23.9
+ unplugin: 1.12.0
+
+ '@emnapi/core@1.7.1':
+ dependencies:
+ '@emnapi/wasi-threads': 1.1.0
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/runtime@1.7.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/wasi-threads@1.1.0':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@emotion/hash@0.8.0': {}
+
+ '@esbuild/aix-ppc64@0.27.2':
+ optional: true
+
+ '@esbuild/android-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/android-arm@0.27.2':
+ optional: true
+
+ '@esbuild/android-x64@0.27.2':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/darwin-x64@0.27.2':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.27.2':
+ optional: true
+
+ '@esbuild/linux-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/linux-arm@0.27.2':
+ optional: true
+
+ '@esbuild/linux-ia32@0.27.2':
+ optional: true
+
+ '@esbuild/linux-loong64@0.27.2':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.27.2':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.27.2':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.27.2':
+ optional: true
+
+ '@esbuild/linux-s390x@0.27.2':
+ optional: true
+
+ '@esbuild/linux-x64@0.27.2':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.27.2':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.27.2':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/sunos-x64@0.27.2':
+ optional: true
+
+ '@esbuild/win32-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/win32-ia32@0.27.2':
+ optional: true
+
+ '@esbuild/win32-x64@0.27.2':
+ optional: true
+
+ '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2(jiti@2.6.1))':
+ dependencies:
+ eslint: 9.39.2(jiti@2.6.1)
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.2': {}
+
+ '@eslint/compat@1.4.1(eslint@9.39.2(jiti@2.6.1))':
+ dependencies:
+ '@eslint/core': 0.17.0
+ optionalDependencies:
+ eslint: 9.39.2(jiti@2.6.1)
+
+ '@eslint/config-array@0.21.1':
+ dependencies:
+ '@eslint/object-schema': 2.1.7
+ debug: 4.4.3
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/config-helpers@0.4.2':
+ dependencies:
+ '@eslint/core': 0.17.0
+
+ '@eslint/core@0.17.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/eslintrc@3.3.3':
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.4.3
+ espree: 10.4.0
+ globals: 14.0.0
+ ignore: 5.3.2
+ import-fresh: 3.3.1
+ js-yaml: 4.1.1
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@9.39.2': {}
+
+ '@eslint/object-schema@2.1.7': {}
+
+ '@eslint/plugin-kit@0.4.1':
+ dependencies:
+ '@eslint/core': 0.17.0
+ levn: 0.4.1
+
+ '@humanfs/core@0.19.1': {}
+
+ '@humanfs/node@0.16.7':
+ dependencies:
+ '@humanfs/core': 0.19.1
+ '@humanwhocodes/retry': 0.4.3
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/retry@0.4.3': {}
+
+ '@iconify/json@2.2.417':
+ dependencies:
+ '@iconify/types': 2.0.0
+ pathe: 2.0.3
+
+ '@iconify/types@2.0.0': {}
+
+ '@iconify/utils@3.1.0':
+ dependencies:
+ '@antfu/install-pkg': 1.1.0
+ '@iconify/types': 2.0.0
+ mlly: 1.8.0
+
+ '@iconify/vue@5.0.0(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@iconify/types': 2.0.0
+ vue: 3.5.26(typescript@5.9.3)
+
+ '@imgly/background-removal@1.5.5':
+ dependencies:
+ '@types/lodash-es': 4.17.12
+ '@types/ndarray': 1.0.14
+ '@types/node': 20.3.3
+ lodash-es: 4.17.22
+ ndarray: 1.0.19
+ onnxruntime-web: 1.18.0
+ zod: 3.25.76
+
+ '@intlify/core-base@10.0.8':
+ dependencies:
+ '@intlify/message-compiler': 10.0.8
+ '@intlify/shared': 10.0.8
+
+ '@intlify/core-base@11.2.7':
+ dependencies:
+ '@intlify/message-compiler': 11.2.7
+ '@intlify/shared': 11.2.7
+
+ '@intlify/message-compiler@10.0.8':
+ dependencies:
+ '@intlify/shared': 10.0.8
+ source-map-js: 1.2.1
+
+ '@intlify/message-compiler@11.2.7':
+ dependencies:
+ '@intlify/shared': 11.2.7
+ source-map-js: 1.2.1
+
+ '@intlify/shared@10.0.8': {}
+
+ '@intlify/shared@11.2.7': {}
+
+ '@isaacs/balanced-match@4.0.1': {}
+
+ '@isaacs/brace-expansion@5.0.0':
+ dependencies:
+ '@isaacs/balanced-match': 4.0.1
+
+ '@isaacs/cliui@8.0.2':
+ dependencies:
+ string-width: 5.1.2
+ string-width-cjs: string-width@4.2.3
+ strip-ansi: 7.1.2
+ strip-ansi-cjs: strip-ansi@6.0.1
+ wrap-ansi: 8.1.0
+ wrap-ansi-cjs: wrap-ansi@7.0.0
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@juggle/resize-observer@3.4.0': {}
+
+ '@mermaid-js/parser@0.6.3':
+ dependencies:
+ langium: 3.3.1
+
+ '@napi-rs/canvas-android-arm64@0.1.86':
+ optional: true
+
+ '@napi-rs/canvas-darwin-arm64@0.1.86':
+ optional: true
+
+ '@napi-rs/canvas-darwin-x64@0.1.86':
+ optional: true
+
+ '@napi-rs/canvas-linux-arm-gnueabihf@0.1.86':
+ optional: true
+
+ '@napi-rs/canvas-linux-arm64-gnu@0.1.86':
+ optional: true
+
+ '@napi-rs/canvas-linux-arm64-musl@0.1.86':
+ optional: true
+
+ '@napi-rs/canvas-linux-riscv64-gnu@0.1.86':
+ optional: true
+
+ '@napi-rs/canvas-linux-x64-gnu@0.1.86':
+ optional: true
+
+ '@napi-rs/canvas-linux-x64-musl@0.1.86':
+ optional: true
+
+ '@napi-rs/canvas-win32-arm64-msvc@0.1.86':
+ optional: true
+
+ '@napi-rs/canvas-win32-x64-msvc@0.1.86':
+ optional: true
+
+ '@napi-rs/canvas@0.1.86':
+ optionalDependencies:
+ '@napi-rs/canvas-android-arm64': 0.1.86
+ '@napi-rs/canvas-darwin-arm64': 0.1.86
+ '@napi-rs/canvas-darwin-x64': 0.1.86
+ '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.86
+ '@napi-rs/canvas-linux-arm64-gnu': 0.1.86
+ '@napi-rs/canvas-linux-arm64-musl': 0.1.86
+ '@napi-rs/canvas-linux-riscv64-gnu': 0.1.86
+ '@napi-rs/canvas-linux-x64-gnu': 0.1.86
+ '@napi-rs/canvas-linux-x64-musl': 0.1.86
+ '@napi-rs/canvas-win32-arm64-msvc': 0.1.86
+ '@napi-rs/canvas-win32-x64-msvc': 0.1.86
+ optional: true
+
+ '@napi-rs/wasm-runtime@0.2.12':
+ dependencies:
+ '@emnapi/core': 1.7.1
+ '@emnapi/runtime': 1.7.1
+ '@tybys/wasm-util': 0.10.1
+ optional: true
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.19.1
+
+ '@parcel/watcher-android-arm64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-darwin-arm64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-darwin-x64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-freebsd-x64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm-glibc@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm-musl@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-musl@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-x64-glibc@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-x64-musl@2.5.1':
+ optional: true
+
+ '@parcel/watcher-win32-arm64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-win32-ia32@2.5.1':
+ optional: true
+
+ '@parcel/watcher-win32-x64@2.5.1':
+ optional: true
+
+ '@parcel/watcher@2.5.1':
+ dependencies:
+ detect-libc: 1.0.3
+ is-glob: 4.0.3
+ micromatch: 4.0.8
+ node-addon-api: 7.1.1
+ optionalDependencies:
+ '@parcel/watcher-android-arm64': 2.5.1
+ '@parcel/watcher-darwin-arm64': 2.5.1
+ '@parcel/watcher-darwin-x64': 2.5.1
+ '@parcel/watcher-freebsd-x64': 2.5.1
+ '@parcel/watcher-linux-arm-glibc': 2.5.1
+ '@parcel/watcher-linux-arm-musl': 2.5.1
+ '@parcel/watcher-linux-arm64-glibc': 2.5.1
+ '@parcel/watcher-linux-arm64-musl': 2.5.1
+ '@parcel/watcher-linux-x64-glibc': 2.5.1
+ '@parcel/watcher-linux-x64-musl': 2.5.1
+ '@parcel/watcher-win32-arm64': 2.5.1
+ '@parcel/watcher-win32-ia32': 2.5.1
+ '@parcel/watcher-win32-x64': 2.5.1
+ optional: true
+
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
+ '@pkgr/core@0.2.9': {}
+
+ '@polka/url@1.0.0-next.29': {}
+
+ '@popperjs/core@2.11.8': {}
+
+ '@protobufjs/aspromise@1.1.2': {}
+
+ '@protobufjs/base64@1.1.2': {}
+
+ '@protobufjs/codegen@2.0.4': {}
+
+ '@protobufjs/eventemitter@1.1.0': {}
+
+ '@protobufjs/fetch@1.1.0':
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+ '@protobufjs/inquire': 1.1.0
+
+ '@protobufjs/float@1.0.2': {}
+
+ '@protobufjs/inquire@1.1.0': {}
+
+ '@protobufjs/path@1.1.2': {}
+
+ '@protobufjs/pool@1.1.0': {}
+
+ '@protobufjs/utf8@1.1.0': {}
+
+ '@quansync/fs@1.0.0':
+ dependencies:
+ quansync: 1.0.0
+
+ '@remirror/core-constants@3.0.0': {}
+
+ '@rolldown/pluginutils@1.0.0-beta.53': {}
+
+ '@rolldown/pluginutils@1.0.0-beta.56': {}
+
+ '@rollup/rollup-android-arm-eabi@4.54.0':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.54.0':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.54.0':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.54.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.54.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.54.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.54.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.54.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.54.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.54.0':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.54.0':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.54.0':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.54.0':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.54.0':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.54.0':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.54.0':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.54.0':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.54.0':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.54.0':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.54.0':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.54.0':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.54.0':
+ optional: true
+
+ '@sec-ant/readable-stream@0.4.1': {}
+
+ '@sindresorhus/merge-streams@4.0.0': {}
+
+ '@soybeanjs/changelog@0.3.25(@typescript-eslint/utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@unocss/eslint-config@66.5.10(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-plugin-vue@10.6.2(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))))(eslint@9.39.2(jiti@2.6.1))(svelte-eslint-parser@1.4.1)(typescript@5.9.3)(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1)))':
+ dependencies:
+ '@soybeanjs/eslint-config': 1.7.4(@typescript-eslint/utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@unocss/eslint-config@66.5.10(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-plugin-vue@10.6.2(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))))(eslint@9.39.2(jiti@2.6.1))(svelte-eslint-parser@1.4.1)(typescript@5.9.3)(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1)))
+ cli-progress: 3.12.0
+ convert-gitmoji: 0.1.5
+ dayjs: 1.11.15
+ execa: 9.6.0
+ ofetch: 1.4.1
+ semver: 7.7.2
+ transitivePeerDependencies:
+ - '@toml-tools/parser'
+ - '@types/eslint'
+ - '@typescript-eslint/utils'
+ - '@unocss/eslint-config'
+ - eslint
+ - eslint-import-resolver-node
+ - eslint-plugin-astro
+ - eslint-plugin-react
+ - eslint-plugin-react-hooks
+ - eslint-plugin-react-native
+ - eslint-plugin-react-refresh
+ - eslint-plugin-solid
+ - eslint-plugin-svelte
+ - eslint-plugin-vue
+ - prettier-plugin-astro
+ - prettier-plugin-svelte
+ - prettier-plugin-toml
+ - supports-color
+ - svelte-eslint-parser
+ - typescript
+ - vue-eslint-parser
+
+ '@soybeanjs/eslint-config@1.7.4(@typescript-eslint/utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@unocss/eslint-config@66.5.10(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-plugin-vue@10.6.2(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))))(eslint@9.39.2(jiti@2.6.1))(svelte-eslint-parser@1.4.1)(typescript@5.9.3)(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1)))':
+ dependencies:
+ '@antfu/eslint-define-config': 1.23.0-2
+ '@antfu/install-pkg': 1.1.0
+ '@eslint/eslintrc': 3.3.3
+ '@eslint/js': 9.39.2
+ '@typescript-eslint/eslint-plugin': 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/parser': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ eslint: 9.39.2(jiti@2.6.1)
+ eslint-config-flat-gitignore: 2.1.0(eslint@9.39.2(jiti@2.6.1))
+ eslint-config-prettier: 10.1.8(eslint@9.39.2(jiti@2.6.1))
+ eslint-parser-plain: 0.1.1
+ eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))
+ eslint-plugin-n: 17.23.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ eslint-plugin-prettier: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.7.4)
+ eslint-plugin-unicorn: 62.0.0(eslint@9.39.2(jiti@2.6.1))
+ globals: 16.5.0
+ local-pkg: 1.1.2
+ prettier: 3.7.4
+ prettier-plugin-json-sort: 0.0.2(prettier@3.7.4)
+ prompts: 2.4.2
+ svelte-eslint-parser: 1.4.1
+ typescript: 5.9.3
+ optionalDependencies:
+ '@unocss/eslint-config': 66.5.10(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ eslint-plugin-vue: 10.6.2(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1)))
+ vue-eslint-parser: 10.2.0(eslint@9.39.2(jiti@2.6.1))
+ transitivePeerDependencies:
+ - '@types/eslint'
+ - '@typescript-eslint/utils'
+ - eslint-import-resolver-node
+ - supports-color
+
+ '@tato30/vue-pdf@1.11.5(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ pdfjs-dist: 5.4.296
+ vue: 3.5.26(typescript@5.9.3)
+
+ '@tiptap-extend/columns@2.1.6(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ tslib: 2.8.1
+
+ '@tiptap/core@2.11.5(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/pm': 2.11.5
+
+ '@tiptap/extension-blockquote@2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-bold@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-bubble-menu@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+ tippy.js: 6.3.7
+
+ '@tiptap/extension-bullet-list@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-character-count@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+
+ '@tiptap/extension-code-block-lowlight@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-code-block@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)(highlight.js@11.11.1)(lowlight@3.3.0)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/extension-code-block': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+ highlight.js: 11.11.1
+ lowlight: 3.3.0
+
+ '@tiptap/extension-code-block@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+
+ '@tiptap/extension-code@2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-collaboration@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)(y-prosemirror@1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(y-protocols@1.0.7(yjs@13.6.28))(yjs@13.6.28))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+ y-prosemirror: 1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(y-protocols@1.0.7(yjs@13.6.28))(yjs@13.6.28)
+
+ '@tiptap/extension-color@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-text-style@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5)))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/extension-text-style': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+
+ '@tiptap/extension-document@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-dropcursor@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+
+ '@tiptap/extension-floating-menu@2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+ tippy.js: 6.3.7
+
+ '@tiptap/extension-focus@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+
+ '@tiptap/extension-font-family@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-text-style@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5)))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/extension-text-style': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+
+ '@tiptap/extension-gapcursor@2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+
+ '@tiptap/extension-hard-break@2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-heading@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-highlight@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-history@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+
+ '@tiptap/extension-horizontal-rule@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+
+ '@tiptap/extension-image@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-italic@2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-link@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+ linkifyjs: 4.3.2
+
+ '@tiptap/extension-list-item@2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-mention@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)(@tiptap/suggestion@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+ '@tiptap/suggestion': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-ordered-list@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-paragraph@2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-placeholder@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+
+ '@tiptap/extension-strike@2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-subscript@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-superscript@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-table-cell@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-table-header@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-table-row@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-table@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+
+ '@tiptap/extension-task-item@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+
+ '@tiptap/extension-task-list@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-text-align@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-text-style@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-text@2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-typography@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/extension-underline@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+
+ '@tiptap/pm@2.11.5':
+ dependencies:
+ prosemirror-changeset: 2.3.1
+ prosemirror-collab: 1.3.1
+ prosemirror-commands: 1.7.1
+ prosemirror-dropcursor: 1.8.2
+ prosemirror-gapcursor: 1.4.0
+ prosemirror-history: 1.5.0
+ prosemirror-inputrules: 1.5.1
+ prosemirror-keymap: 1.2.3
+ prosemirror-markdown: 1.13.2
+ prosemirror-menu: 1.2.5
+ prosemirror-model: 1.25.4
+ prosemirror-schema-basic: 1.2.4
+ prosemirror-schema-list: 1.5.1
+ prosemirror-state: 1.4.4
+ prosemirror-tables: 1.8.4
+ prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.4
+
+ '@tiptap/starter-kit@2.11.5':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/extension-blockquote': 2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-bold': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-bullet-list': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-code': 2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-code-block': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-document': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-dropcursor': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-gapcursor': 2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-hard-break': 2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-heading': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-history': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-horizontal-rule': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-italic': 2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-list-item': 2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-ordered-list': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-paragraph': 2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-strike': 2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-text': 2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-text-style': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/pm': 2.11.5
+
+ '@tiptap/suggestion@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+
+ '@tiptap/vue-3@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/extension-bubble-menu': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-floating-menu': 2.27.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/pm': 2.11.5
+ vue: 3.5.26(typescript@5.9.3)
+
+ '@tool-belt/type-predicates@1.4.1': {}
+
+ '@trysound/sax@0.2.0': {}
+
+ '@tybys/wasm-util@0.10.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@types/crypto-js@4.2.2': {}
+
+ '@types/d3-array@3.2.2': {}
+
+ '@types/d3-axis@3.0.6':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-brush@3.0.6':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-chord@3.0.6': {}
+
+ '@types/d3-color@3.1.3': {}
+
+ '@types/d3-contour@3.0.6':
+ dependencies:
+ '@types/d3-array': 3.2.2
+ '@types/geojson': 7946.0.16
+
+ '@types/d3-delaunay@6.0.4': {}
+
+ '@types/d3-dispatch@3.0.7': {}
+
+ '@types/d3-drag@3.0.7':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-dsv@3.0.7': {}
+
+ '@types/d3-ease@3.0.2': {}
+
+ '@types/d3-fetch@3.0.7':
+ dependencies:
+ '@types/d3-dsv': 3.0.7
+
+ '@types/d3-force@3.0.10': {}
+
+ '@types/d3-format@3.0.4': {}
+
+ '@types/d3-geo@3.1.0':
+ dependencies:
+ '@types/geojson': 7946.0.16
+
+ '@types/d3-hierarchy@3.1.7': {}
+
+ '@types/d3-interpolate@3.0.4':
+ dependencies:
+ '@types/d3-color': 3.1.3
+
+ '@types/d3-path@3.1.1': {}
+
+ '@types/d3-polygon@3.0.2': {}
+
+ '@types/d3-quadtree@3.0.6': {}
+
+ '@types/d3-random@3.0.3': {}
+
+ '@types/d3-scale-chromatic@3.1.0': {}
+
+ '@types/d3-scale@4.0.9':
+ dependencies:
+ '@types/d3-time': 3.0.4
+
+ '@types/d3-selection@3.0.11': {}
+
+ '@types/d3-shape@3.1.7':
+ dependencies:
+ '@types/d3-path': 3.1.1
+
+ '@types/d3-time-format@4.0.3': {}
+
+ '@types/d3-time@3.0.4': {}
+
+ '@types/d3-timer@3.0.2': {}
+
+ '@types/d3-transition@3.0.9':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-zoom@3.0.8':
+ dependencies:
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3@7.4.3':
+ dependencies:
+ '@types/d3-array': 3.2.2
+ '@types/d3-axis': 3.0.6
+ '@types/d3-brush': 3.0.6
+ '@types/d3-chord': 3.0.6
+ '@types/d3-color': 3.1.3
+ '@types/d3-contour': 3.0.6
+ '@types/d3-delaunay': 6.0.4
+ '@types/d3-dispatch': 3.0.7
+ '@types/d3-drag': 3.0.7
+ '@types/d3-dsv': 3.0.7
+ '@types/d3-ease': 3.0.2
+ '@types/d3-fetch': 3.0.7
+ '@types/d3-force': 3.0.10
+ '@types/d3-format': 3.0.4
+ '@types/d3-geo': 3.1.0
+ '@types/d3-hierarchy': 3.1.7
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-path': 3.1.1
+ '@types/d3-polygon': 3.0.2
+ '@types/d3-quadtree': 3.0.6
+ '@types/d3-random': 3.0.3
+ '@types/d3-scale': 4.0.9
+ '@types/d3-scale-chromatic': 3.1.0
+ '@types/d3-selection': 3.0.11
+ '@types/d3-shape': 3.1.7
+ '@types/d3-time': 3.0.4
+ '@types/d3-time-format': 4.0.3
+ '@types/d3-timer': 3.0.2
+ '@types/d3-transition': 3.0.9
+ '@types/d3-zoom': 3.0.8
+
+ '@types/estree@1.0.8': {}
+
+ '@types/geojson@7946.0.16': {}
+
+ '@types/hast@3.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
+ '@types/json-schema@7.0.15': {}
+
+ '@types/katex@0.16.7': {}
+
+ '@types/linkify-it@5.0.0': {}
+
+ '@types/lodash-es@4.17.12':
+ dependencies:
+ '@types/lodash': 4.17.21
+
+ '@types/lodash@4.17.21': {}
+
+ '@types/markdown-it@14.1.2':
+ dependencies:
+ '@types/linkify-it': 5.0.0
+ '@types/mdurl': 2.0.0
+
+ '@types/mdurl@2.0.0': {}
+
+ '@types/ndarray@1.0.14': {}
+
+ '@types/node@10.17.60': {}
+
+ '@types/node@20.3.3': {}
+
+ '@types/node@25.0.3':
+ dependencies:
+ undici-types: 7.16.0
+
+ '@types/nprogress@0.2.3': {}
+
+ '@types/qs@6.14.0': {}
+
+ '@types/sortablejs@1.15.9': {}
+
+ '@types/streamsaver@2.0.5': {}
+
+ '@types/svg64@1.1.2': {}
+
+ '@types/svgo@2.6.4':
+ dependencies:
+ '@types/node': 25.0.3
+
+ '@types/trusted-types@2.0.7':
+ optional: true
+
+ '@types/unist@3.0.3': {}
+
+ '@types/web-bluetooth@0.0.20': {}
+
+ '@types/web-bluetooth@0.0.21': {}
+
+ '@typescript-eslint/eslint-plugin@8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/regexpp': 4.12.2
+ '@typescript-eslint/parser': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/scope-manager': 8.50.1
+ '@typescript-eslint/type-utils': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.50.1
+ eslint: 9.39.2(jiti@2.6.1)
+ ignore: 7.0.5
+ natural-compare: 1.4.0
+ ts-api-utils: 2.1.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 8.50.1
+ '@typescript-eslint/types': 8.50.1
+ '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.50.1
+ debug: 4.4.3
+ eslint: 9.39.2(jiti@2.6.1)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/project-service@8.50.1(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/tsconfig-utils': 8.50.1(typescript@5.9.3)
+ '@typescript-eslint/types': 8.50.1
+ debug: 4.4.3
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/scope-manager@8.50.1':
+ dependencies:
+ '@typescript-eslint/types': 8.50.1
+ '@typescript-eslint/visitor-keys': 8.50.1
+
+ '@typescript-eslint/tsconfig-utils@8.50.1(typescript@5.9.3)':
+ dependencies:
+ typescript: 5.9.3
+
+ '@typescript-eslint/type-utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/types': 8.50.1
+ '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ debug: 4.4.3
+ eslint: 9.39.2(jiti@2.6.1)
+ ts-api-utils: 2.1.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/types@8.50.1': {}
+
+ '@typescript-eslint/typescript-estree@8.50.1(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/project-service': 8.50.1(typescript@5.9.3)
+ '@typescript-eslint/tsconfig-utils': 8.50.1(typescript@5.9.3)
+ '@typescript-eslint/types': 8.50.1
+ '@typescript-eslint/visitor-keys': 8.50.1
+ debug: 4.4.3
+ minimatch: 9.0.5
+ semver: 7.7.3
+ tinyglobby: 0.2.15
+ ts-api-utils: 2.1.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1))
+ '@typescript-eslint/scope-manager': 8.50.1
+ '@typescript-eslint/types': 8.50.1
+ '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3)
+ eslint: 9.39.2(jiti@2.6.1)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/visitor-keys@8.50.1':
+ dependencies:
+ '@typescript-eslint/types': 8.50.1
+ eslint-visitor-keys: 4.2.1
+
+ '@umoteam/editor-external@8.1.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@umoteam/editor': 8.1.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
+ '@umoteam/viewer': 0.1.9(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ - prosemirror-model
+ - prosemirror-state
+ - prosemirror-view
+ - typescript
+ - vue
+
+ '@umoteam/editor-external@9.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@umoteam/editor': 9.0.1(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
+ '@umoteam/viewer': 0.3.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ - prosemirror-model
+ - prosemirror-state
+ - prosemirror-view
+ - typescript
+ - vue
+
+ '@umoteam/editor@8.1.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@eslint/object-schema': 2.1.7
+ '@imgly/background-removal': 1.5.5
+ '@tiptap-extend/columns': 2.1.6(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/extension-bold': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-bubble-menu': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-bullet-list': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-character-count': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-code-block': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-code-block-lowlight': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-code-block@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)(highlight.js@11.11.1)(lowlight@3.3.0)
+ '@tiptap/extension-collaboration': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)(y-prosemirror@1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(y-protocols@1.0.7(yjs@13.6.28))(yjs@13.6.28))
+ '@tiptap/extension-color': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-text-style@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5)))
+ '@tiptap/extension-document': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-dropcursor': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-focus': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-font-family': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-text-style@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5)))
+ '@tiptap/extension-heading': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-highlight': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-history': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-horizontal-rule': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-image': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-link': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-mention': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)(@tiptap/suggestion@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5))
+ '@tiptap/extension-ordered-list': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-placeholder': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-subscript': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-superscript': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-table': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-table-cell': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-table-header': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-table-row': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-task-item': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-task-list': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-text-align': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-text-style': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-typography': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-underline': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/pm': 2.11.5
+ '@tiptap/starter-kit': 2.11.5
+ '@tiptap/suggestion': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/vue-3': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)(vue@3.5.26(typescript@5.9.3))
+ '@tool-belt/type-predicates': 1.4.1
+ '@types/svg64': 1.1.2
+ '@umoteam/editor-external': 8.1.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
+ '@umoteam/viewer': 0.1.9(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
+ '@vueuse/core': 11.3.0(vue@3.5.26(typescript@5.9.3))
+ buffer-image-size: 0.6.4
+ dom-to-image-more: 3.7.2
+ es-drager: 1.2.11(vue@3.5.26(typescript@5.9.3))
+ file-saver: 2.0.5
+ file64: 1.0.5
+ highlight.js: 11.11.1
+ hotkeys-js: 3.13.15
+ jsbarcode: 3.12.1
+ katex: 0.16.27
+ lowlight: 3.3.0
+ mermaid: 11.12.2
+ nzh: 1.0.14
+ plyr: 3.8.3
+ pretty-bytes: 6.1.1
+ prosemirror-transform: 1.10.5
+ qrcode-svg: 1.1.0
+ sign-canvas-plus: 2.0.3(typescript@5.9.3)
+ smooth-signature: 1.1.0
+ svg64: 2.0.0
+ tippy.js: 6.3.7
+ vue-i18n: 10.0.8(vue@3.5.26(typescript@5.9.3))
+ y-prosemirror: 1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(y-protocols@1.0.7(yjs@13.6.28))(yjs@13.6.28)
+ y-protocols: 1.0.7(yjs@13.6.28)
+ yjs: 13.6.28
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ - prosemirror-model
+ - prosemirror-state
+ - prosemirror-view
+ - typescript
+ - vue
+
+ '@umoteam/editor@9.0.1(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@eslint/object-schema': 2.1.7
+ '@imgly/background-removal': 1.5.5
+ '@tiptap-extend/columns': 2.1.6(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)
+ '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5)
+ '@tiptap/extension-bold': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-bubble-menu': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-bullet-list': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-character-count': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-code-block': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-code-block-lowlight': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-code-block@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)(highlight.js@11.11.1)(lowlight@3.3.0)
+ '@tiptap/extension-collaboration': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)(y-prosemirror@1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(y-protocols@1.0.7(yjs@13.6.28))(yjs@13.6.28))
+ '@tiptap/extension-color': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-text-style@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5)))
+ '@tiptap/extension-document': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-dropcursor': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-focus': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-font-family': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-text-style@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5)))
+ '@tiptap/extension-heading': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-highlight': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-history': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-horizontal-rule': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-image': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-link': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-mention': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)(@tiptap/suggestion@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5))
+ '@tiptap/extension-ordered-list': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-placeholder': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-subscript': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-superscript': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-table': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-table-cell': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-table-header': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-table-row': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-task-item': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/extension-task-list': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-text-align': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-text-style': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-typography': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/extension-underline': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))
+ '@tiptap/pm': 2.11.5
+ '@tiptap/starter-kit': 2.11.5
+ '@tiptap/suggestion': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)
+ '@tiptap/vue-3': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5)(vue@3.5.26(typescript@5.9.3))
+ '@tool-belt/type-predicates': 1.4.1
+ '@types/svg64': 1.1.2
+ '@umoteam/editor-external': 9.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
+ '@umoteam/viewer': 0.3.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
+ '@vueuse/core': 11.3.0(vue@3.5.26(typescript@5.9.3))
+ buffer-image-size: 0.6.4
+ dom-to-image-more: 3.7.2
+ es-drager: 1.2.11(vue@3.5.26(typescript@5.9.3))
+ file-saver: 2.0.5
+ file64: 1.0.5
+ highlight.js: 11.11.1
+ hotkeys-js: 3.13.15
+ jsbarcode: 3.12.1
+ katex: 0.16.27
+ lowlight: 3.3.0
+ mermaid: 11.12.2
+ nzh: 1.0.14
+ plyr: 3.8.3
+ pretty-bytes: 6.1.1
+ prosemirror-transform: 1.10.5
+ pure-svg-code: 1.0.6
+ sign-canvas-plus: 2.0.3(typescript@5.9.3)
+ smooth-signature: 1.1.0
+ svg64: 2.0.0
+ tippy.js: 6.3.7
+ vue-i18n: 10.0.8(vue@3.5.26(typescript@5.9.3))
+ y-prosemirror: 1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(y-protocols@1.0.7(yjs@13.6.28))(yjs@13.6.28)
+ y-protocols: 1.0.7(yjs@13.6.28)
+ yjs: 13.6.28
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ - prosemirror-model
+ - prosemirror-state
+ - prosemirror-view
+ - typescript
+ - vue
+
+ '@umoteam/viewer@0.1.9(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@tato30/vue-pdf': 1.11.5(vue@3.5.26(typescript@5.9.3))
+ '@umoteam/editor': 8.1.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
+ '@vueuse/core': 13.9.0(vue@3.5.26(typescript@5.9.3))
+ echarts: 5.6.0
+ plyr: 3.8.3
+ zod: 4.2.1
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ - prosemirror-model
+ - prosemirror-state
+ - prosemirror-view
+ - typescript
+ - vue
+
+ '@umoteam/viewer@0.3.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@tato30/vue-pdf': 1.11.5(vue@3.5.26(typescript@5.9.3))
+ '@umoteam/editor': 8.1.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
+ '@vueuse/core': 13.9.0(vue@3.5.26(typescript@5.9.3))
+ default-passive-events: 4.0.0
+ echarts: 5.6.0
+ plyr: 3.8.3
+ zod: 4.2.1
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ - prosemirror-model
+ - prosemirror-state
+ - prosemirror-view
+ - typescript
+ - vue
+
+ '@unocss/config@66.5.10':
+ dependencies:
+ '@unocss/core': 66.5.10
+ unconfig: 7.4.2
+
+ '@unocss/core@66.5.10': {}
+
+ '@unocss/eslint-config@66.5.10(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+ dependencies:
+ '@unocss/eslint-plugin': 66.5.10(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ transitivePeerDependencies:
+ - eslint
+ - supports-color
+ - typescript
+
+ '@unocss/eslint-plugin@66.5.10(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/utils': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ '@unocss/config': 66.5.10
+ '@unocss/core': 66.5.10
+ '@unocss/rule-utils': 66.5.10
+ magic-string: 0.30.21
+ synckit: 0.11.11
+ transitivePeerDependencies:
+ - eslint
+ - supports-color
+ - typescript
+
+ '@unocss/extractor-arbitrary-variants@66.5.10':
+ dependencies:
+ '@unocss/core': 66.5.10
+
+ '@unocss/inspector@66.5.10':
+ dependencies:
+ '@unocss/core': 66.5.10
+ '@unocss/rule-utils': 66.5.10
+ colorette: 2.0.20
+ gzip-size: 6.0.0
+ sirv: 3.0.2
+ vue-flow-layout: 0.2.0
+
+ '@unocss/preset-icons@66.5.10':
+ dependencies:
+ '@iconify/utils': 3.1.0
+ '@unocss/core': 66.5.10
+ ofetch: 1.5.1
+
+ '@unocss/preset-mini@66.5.10':
+ dependencies:
+ '@unocss/core': 66.5.10
+ '@unocss/extractor-arbitrary-variants': 66.5.10
+ '@unocss/rule-utils': 66.5.10
+
+ '@unocss/preset-uno@66.5.10':
+ dependencies:
+ '@unocss/core': 66.5.10
+ '@unocss/preset-wind3': 66.5.10
+
+ '@unocss/preset-wind3@66.5.10':
+ dependencies:
+ '@unocss/core': 66.5.10
+ '@unocss/preset-mini': 66.5.10
+ '@unocss/rule-utils': 66.5.10
+
+ '@unocss/rule-utils@66.5.10':
+ dependencies:
+ '@unocss/core': 66.5.10
+ magic-string: 0.30.21
+
+ '@unocss/transformer-directives@66.5.10':
+ dependencies:
+ '@unocss/core': 66.5.10
+ '@unocss/rule-utils': 66.5.10
+ css-tree: 3.1.0
+
+ '@unocss/transformer-variant-group@66.5.10':
+ dependencies:
+ '@unocss/core': 66.5.10
+
+ '@unocss/vite@66.5.10(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ '@unocss/config': 66.5.10
+ '@unocss/core': 66.5.10
+ '@unocss/inspector': 66.5.10
+ chokidar: 3.6.0
+ magic-string: 0.30.21
+ pathe: 2.0.3
+ tinyglobby: 0.2.15
+ unplugin-utils: 0.3.1
+ vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)
+
+ '@unrs/resolver-binding-android-arm-eabi@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-android-arm64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-darwin-arm64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-darwin-x64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-freebsd-x64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm64-musl@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-x64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-x64-musl@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-wasm32-wasi@1.11.1':
+ dependencies:
+ '@napi-rs/wasm-runtime': 0.2.12
+ optional: true
+
+ '@unrs/resolver-binding-win32-arm64-msvc@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-win32-ia32-msvc@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
+ optional: true
+
+ '@vitejs/plugin-vue-jsx@5.1.2(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5)
+ '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5)
+ '@rolldown/pluginutils': 1.0.0-beta.56
+ '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.28.5)
+ vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)
+ vue: 3.5.26(typescript@5.9.3)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@vitejs/plugin-vue@6.0.3(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@rolldown/pluginutils': 1.0.0-beta.53
+ vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)
+ vue: 3.5.26(typescript@5.9.3)
+
+ '@volar/language-core@2.4.27':
+ dependencies:
+ '@volar/source-map': 2.4.27
+
+ '@volar/source-map@2.4.27': {}
+
+ '@volar/typescript@2.4.27':
+ dependencies:
+ '@volar/language-core': 2.4.27
+ path-browserify: 1.0.1
+ vscode-uri: 3.1.0
+
+ '@vue/babel-helper-vue-transform-on@1.5.0': {}
+
+ '@vue/babel-helper-vue-transform-on@2.0.1': {}
+
+ '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5)
+ '@babel/template': 7.27.2
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ '@vue/babel-helper-vue-transform-on': 1.5.0
+ '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.28.5)
+ '@vue/shared': 3.5.26
+ optionalDependencies:
+ '@babel/core': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5)
+ '@babel/template': 7.27.2
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ '@vue/babel-helper-vue-transform-on': 2.0.1
+ '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.28.5)
+ '@vue/shared': 3.5.26
+ optionalDependencies:
+ '@babel/core': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/core': 7.28.5
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/parser': 7.28.5
+ '@vue/compiler-sfc': 3.5.26
+ transitivePeerDependencies:
+ - supports-color
+
+ '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/core': 7.28.5
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/parser': 7.28.5
+ '@vue/compiler-sfc': 3.5.26
+ transitivePeerDependencies:
+ - supports-color
+
+ '@vue/compiler-core@3.5.26':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@vue/shared': 3.5.26
+ entities: 7.0.0
+ estree-walker: 2.0.2
+ source-map-js: 1.2.1
+
+ '@vue/compiler-dom@3.5.26':
+ dependencies:
+ '@vue/compiler-core': 3.5.26
+ '@vue/shared': 3.5.26
+
+ '@vue/compiler-sfc@3.5.26':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@vue/compiler-core': 3.5.26
+ '@vue/compiler-dom': 3.5.26
+ '@vue/compiler-ssr': 3.5.26
+ '@vue/shared': 3.5.26
+ estree-walker: 2.0.2
+ magic-string: 0.30.21
+ postcss: 8.5.6
+ source-map-js: 1.2.1
+
+ '@vue/compiler-ssr@3.5.26':
+ dependencies:
+ '@vue/compiler-dom': 3.5.26
+ '@vue/shared': 3.5.26
+
+ '@vue/devtools-api@6.6.4': {}
+
+ '@vue/devtools-api@7.7.9':
+ dependencies:
+ '@vue/devtools-kit': 7.7.9
+
+ '@vue/devtools-core@8.0.5(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@vue/devtools-kit': 8.0.5
+ '@vue/devtools-shared': 8.0.5
+ mitt: 3.0.1
+ nanoid: 5.1.6
+ pathe: 2.0.3
+ vite-hot-client: 2.1.0(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))
+ vue: 3.5.26(typescript@5.9.3)
+ transitivePeerDependencies:
+ - vite
+
+ '@vue/devtools-kit@7.7.9':
+ dependencies:
+ '@vue/devtools-shared': 7.7.9
+ birpc: 2.9.0
+ hookable: 5.5.3
+ mitt: 3.0.1
+ perfect-debounce: 1.0.0
+ speakingurl: 14.0.1
+ superjson: 2.2.6
+
+ '@vue/devtools-kit@8.0.5':
+ dependencies:
+ '@vue/devtools-shared': 8.0.5
+ birpc: 2.9.0
+ hookable: 5.5.3
+ mitt: 3.0.1
+ perfect-debounce: 2.0.0
+ speakingurl: 14.0.1
+ superjson: 2.2.6
+
+ '@vue/devtools-shared@7.7.9':
+ dependencies:
+ rfdc: 1.4.1
+
+ '@vue/devtools-shared@8.0.5':
+ dependencies:
+ rfdc: 1.4.1
+
+ '@vue/language-core@3.2.1':
+ dependencies:
+ '@volar/language-core': 2.4.27
+ '@vue/compiler-dom': 3.5.26
+ '@vue/shared': 3.5.26
+ alien-signals: 3.1.1
+ muggle-string: 0.4.1
+ path-browserify: 1.0.1
+ picomatch: 4.0.3
+
+ '@vue/reactivity@3.5.26':
+ dependencies:
+ '@vue/shared': 3.5.26
+
+ '@vue/runtime-core@3.5.26':
+ dependencies:
+ '@vue/reactivity': 3.5.26
+ '@vue/shared': 3.5.26
+
+ '@vue/runtime-dom@3.5.26':
+ dependencies:
+ '@vue/reactivity': 3.5.26
+ '@vue/runtime-core': 3.5.26
+ '@vue/shared': 3.5.26
+ csstype: 3.2.3
+
+ '@vue/server-renderer@3.5.26(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@vue/compiler-ssr': 3.5.26
+ '@vue/shared': 3.5.26
+ vue: 3.5.26(typescript@5.9.3)
+
+ '@vue/shared@3.5.26': {}
+
+ '@vueuse/core@11.3.0(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@types/web-bluetooth': 0.0.20
+ '@vueuse/metadata': 11.3.0
+ '@vueuse/shared': 11.3.0(vue@3.5.26(typescript@5.9.3))
+ vue-demi: 0.14.10(vue@3.5.26(typescript@5.9.3))
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ - vue
+
+ '@vueuse/core@13.9.0(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@types/web-bluetooth': 0.0.21
+ '@vueuse/metadata': 13.9.0
+ '@vueuse/shared': 13.9.0(vue@3.5.26(typescript@5.9.3))
+ vue: 3.5.26(typescript@5.9.3)
+
+ '@vueuse/core@14.1.0(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@types/web-bluetooth': 0.0.21
+ '@vueuse/metadata': 14.1.0
+ '@vueuse/shared': 14.1.0(vue@3.5.26(typescript@5.9.3))
+ vue: 3.5.26(typescript@5.9.3)
+
+ '@vueuse/metadata@11.3.0': {}
+
+ '@vueuse/metadata@13.9.0': {}
+
+ '@vueuse/metadata@14.1.0': {}
+
+ '@vueuse/shared@11.3.0(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ vue-demi: 0.14.10(vue@3.5.26(typescript@5.9.3))
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ - vue
+
+ '@vueuse/shared@13.9.0(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ vue: 3.5.26(typescript@5.9.3)
+
+ '@vueuse/shared@14.1.0(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ vue: 3.5.26(typescript@5.9.3)
+
+ acorn-jsx@5.3.2(acorn@8.15.0):
+ dependencies:
+ acorn: 8.15.0
+
+ acorn@8.15.0: {}
+
+ ajv@6.12.6:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ alien-signals@3.1.1: {}
+
+ alova@3.4.1:
+ dependencies:
+ '@alova/shared': 1.3.2
+ rate-limiter-flexible: 5.0.5
+
+ ansi-colors@4.1.3: {}
+
+ ansi-regex@2.1.1: {}
+
+ ansi-regex@5.0.1: {}
+
+ ansi-regex@6.2.2: {}
+
+ ansi-styles@2.2.1: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ ansi-styles@6.2.3: {}
+
+ ansis@4.2.0: {}
+
+ anymatch@3.1.3:
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+
+ argparse@2.0.1: {}
+
+ args-tokenizer@0.3.0: {}
+
+ arr-diff@4.0.0: {}
+
+ arr-flatten@1.1.0: {}
+
+ arr-union@3.1.0: {}
+
+ array-buffer-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ is-array-buffer: 3.0.5
+
+ array-unique@0.3.2: {}
+
+ arraybuffer.prototype.slice@1.0.4:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ is-array-buffer: 3.0.5
+
+ assign-symbols@1.0.0: {}
+
+ ast-types@0.16.1:
+ dependencies:
+ tslib: 2.8.1
+
+ async-function@1.0.0: {}
+
+ async-validator@4.2.5: {}
+
+ asynckit@0.4.0: {}
+
+ atob@2.1.2: {}
+
+ available-typed-arrays@1.0.7:
+ dependencies:
+ possible-typed-array-names: 1.1.0
+
+ axios-retry@4.5.0(axios@1.13.2):
+ dependencies:
+ axios: 1.13.2
+ is-retry-allowed: 2.2.0
+
+ axios@1.13.2:
+ dependencies:
+ follow-redirects: 1.15.11
+ form-data: 4.0.5
+ proxy-from-env: 1.1.0
+ transitivePeerDependencies:
+ - debug
+
+ balanced-match@1.0.2: {}
+
+ base@0.11.2:
+ dependencies:
+ cache-base: 1.0.1
+ class-utils: 0.3.6
+ component-emitter: 1.3.1
+ define-property: 1.0.0
+ isobject: 3.0.1
+ mixin-deep: 1.3.2
+ pascalcase: 0.1.1
+
+ baseline-browser-mapping@2.9.11: {}
+
+ big.js@5.2.2: {}
+
+ binary-extensions@2.3.0: {}
+
+ birpc@2.9.0: {}
+
+ bluebird@3.7.2: {}
+
+ boolbase@1.0.0: {}
+
+ brace-expansion@1.1.12:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@2.0.2:
+ dependencies:
+ balanced-match: 1.0.2
+
+ braces@2.3.2:
+ dependencies:
+ arr-flatten: 1.1.0
+ array-unique: 0.3.2
+ extend-shallow: 2.0.1
+ fill-range: 4.0.0
+ isobject: 3.0.1
+ repeat-element: 1.1.4
+ snapdragon: 0.8.2
+ snapdragon-node: 2.1.1
+ split-string: 3.1.0
+ to-regex: 3.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ browserslist@4.28.1:
+ dependencies:
+ baseline-browser-mapping: 2.9.11
+ caniuse-lite: 1.0.30001761
+ electron-to-chromium: 1.5.267
+ node-releases: 2.0.27
+ update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+ buffer-image-size@0.6.4:
+ dependencies:
+ '@types/node': 25.0.3
+
+ builtin-modules@5.0.0: {}
+
+ bumpp@10.3.2(magicast@0.3.4):
+ dependencies:
+ ansis: 4.2.0
+ args-tokenizer: 0.3.0
+ c12: 3.3.3(magicast@0.3.4)
+ cac: 6.7.14
+ escalade: 3.2.0
+ jsonc-parser: 3.3.1
+ package-manager-detector: 1.6.0
+ semver: 7.7.3
+ tinyexec: 1.0.2
+ tinyglobby: 0.2.15
+ yaml: 2.8.2
+ transitivePeerDependencies:
+ - magicast
+
+ bundle-name@4.1.0:
+ dependencies:
+ run-applescript: 7.1.0
+
+ c12@3.3.3(magicast@0.3.4):
+ dependencies:
+ chokidar: 5.0.0
+ confbox: 0.2.2
+ defu: 6.1.4
+ dotenv: 17.2.3
+ exsolve: 1.0.8
+ giget: 2.0.0
+ jiti: 2.6.1
+ ohash: 2.0.11
+ pathe: 2.0.3
+ perfect-debounce: 2.0.0
+ pkg-types: 2.3.0
+ rc9: 2.1.2
+ optionalDependencies:
+ magicast: 0.3.4
+
+ cac@6.7.14: {}
+
+ cache-base@1.0.1:
+ dependencies:
+ collection-visit: 1.0.0
+ component-emitter: 1.3.1
+ get-value: 2.0.6
+ has-value: 1.0.0
+ isobject: 3.0.1
+ set-value: 2.0.1
+ to-object-path: 0.3.0
+ union-value: 1.0.1
+ unset-value: 1.0.0
+
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ call-bind@1.0.8:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ get-intrinsic: 1.3.0
+ set-function-length: 1.2.2
+
+ call-bound@1.0.4:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ get-intrinsic: 1.3.0
+
+ callsites@3.1.0: {}
+
+ camelcase@6.3.0: {}
+
+ caniuse-lite@1.0.30001761: {}
+
+ chalk@1.1.3:
+ dependencies:
+ ansi-styles: 2.2.1
+ escape-string-regexp: 1.0.5
+ has-ansi: 2.0.0
+ strip-ansi: 3.0.1
+ supports-color: 2.0.0
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ change-case@5.4.4: {}
+
+ chevrotain-allstar@0.3.1(chevrotain@11.0.3):
+ dependencies:
+ chevrotain: 11.0.3
+ lodash-es: 4.17.22
+
+ chevrotain@11.0.3:
+ dependencies:
+ '@chevrotain/cst-dts-gen': 11.0.3
+ '@chevrotain/gast': 11.0.3
+ '@chevrotain/regexp-to-ast': 11.0.3
+ '@chevrotain/types': 11.0.3
+ '@chevrotain/utils': 11.0.3
+ lodash-es: 4.17.21
+
+ chokidar@3.6.0:
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.3
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ chokidar@4.0.3:
+ dependencies:
+ readdirp: 4.1.2
+
+ chokidar@5.0.0:
+ dependencies:
+ readdirp: 5.0.0
+
+ ci-info@4.3.1: {}
+
+ citty@0.1.6:
+ dependencies:
+ consola: 3.4.2
+
+ class-utils@0.3.6:
+ dependencies:
+ arr-union: 3.1.0
+ define-property: 0.2.5
+ isobject: 3.0.1
+ static-extend: 0.1.2
+
+ classnames@2.5.1: {}
+
+ clean-regexp@1.0.0:
+ dependencies:
+ escape-string-regexp: 1.0.5
+
+ cli-progress@3.12.0:
+ dependencies:
+ string-width: 4.2.3
+
+ clipboard@2.0.11:
+ dependencies:
+ good-listener: 1.2.2
+ select: 1.1.2
+ tiny-emitter: 2.1.0
+
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
+ clone@2.1.2: {}
+
+ collection-visit@1.0.0:
+ dependencies:
+ map-visit: 1.0.0
+ object-visit: 1.0.1
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ colord@2.9.3: {}
+
+ colorette@2.0.20: {}
+
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
+ commander@7.2.0: {}
+
+ commander@8.3.0: {}
+
+ comment-parser@1.4.1: {}
+
+ component-emitter@1.3.1: {}
+
+ concat-map@0.0.1: {}
+
+ confbox@0.1.8: {}
+
+ confbox@0.2.2: {}
+
+ consola@3.2.3: {}
+
+ consola@3.4.2: {}
+
+ convert-gitmoji@0.1.5: {}
+
+ convert-source-map@2.0.0: {}
+
+ copy-anything@4.0.5:
+ dependencies:
+ is-what: 5.5.0
+
+ copy-descriptor@0.1.1: {}
+
+ core-js-compat@3.47.0:
+ dependencies:
+ browserslist: 4.28.1
+
+ core-js@3.47.0: {}
+
+ cors@2.8.5:
+ dependencies:
+ object-assign: 4.1.1
+ vary: 1.1.2
+
+ cose-base@1.0.3:
+ dependencies:
+ layout-base: 1.0.2
+
+ cose-base@2.2.0:
+ dependencies:
+ layout-base: 2.0.1
+
+ crelt@1.0.6: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ crypto-js@4.2.0: {}
+
+ css-render@0.15.14:
+ dependencies:
+ '@emotion/hash': 0.8.0
+ csstype: 3.0.11
+
+ css-select@4.3.0:
+ dependencies:
+ boolbase: 1.0.0
+ css-what: 6.2.2
+ domhandler: 4.3.1
+ domutils: 2.8.0
+ nth-check: 2.1.1
+
+ css-tree@1.1.3:
+ dependencies:
+ mdn-data: 2.0.14
+ source-map: 0.6.1
+
+ css-tree@3.1.0:
+ dependencies:
+ mdn-data: 2.12.2
+ source-map-js: 1.2.1
+
+ css-what@6.2.2: {}
+
+ cssesc@3.0.0: {}
+
+ csso@4.2.0:
+ dependencies:
+ css-tree: 1.1.3
+
+ csstype@3.0.11: {}
+
+ csstype@3.2.3: {}
+
+ custom-event-polyfill@1.0.7: {}
+
+ cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1):
+ dependencies:
+ cose-base: 1.0.3
+ cytoscape: 3.33.1
+
+ cytoscape-fcose@2.2.0(cytoscape@3.33.1):
+ dependencies:
+ cose-base: 2.2.0
+ cytoscape: 3.33.1
+
+ cytoscape@3.33.1: {}
+
+ d3-array@2.12.1:
+ dependencies:
+ internmap: 1.0.1
+
+ d3-array@3.2.4:
+ dependencies:
+ internmap: 2.0.3
+
+ d3-axis@3.0.0: {}
+
+ d3-brush@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
+ d3-chord@3.0.1:
+ dependencies:
+ d3-path: 3.1.0
+
+ d3-color@3.1.0: {}
+
+ d3-contour@4.0.2:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-delaunay@6.0.4:
+ dependencies:
+ delaunator: 5.0.1
+
+ d3-dispatch@3.0.1: {}
+
+ d3-drag@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-selection: 3.0.0
+
+ d3-dsv@3.0.1:
+ dependencies:
+ commander: 7.2.0
+ iconv-lite: 0.6.3
+ rw: 1.3.3
+
+ d3-ease@3.0.1: {}
+
+ d3-fetch@3.0.1:
+ dependencies:
+ d3-dsv: 3.0.1
+
+ d3-force@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-quadtree: 3.0.1
+ d3-timer: 3.0.1
+
+ d3-format@3.1.0: {}
+
+ d3-geo@3.1.1:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-hierarchy@3.1.2: {}
+
+ d3-interpolate@3.0.1:
+ dependencies:
+ d3-color: 3.1.0
+
+ d3-path@1.0.9: {}
+
+ d3-path@3.1.0: {}
+
+ d3-polygon@3.0.1: {}
+
+ d3-quadtree@3.0.1: {}
+
+ d3-random@3.0.1: {}
+
+ d3-sankey@0.12.3:
+ dependencies:
+ d3-array: 2.12.1
+ d3-shape: 1.3.7
+
+ d3-scale-chromatic@3.1.0:
+ dependencies:
+ d3-color: 3.1.0
+ d3-interpolate: 3.0.1
+
+ d3-scale@4.0.2:
+ dependencies:
+ d3-array: 3.2.4
+ d3-format: 3.1.0
+ d3-interpolate: 3.0.1
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+
+ d3-selection@3.0.0: {}
+
+ d3-shape@1.3.7:
+ dependencies:
+ d3-path: 1.0.9
+
+ d3-shape@3.2.0:
+ dependencies:
+ d3-path: 3.1.0
+
+ d3-time-format@4.1.0:
+ dependencies:
+ d3-time: 3.1.0
+
+ d3-time@3.1.0:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-timer@3.0.1: {}
+
+ d3-transition@3.0.1(d3-selection@3.0.0):
+ dependencies:
+ d3-color: 3.1.0
+ d3-dispatch: 3.0.1
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-timer: 3.0.1
+
+ d3-zoom@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
+ d3@7.9.0:
+ dependencies:
+ d3-array: 3.2.4
+ d3-axis: 3.0.0
+ d3-brush: 3.0.0
+ d3-chord: 3.0.1
+ d3-color: 3.1.0
+ d3-contour: 4.0.2
+ d3-delaunay: 6.0.4
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-dsv: 3.0.1
+ d3-ease: 3.0.1
+ d3-fetch: 3.0.1
+ d3-force: 3.0.0
+ d3-format: 3.1.0
+ d3-geo: 3.1.1
+ d3-hierarchy: 3.1.2
+ d3-interpolate: 3.0.1
+ d3-path: 3.1.0
+ d3-polygon: 3.0.1
+ d3-quadtree: 3.0.1
+ d3-random: 3.0.1
+ d3-scale: 4.0.2
+ d3-scale-chromatic: 3.1.0
+ d3-selection: 3.0.0
+ d3-shape: 3.2.0
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+ d3-timer: 3.0.1
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+ d3-zoom: 3.0.0
+
+ dagre-d3-es@7.0.13:
+ dependencies:
+ d3: 7.9.0
+ lodash-es: 4.17.22
+
+ data-view-buffer@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-offset@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ date-fns-tz@3.2.0(date-fns@4.1.0):
+ dependencies:
+ date-fns: 4.1.0
+
+ date-fns@4.1.0: {}
+
+ dayjs@1.11.15: {}
+
+ dayjs@1.11.19: {}
+
+ debounce@1.2.1: {}
+
+ debug@2.6.9:
+ dependencies:
+ ms: 2.0.0
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ decode-uri-component@0.2.2: {}
+
+ deep-is@0.1.4: {}
+
+ default-browser-id@5.0.1: {}
+
+ default-browser@5.4.0:
+ dependencies:
+ bundle-name: 4.1.0
+ default-browser-id: 5.0.1
+
+ default-passive-events@4.0.0: {}
+
+ define-data-property@1.1.4:
+ dependencies:
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ define-lazy-prop@3.0.0: {}
+
+ define-properties@1.2.1:
+ dependencies:
+ define-data-property: 1.1.4
+ has-property-descriptors: 1.0.2
+ object-keys: 1.1.1
+
+ define-property@0.2.5:
+ dependencies:
+ is-descriptor: 0.1.7
+
+ define-property@1.0.0:
+ dependencies:
+ is-descriptor: 1.0.3
+
+ define-property@2.0.2:
+ dependencies:
+ is-descriptor: 1.0.3
+ isobject: 3.0.1
+
+ defu@6.1.4: {}
+
+ delaunator@5.0.1:
+ dependencies:
+ robust-predicates: 3.0.2
+
+ delayed-stream@1.0.0: {}
+
+ delegate@3.2.0: {}
+
+ dequal@2.0.3: {}
+
+ destr@2.0.5: {}
+
+ detect-libc@1.0.3:
+ optional: true
+
+ devlop@1.1.0:
+ dependencies:
+ dequal: 2.0.3
+
+ dom-serializer@0.2.2:
+ dependencies:
+ domelementtype: 2.3.0
+ entities: 2.2.0
+
+ dom-serializer@1.4.1:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 4.3.1
+ entities: 2.2.0
+
+ dom-to-image-more@3.7.2: {}
+
+ domelementtype@1.3.1: {}
+
+ domelementtype@2.3.0: {}
+
+ domhandler@2.4.2:
+ dependencies:
+ domelementtype: 1.3.1
+
+ domhandler@4.3.1:
+ dependencies:
+ domelementtype: 2.3.0
+
+ dompurify@3.3.1:
+ optionalDependencies:
+ '@types/trusted-types': 2.0.7
+
+ domutils@1.7.0:
+ dependencies:
+ dom-serializer: 0.2.2
+ domelementtype: 1.3.1
+
+ domutils@2.8.0:
+ dependencies:
+ dom-serializer: 1.4.1
+ domelementtype: 2.3.0
+ domhandler: 4.3.1
+
+ dotenv@17.2.3: {}
+
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ duplexer@0.1.2: {}
+
+ eastasianwidth@0.2.0: {}
+
+ easy-bem@1.1.1: {}
+
+ echarts@5.6.0:
+ dependencies:
+ tslib: 2.3.0
+ zrender: 5.6.1
+
+ echarts@6.0.0:
+ dependencies:
+ tslib: 2.3.0
+ zrender: 6.0.0
+
+ electron-to-chromium@1.5.267: {}
+
+ emoji-regex@8.0.0: {}
+
+ emoji-regex@9.2.2: {}
+
+ emojis-list@3.0.0: {}
+
+ enhanced-resolve@5.18.4:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.0
+
+ enquirer@2.4.1:
+ dependencies:
+ ansi-colors: 4.1.3
+ strip-ansi: 6.0.1
+
+ entities@1.1.2: {}
+
+ entities@2.2.0: {}
+
+ entities@4.5.0: {}
+
+ entities@7.0.0: {}
+
+ error-stack-parser-es@1.0.5: {}
+
+ es-abstract@1.24.1:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ arraybuffer.prototype.slice: 1.0.4
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ data-view-buffer: 1.0.2
+ data-view-byte-length: 1.0.2
+ data-view-byte-offset: 1.0.1
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-set-tostringtag: 2.1.0
+ es-to-primitive: 1.3.0
+ function.prototype.name: 1.1.8
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ get-symbol-description: 1.1.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ internal-slot: 1.1.0
+ is-array-buffer: 3.0.5
+ is-callable: 1.2.7
+ is-data-view: 1.0.2
+ is-negative-zero: 2.0.3
+ is-regex: 1.2.1
+ is-set: 2.0.3
+ is-shared-array-buffer: 1.0.4
+ is-string: 1.1.1
+ is-typed-array: 1.1.15
+ is-weakref: 1.1.1
+ math-intrinsics: 1.1.0
+ object-inspect: 1.13.4
+ object-keys: 1.1.1
+ object.assign: 4.1.7
+ own-keys: 1.0.1
+ regexp.prototype.flags: 1.5.4
+ safe-array-concat: 1.1.3
+ safe-push-apply: 1.0.0
+ safe-regex-test: 1.1.0
+ set-proto: 1.0.0
+ stop-iteration-iterator: 1.1.0
+ string.prototype.trim: 1.2.10
+ string.prototype.trimend: 1.0.9
+ string.prototype.trimstart: 1.0.8
+ typed-array-buffer: 1.0.3
+ typed-array-byte-length: 1.0.3
+ typed-array-byte-offset: 1.0.4
+ typed-array-length: 1.0.7
+ unbox-primitive: 1.1.0
+ which-typed-array: 1.1.19
+
+ es-define-property@1.0.1: {}
+
+ es-drager@1.2.11(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ vue: 3.5.26(typescript@5.9.3)
+
+ es-errors@1.3.0: {}
+
+ es-object-atoms@1.1.1:
+ dependencies:
+ es-errors: 1.3.0
+
+ es-set-tostringtag@2.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ es-to-primitive@1.3.0:
+ dependencies:
+ is-callable: 1.2.7
+ is-date-object: 1.1.0
+ is-symbol: 1.1.1
+
+ esbuild@0.27.2:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.27.2
+ '@esbuild/android-arm': 0.27.2
+ '@esbuild/android-arm64': 0.27.2
+ '@esbuild/android-x64': 0.27.2
+ '@esbuild/darwin-arm64': 0.27.2
+ '@esbuild/darwin-x64': 0.27.2
+ '@esbuild/freebsd-arm64': 0.27.2
+ '@esbuild/freebsd-x64': 0.27.2
+ '@esbuild/linux-arm': 0.27.2
+ '@esbuild/linux-arm64': 0.27.2
+ '@esbuild/linux-ia32': 0.27.2
+ '@esbuild/linux-loong64': 0.27.2
+ '@esbuild/linux-mips64el': 0.27.2
+ '@esbuild/linux-ppc64': 0.27.2
+ '@esbuild/linux-riscv64': 0.27.2
+ '@esbuild/linux-s390x': 0.27.2
+ '@esbuild/linux-x64': 0.27.2
+ '@esbuild/netbsd-arm64': 0.27.2
+ '@esbuild/netbsd-x64': 0.27.2
+ '@esbuild/openbsd-arm64': 0.27.2
+ '@esbuild/openbsd-x64': 0.27.2
+ '@esbuild/openharmony-arm64': 0.27.2
+ '@esbuild/sunos-x64': 0.27.2
+ '@esbuild/win32-arm64': 0.27.2
+ '@esbuild/win32-ia32': 0.27.2
+ '@esbuild/win32-x64': 0.27.2
+
+ escalade@3.2.0: {}
+
+ escape-string-regexp@1.0.5: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ eslint-compat-utils@0.5.1(eslint@9.39.2(jiti@2.6.1)):
+ dependencies:
+ eslint: 9.39.2(jiti@2.6.1)
+ semver: 7.7.3
+
+ eslint-config-flat-gitignore@2.1.0(eslint@9.39.2(jiti@2.6.1)):
+ dependencies:
+ '@eslint/compat': 1.4.1(eslint@9.39.2(jiti@2.6.1))
+ eslint: 9.39.2(jiti@2.6.1)
+
+ eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)):
+ dependencies:
+ eslint: 9.39.2(jiti@2.6.1)
+
+ eslint-import-context@0.1.9(unrs-resolver@1.11.1):
+ dependencies:
+ get-tsconfig: 4.13.0
+ stable-hash-x: 0.2.0
+ optionalDependencies:
+ unrs-resolver: 1.11.1
+
+ eslint-parser-plain@0.1.1: {}
+
+ eslint-plugin-es-x@7.8.0(eslint@9.39.2(jiti@2.6.1)):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1))
+ '@eslint-community/regexpp': 4.12.2
+ eslint: 9.39.2(jiti@2.6.1)
+ eslint-compat-utils: 0.5.1(eslint@9.39.2(jiti@2.6.1))
+
+ eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)):
+ dependencies:
+ '@typescript-eslint/types': 8.50.1
+ comment-parser: 1.4.1
+ debug: 4.4.3
+ eslint: 9.39.2(jiti@2.6.1)
+ eslint-import-context: 0.1.9(unrs-resolver@1.11.1)
+ is-glob: 4.0.3
+ minimatch: 10.1.1
+ semver: 7.7.3
+ stable-hash-x: 0.2.0
+ unrs-resolver: 1.11.1
+ optionalDependencies:
+ '@typescript-eslint/utils': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-n@17.23.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1))
+ enhanced-resolve: 5.18.4
+ eslint: 9.39.2(jiti@2.6.1)
+ eslint-plugin-es-x: 7.8.0(eslint@9.39.2(jiti@2.6.1))
+ get-tsconfig: 4.13.0
+ globals: 15.15.0
+ globrex: 0.1.2
+ ignore: 5.3.2
+ semver: 7.7.3
+ ts-declaration-location: 1.0.7(typescript@5.9.3)
+ transitivePeerDependencies:
+ - typescript
+
+ eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.7.4):
+ dependencies:
+ eslint: 9.39.2(jiti@2.6.1)
+ prettier: 3.7.4
+ prettier-linter-helpers: 1.0.0
+ synckit: 0.11.11
+ optionalDependencies:
+ eslint-config-prettier: 10.1.8(eslint@9.39.2(jiti@2.6.1))
+
+ eslint-plugin-unicorn@62.0.0(eslint@9.39.2(jiti@2.6.1)):
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1))
+ '@eslint/plugin-kit': 0.4.1
+ change-case: 5.4.4
+ ci-info: 4.3.1
+ clean-regexp: 1.0.0
+ core-js-compat: 3.47.0
+ eslint: 9.39.2(jiti@2.6.1)
+ esquery: 1.6.0
+ find-up-simple: 1.0.1
+ globals: 16.5.0
+ indent-string: 5.0.0
+ is-builtin-module: 5.0.0
+ jsesc: 3.1.0
+ pluralize: 8.0.0
+ regexp-tree: 0.1.27
+ regjsparser: 0.13.0
+ semver: 7.7.3
+ strip-indent: 4.1.1
+
+ eslint-plugin-vue@10.6.2(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1))
+ eslint: 9.39.2(jiti@2.6.1)
+ natural-compare: 1.4.0
+ nth-check: 2.1.1
+ postcss-selector-parser: 7.1.1
+ semver: 7.7.3
+ vue-eslint-parser: 10.2.0(eslint@9.39.2(jiti@2.6.1))
+ xml-name-validator: 4.0.0
+ optionalDependencies:
+ '@typescript-eslint/parser': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+
+ eslint-scope@8.4.0:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint-visitor-keys@4.2.1: {}
+
+ eslint@9.39.2(jiti@2.6.1):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1))
+ '@eslint-community/regexpp': 4.12.2
+ '@eslint/config-array': 0.21.1
+ '@eslint/config-helpers': 0.4.2
+ '@eslint/core': 0.17.0
+ '@eslint/eslintrc': 3.3.3
+ '@eslint/js': 9.39.2
+ '@eslint/plugin-kit': 0.4.1
+ '@humanfs/node': 0.16.7
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.3
+ '@types/estree': 1.0.8
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.3
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.4.0
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ esquery: 1.6.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ optionalDependencies:
+ jiti: 2.6.1
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@10.4.0:
+ dependencies:
+ acorn: 8.15.0
+ acorn-jsx: 5.3.2(acorn@8.15.0)
+ eslint-visitor-keys: 4.2.1
+
+ esprima@4.0.1: {}
+
+ esquery@1.6.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@5.3.0: {}
+
+ estree-walker@2.0.2: {}
+
+ esutils@2.0.3: {}
+
+ etag@1.8.1: {}
+
+ evtd@0.2.4: {}
+
+ execa@9.6.0:
+ dependencies:
+ '@sindresorhus/merge-streams': 4.0.0
+ cross-spawn: 7.0.6
+ figures: 6.1.0
+ get-stream: 9.0.1
+ human-signals: 8.0.1
+ is-plain-obj: 4.1.0
+ is-stream: 4.0.1
+ npm-run-path: 6.0.0
+ pretty-ms: 9.3.0
+ signal-exit: 4.1.0
+ strip-final-newline: 4.0.0
+ yoctocolors: 2.1.2
+
+ execa@9.6.1:
+ dependencies:
+ '@sindresorhus/merge-streams': 4.0.0
+ cross-spawn: 7.0.6
+ figures: 6.1.0
+ get-stream: 9.0.1
+ human-signals: 8.0.1
+ is-plain-obj: 4.1.0
+ is-stream: 4.0.1
+ npm-run-path: 6.0.0
+ pretty-ms: 9.3.0
+ signal-exit: 4.1.0
+ strip-final-newline: 4.0.0
+ yoctocolors: 2.1.2
+
+ expand-brackets@2.1.4:
+ dependencies:
+ debug: 2.6.9
+ define-property: 0.2.5
+ extend-shallow: 2.0.1
+ posix-character-classes: 0.1.1
+ regex-not: 1.0.2
+ snapdragon: 0.8.2
+ to-regex: 3.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ exsolve@1.0.8: {}
+
+ extend-shallow@2.0.1:
+ dependencies:
+ is-extendable: 0.1.1
+
+ extend-shallow@3.0.2:
+ dependencies:
+ assign-symbols: 1.0.0
+ is-extendable: 1.0.1
+
+ extglob@2.0.4:
+ dependencies:
+ array-unique: 0.3.2
+ define-property: 1.0.0
+ expand-brackets: 2.1.4
+ extend-shallow: 2.0.1
+ fragment-cache: 0.2.1
+ regex-not: 1.0.2
+ snapdragon: 0.8.2
+ to-regex: 3.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-diff@1.3.0: {}
+
+ fast-glob@3.3.2:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.7
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fastq@1.19.1:
+ dependencies:
+ reusify: 1.1.0
+
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ figures@6.1.0:
+ dependencies:
+ is-unicode-supported: 2.1.0
+
+ file-entry-cache@8.0.0:
+ dependencies:
+ flat-cache: 4.0.1
+
+ file-saver@2.0.5: {}
+
+ file64@1.0.5: {}
+
+ fill-range@4.0.0:
+ dependencies:
+ extend-shallow: 2.0.1
+ is-number: 3.0.0
+ repeat-string: 1.6.1
+ to-regex-range: 2.1.1
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ find-up-simple@1.0.1: {}
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@4.0.1:
+ dependencies:
+ flatted: 3.3.3
+ keyv: 4.5.4
+
+ flatbuffers@1.12.0: {}
+
+ flatted@3.3.3: {}
+
+ follow-redirects@1.15.11: {}
+
+ for-each@0.3.5:
+ dependencies:
+ is-callable: 1.2.7
+
+ for-in@1.0.2: {}
+
+ foreground-child@3.3.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ signal-exit: 4.1.0
+
+ form-data@4.0.5:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ hasown: 2.0.2
+ mime-types: 2.1.35
+
+ fragment-cache@0.2.1:
+ dependencies:
+ map-cache: 0.2.2
+
+ fs-extra@10.1.0:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.0
+ universalify: 2.0.1
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ function.prototype.name@1.1.8:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ functions-have-names: 1.2.3
+ hasown: 2.0.2
+ is-callable: 1.2.7
+
+ functions-have-names@1.2.3: {}
+
+ generator-function@2.0.1: {}
+
+ gensync@1.0.0-beta.2: {}
+
+ get-caller-file@2.0.5: {}
+
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ math-intrinsics: 1.1.0
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.1
+
+ get-stream@9.0.1:
+ dependencies:
+ '@sec-ant/readable-stream': 0.4.1
+ is-stream: 4.0.1
+
+ get-symbol-description@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+
+ get-tsconfig@4.13.0:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ get-value@2.0.6: {}
+
+ giget@2.0.0:
+ dependencies:
+ citty: 0.1.6
+ consola: 3.4.2
+ defu: 6.1.4
+ node-fetch-native: 1.6.7
+ nypm: 0.6.2
+ pathe: 2.0.3
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob@10.5.0:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 3.4.3
+ minimatch: 9.0.5
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.1
+ path-scurry: 1.11.1
+
+ glob@13.0.0:
+ dependencies:
+ minimatch: 10.1.1
+ minipass: 7.1.2
+ path-scurry: 2.0.1
+
+ globals@14.0.0: {}
+
+ globals@15.15.0: {}
+
+ globals@16.5.0: {}
+
+ globalthis@1.0.4:
+ dependencies:
+ define-properties: 1.2.1
+ gopd: 1.2.0
+
+ globrex@0.1.2: {}
+
+ good-listener@1.2.2:
+ dependencies:
+ delegate: 3.2.0
+
+ gopd@1.2.0: {}
+
+ graceful-fs@4.2.11: {}
+
+ guid-typescript@1.0.9: {}
+
+ gzip-size@6.0.0:
+ dependencies:
+ duplexer: 0.1.2
+
+ hachure-fill@0.5.2: {}
+
+ has-ansi@2.0.0:
+ dependencies:
+ ansi-regex: 2.1.1
+
+ has-bigints@1.1.0: {}
+
+ has-flag@1.0.0: {}
+
+ has-flag@4.0.0: {}
+
+ has-property-descriptors@1.0.2:
+ dependencies:
+ es-define-property: 1.0.1
+
+ has-proto@1.2.0:
+ dependencies:
+ dunder-proto: 1.0.1
+
+ has-symbols@1.1.0: {}
+
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.1.0
+
+ has-value@0.3.1:
+ dependencies:
+ get-value: 2.0.6
+ has-values: 0.1.4
+ isobject: 2.1.0
+
+ has-value@1.0.0:
+ dependencies:
+ get-value: 2.0.6
+ has-values: 1.0.0
+ isobject: 3.0.1
+
+ has-values@0.1.4: {}
+
+ has-values@1.0.0:
+ dependencies:
+ is-number: 3.0.0
+ kind-of: 4.0.0
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ he@1.2.0: {}
+
+ highlight.js@11.11.1: {}
+
+ hookable@5.5.3: {}
+
+ hotkeys-js@3.13.15: {}
+
+ htmlparser2@3.10.1:
+ dependencies:
+ domelementtype: 1.3.1
+ domhandler: 2.4.2
+ domutils: 1.7.0
+ entities: 1.1.2
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+
+ human-signals@8.0.1: {}
+
+ iconv-lite@0.6.3:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ icss-replace-symbols@1.1.0: {}
+
+ icss-utils@5.1.0(postcss@8.5.6):
+ dependencies:
+ postcss: 8.5.6
+
+ ignore@5.3.2: {}
+
+ ignore@7.0.5: {}
+
+ image-size@0.5.5: {}
+
+ immediate@3.0.6: {}
+
+ immutable@5.1.4: {}
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ imurmurhash@0.1.4: {}
+
+ indent-string@5.0.0: {}
+
+ inherits@2.0.4: {}
+
+ internal-slot@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ hasown: 2.0.2
+ side-channel: 1.1.0
+
+ internmap@1.0.1: {}
+
+ internmap@2.0.3: {}
+
+ iota-array@1.0.0: {}
+
+ is-accessor-descriptor@1.0.1:
+ dependencies:
+ hasown: 2.0.2
+
+ is-array-buffer@3.0.5:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ is-async-function@2.1.1:
+ dependencies:
+ async-function: 1.0.0
+ call-bound: 1.0.4
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-bigint@1.1.0:
+ dependencies:
+ has-bigints: 1.1.0
+
+ is-binary-path@2.1.0:
+ dependencies:
+ binary-extensions: 2.3.0
+
+ is-boolean-object@1.2.2:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-buffer@1.1.6: {}
+
+ is-builtin-module@5.0.0:
+ dependencies:
+ builtin-modules: 5.0.0
+
+ is-callable@1.2.7: {}
+
+ is-data-descriptor@1.0.1:
+ dependencies:
+ hasown: 2.0.2
+
+ is-data-view@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ is-typed-array: 1.1.15
+
+ is-date-object@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-descriptor@0.1.7:
+ dependencies:
+ is-accessor-descriptor: 1.0.1
+ is-data-descriptor: 1.0.1
+
+ is-descriptor@1.0.3:
+ dependencies:
+ is-accessor-descriptor: 1.0.1
+ is-data-descriptor: 1.0.1
+
+ is-docker@3.0.0: {}
+
+ is-extendable@0.1.1: {}
+
+ is-extendable@1.0.1:
+ dependencies:
+ is-plain-object: 2.0.4
+
+ is-extglob@2.1.1: {}
+
+ is-finalizationregistry@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-generator-function@1.1.2:
+ dependencies:
+ call-bound: 1.0.4
+ generator-function: 2.0.1
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-inside-container@1.0.0:
+ dependencies:
+ is-docker: 3.0.0
+
+ is-map@2.0.3: {}
+
+ is-negative-zero@2.0.3: {}
+
+ is-number-object@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-number@3.0.0:
+ dependencies:
+ kind-of: 3.2.2
+
+ is-number@7.0.0: {}
+
+ is-plain-obj@1.1.0: {}
+
+ is-plain-obj@4.1.0: {}
+
+ is-plain-object@2.0.4:
+ dependencies:
+ isobject: 3.0.1
+
+ is-regex@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ is-retry-allowed@2.2.0: {}
+
+ is-set@2.0.3: {}
+
+ is-shared-array-buffer@1.0.4:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-stream@4.0.1: {}
+
+ is-string@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-symbol@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-symbols: 1.1.0
+ safe-regex-test: 1.1.0
+
+ is-there@4.5.2: {}
+
+ is-typed-array@1.1.15:
+ dependencies:
+ which-typed-array: 1.1.19
+
+ is-unicode-supported@2.1.0: {}
+
+ is-weakmap@2.0.2: {}
+
+ is-weakref@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-weakset@2.0.4:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ is-what@5.5.0: {}
+
+ is-windows@1.0.2: {}
+
+ is-wsl@3.1.0:
+ dependencies:
+ is-inside-container: 1.0.0
+
+ isarray@1.0.0: {}
+
+ isarray@2.0.5: {}
+
+ isexe@2.0.0: {}
+
+ isobject@2.1.0:
+ dependencies:
+ isarray: 1.0.0
+
+ isobject@3.0.1: {}
+
+ isomorphic.js@0.2.5: {}
+
+ jackspeak@3.4.3:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
+
+ jiti@2.6.1: {}
+
+ js-base64@2.6.4: {}
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@4.1.1:
+ dependencies:
+ argparse: 2.0.1
+
+ jsbarcode@3.12.1: {}
+
+ jsencrypt@3.5.4: {}
+
+ jsesc@3.1.0: {}
+
+ json-buffer@3.0.1: {}
+
+ json-schema-traverse@0.4.1: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ json5@1.0.2:
+ dependencies:
+ minimist: 1.2.8
+
+ json5@2.2.3: {}
+
+ jsonc-parser@3.3.1: {}
+
+ jsonfile@6.2.0:
+ dependencies:
+ universalify: 2.0.1
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
+ katex@0.16.27:
+ dependencies:
+ commander: 8.3.0
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ khroma@2.1.0: {}
+
+ kind-of@3.2.2:
+ dependencies:
+ is-buffer: 1.1.6
+
+ kind-of@4.0.0:
+ dependencies:
+ is-buffer: 1.1.6
+
+ kind-of@5.1.0: {}
+
+ kind-of@6.0.3: {}
+
+ kleur@3.0.3: {}
+
+ klona@2.0.6: {}
+
+ kolorist@1.8.0: {}
+
+ langium@3.3.1:
+ dependencies:
+ chevrotain: 11.0.3
+ chevrotain-allstar: 0.3.1(chevrotain@11.0.3)
+ vscode-languageserver: 9.0.1
+ vscode-languageserver-textdocument: 1.0.12
+ vscode-uri: 3.0.8
+
+ layout-base@1.0.2: {}
+
+ layout-base@2.0.1: {}
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ lib0@0.2.115:
+ dependencies:
+ isomorphic.js: 0.2.5
+
+ lie@3.1.1:
+ dependencies:
+ immediate: 3.0.6
+
+ linkify-it@5.0.0:
+ dependencies:
+ uc.micro: 2.1.0
+
+ linkifyjs@4.3.2: {}
+
+ loader-utils@1.4.2:
+ dependencies:
+ big.js: 5.2.2
+ emojis-list: 3.0.0
+ json5: 1.0.2
+
+ loadjs@4.3.0: {}
+
+ local-pkg@1.1.2:
+ dependencies:
+ mlly: 1.8.0
+ pkg-types: 2.3.0
+ quansync: 0.2.11
+
+ localforage@1.10.0:
+ dependencies:
+ lie: 3.1.1
+
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash-es@4.17.21: {}
+
+ lodash-es@4.17.22: {}
+
+ lodash.merge@4.6.2: {}
+
+ lodash@4.17.21: {}
+
+ long@5.3.2: {}
+
+ lowlight@3.3.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ devlop: 1.1.0
+ highlight.js: 11.11.1
+
+ lru-cache@10.4.3: {}
+
+ lru-cache@11.2.4: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ magic-string@0.30.11:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ magicast@0.3.4:
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+ source-map-js: 1.2.1
+
+ map-cache@0.2.2: {}
+
+ map-visit@1.0.0:
+ dependencies:
+ object-visit: 1.0.1
+
+ markdown-it@14.1.0:
+ dependencies:
+ argparse: 2.0.1
+ entities: 4.5.0
+ linkify-it: 5.0.0
+ mdurl: 2.0.0
+ punycode.js: 2.3.1
+ uc.micro: 2.1.0
+
+ marked@16.4.2: {}
+
+ math-intrinsics@1.1.0: {}
+
+ mdn-data@2.0.14: {}
+
+ mdn-data@2.12.2: {}
+
+ mdurl@2.0.0: {}
+
+ merge-options@1.0.1:
+ dependencies:
+ is-plain-obj: 1.1.0
+
+ merge2@1.4.1: {}
+
+ mermaid@11.12.2:
+ dependencies:
+ '@braintree/sanitize-url': 7.1.1
+ '@iconify/utils': 3.1.0
+ '@mermaid-js/parser': 0.6.3
+ '@types/d3': 7.4.3
+ cytoscape: 3.33.1
+ cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1)
+ cytoscape-fcose: 2.2.0(cytoscape@3.33.1)
+ d3: 7.9.0
+ d3-sankey: 0.12.3
+ dagre-d3-es: 7.0.13
+ dayjs: 1.11.19
+ dompurify: 3.3.1
+ katex: 0.16.27
+ khroma: 2.1.0
+ lodash-es: 4.17.22
+ marked: 16.4.2
+ roughjs: 4.6.6
+ stylis: 4.3.6
+ ts-dedent: 2.2.0
+ uuid: 11.1.0
+
+ micromatch@3.1.0:
+ dependencies:
+ arr-diff: 4.0.0
+ array-unique: 0.3.2
+ braces: 2.3.2
+ define-property: 1.0.0
+ extend-shallow: 2.0.1
+ extglob: 2.0.4
+ fragment-cache: 0.2.1
+ kind-of: 5.1.0
+ nanomatch: 1.2.13
+ object.pick: 1.3.0
+ regex-not: 1.0.2
+ snapdragon: 0.8.2
+ to-regex: 3.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ micromatch@4.0.7:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+ optional: true
+
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ minimatch@10.1.1:
+ dependencies:
+ '@isaacs/brace-expansion': 5.0.0
+
+ minimatch@3.1.2:
+ dependencies:
+ brace-expansion: 1.1.12
+
+ minimatch@9.0.5:
+ dependencies:
+ brace-expansion: 2.0.2
+
+ minimist@1.2.8: {}
+
+ minipass@7.1.2: {}
+
+ mitt@3.0.1: {}
+
+ mixin-deep@1.3.2:
+ dependencies:
+ for-in: 1.0.2
+ is-extendable: 1.0.1
+
+ mkdirp@3.0.1: {}
+
+ mlly@1.8.0:
+ dependencies:
+ acorn: 8.15.0
+ pathe: 2.0.3
+ pkg-types: 1.3.1
+ ufo: 1.6.1
+
+ mrmime@2.0.1: {}
+
+ ms@2.0.0: {}
+
+ ms@2.1.3: {}
+
+ muggle-string@0.4.1: {}
+
+ naive-ui@2.43.2(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ '@css-render/plugin-bem': 0.15.14(css-render@0.15.14)
+ '@css-render/vue3-ssr': 0.15.14(vue@3.5.26(typescript@5.9.3))
+ '@types/katex': 0.16.7
+ '@types/lodash': 4.17.21
+ '@types/lodash-es': 4.17.12
+ async-validator: 4.2.5
+ css-render: 0.15.14
+ csstype: 3.2.3
+ date-fns: 4.1.0
+ date-fns-tz: 3.2.0(date-fns@4.1.0)
+ evtd: 0.2.4
+ highlight.js: 11.11.1
+ lodash: 4.17.21
+ lodash-es: 4.17.22
+ seemly: 0.3.10
+ treemate: 0.3.11
+ vdirs: 0.1.8(vue@3.5.26(typescript@5.9.3))
+ vooks: 0.2.12(vue@3.5.26(typescript@5.9.3))
+ vue: 3.5.26(typescript@5.9.3)
+ vueuc: 0.4.65(vue@3.5.26(typescript@5.9.3))
+
+ nanoid@3.3.11: {}
+
+ nanoid@5.1.6: {}
+
+ nanomatch@1.2.13:
+ dependencies:
+ arr-diff: 4.0.0
+ array-unique: 0.3.2
+ define-property: 2.0.2
+ extend-shallow: 3.0.2
+ fragment-cache: 0.2.1
+ is-windows: 1.0.2
+ kind-of: 6.0.3
+ object.pick: 1.3.0
+ regex-not: 1.0.2
+ snapdragon: 0.8.2
+ to-regex: 3.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ napi-postinstall@0.3.4: {}
+
+ natural-compare@1.4.0: {}
+
+ ndarray@1.0.19:
+ dependencies:
+ iota-array: 1.0.0
+ is-buffer: 1.1.6
+
+ node-addon-api@7.1.1:
+ optional: true
+
+ node-fetch-native@1.6.7: {}
+
+ node-releases@2.0.27: {}
+
+ normalize-path@3.0.0: {}
+
+ npm-check-updates@19.2.0: {}
+
+ npm-run-path@6.0.0:
+ dependencies:
+ path-key: 4.0.0
+ unicorn-magic: 0.3.0
+
+ nprogress@0.2.0: {}
+
+ nth-check@2.1.1:
+ dependencies:
+ boolbase: 1.0.0
+
+ nypm@0.6.2:
+ dependencies:
+ citty: 0.1.6
+ consola: 3.4.2
+ pathe: 2.0.3
+ pkg-types: 2.3.0
+ tinyexec: 1.0.2
+
+ nzh@1.0.14: {}
+
+ object-assign@4.1.1: {}
+
+ object-copy@0.1.0:
+ dependencies:
+ copy-descriptor: 0.1.1
+ define-property: 0.2.5
+ kind-of: 3.2.2
+
+ object-inspect@1.13.4: {}
+
+ object-keys@1.1.1: {}
+
+ object-visit@1.0.1:
+ dependencies:
+ isobject: 3.0.1
+
+ object.assign@4.1.7:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+ has-symbols: 1.1.0
+ object-keys: 1.1.1
+
+ object.pick@1.3.0:
+ dependencies:
+ isobject: 3.0.1
+
+ ofetch@1.4.1:
+ dependencies:
+ destr: 2.0.5
+ node-fetch-native: 1.6.7
+ ufo: 1.6.1
+
+ ofetch@1.5.1:
+ dependencies:
+ destr: 2.0.5
+ node-fetch-native: 1.6.7
+ ufo: 1.6.1
+
+ ohash@2.0.11: {}
+
+ onnxruntime-common@1.18.0: {}
+
+ onnxruntime-web@1.18.0:
+ dependencies:
+ flatbuffers: 1.12.0
+ guid-typescript: 1.0.9
+ long: 5.3.2
+ onnxruntime-common: 1.18.0
+ platform: 1.3.6
+ protobufjs: 7.5.4
+
+ open@10.2.0:
+ dependencies:
+ default-browser: 5.4.0
+ define-lazy-prop: 3.0.0
+ is-inside-container: 1.0.0
+ wsl-utils: 0.1.0
+
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
+ orderedmap@2.1.1: {}
+
+ own-keys@1.0.1:
+ dependencies:
+ get-intrinsic: 1.3.0
+ object-keys: 1.1.1
+ safe-push-apply: 1.0.0
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ package-json-from-dist@1.0.1: {}
+
+ package-manager-detector@1.6.0: {}
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ parse-ms@4.0.0: {}
+
+ pascalcase@0.1.1: {}
+
+ path-browserify@1.0.1: {}
+
+ path-data-parser@0.1.0: {}
+
+ path-exists@4.0.0: {}
+
+ path-key@3.1.1: {}
+
+ path-key@4.0.0: {}
+
+ path-scurry@1.11.1:
+ dependencies:
+ lru-cache: 10.4.3
+ minipass: 7.1.2
+
+ path-scurry@2.0.1:
+ dependencies:
+ lru-cache: 11.2.4
+ minipass: 7.1.2
+
+ pathe@0.2.0: {}
+
+ pathe@2.0.3: {}
+
+ pdfjs-dist@5.4.296:
+ optionalDependencies:
+ '@napi-rs/canvas': 0.1.86
+
+ perfect-debounce@1.0.0: {}
+
+ perfect-debounce@2.0.0: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@2.3.1: {}
+
+ picomatch@4.0.3: {}
+
+ pinia@3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ '@vue/devtools-api': 7.7.9
+ vue: 3.5.26(typescript@5.9.3)
+ optionalDependencies:
+ typescript: 5.9.3
+
+ pkg-types@1.3.1:
+ dependencies:
+ confbox: 0.1.8
+ mlly: 1.8.0
+ pathe: 2.0.3
+
+ pkg-types@2.3.0:
+ dependencies:
+ confbox: 0.2.2
+ exsolve: 1.0.8
+ pathe: 2.0.3
+
+ platform@1.3.6: {}
+
+ pluralize@8.0.0: {}
+
+ plyr@3.8.3:
+ dependencies:
+ core-js: 3.47.0
+ custom-event-polyfill: 1.0.7
+ loadjs: 4.3.0
+ rangetouch: 2.0.1
+ url-polyfill: 1.1.14
+
+ points-on-curve@0.2.0: {}
+
+ points-on-path@0.2.1:
+ dependencies:
+ path-data-parser: 0.1.0
+ points-on-curve: 0.2.0
+
+ posix-character-classes@0.1.1: {}
+
+ possible-typed-array-names@1.1.0: {}
+
+ postcss-modules-extract-imports@3.1.0(postcss@8.5.6):
+ dependencies:
+ postcss: 8.5.6
+
+ postcss-modules-local-by-default@4.2.0(postcss@8.5.6):
+ dependencies:
+ icss-utils: 5.1.0(postcss@8.5.6)
+ postcss: 8.5.6
+ postcss-selector-parser: 7.1.1
+ postcss-value-parser: 4.2.0
+
+ postcss-modules-scope@3.2.1(postcss@8.5.6):
+ dependencies:
+ postcss: 8.5.6
+ postcss-selector-parser: 7.1.1
+
+ postcss-modules-values@4.0.0(postcss@8.5.6):
+ dependencies:
+ icss-utils: 5.1.0(postcss@8.5.6)
+ postcss: 8.5.6
+
+ postcss-prefix-selector@1.16.1(postcss@5.2.18):
+ dependencies:
+ postcss: 5.2.18
+
+ postcss-scss@4.0.9(postcss@8.5.6):
+ dependencies:
+ postcss: 8.5.6
+
+ postcss-selector-parser@7.1.1:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-value-parser@4.2.0: {}
+
+ postcss@5.2.18:
+ dependencies:
+ chalk: 1.1.3
+ js-base64: 2.6.4
+ source-map: 0.5.7
+ supports-color: 3.2.3
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ posthtml-parser@0.2.1:
+ dependencies:
+ htmlparser2: 3.10.1
+ isobject: 2.1.0
+
+ posthtml-rename-id@1.0.12:
+ dependencies:
+ escape-string-regexp: 1.0.5
+
+ posthtml-render@1.4.0: {}
+
+ posthtml-svg-mode@1.0.3:
+ dependencies:
+ merge-options: 1.0.1
+ posthtml: 0.9.2
+ posthtml-parser: 0.2.1
+ posthtml-render: 1.4.0
+
+ posthtml@0.9.2:
+ dependencies:
+ posthtml-parser: 0.2.1
+ posthtml-render: 1.4.0
+
+ prelude-ls@1.2.1: {}
+
+ prettier-linter-helpers@1.0.0:
+ dependencies:
+ fast-diff: 1.3.0
+
+ prettier-plugin-json-sort@0.0.2(prettier@3.7.4):
+ dependencies:
+ prettier: 3.7.4
+
+ prettier@3.3.3: {}
+
+ prettier@3.7.4: {}
+
+ pretty-bytes@6.1.1: {}
+
+ pretty-ms@9.3.0:
+ dependencies:
+ parse-ms: 4.0.0
+
+ progress@2.0.3: {}
+
+ prompts@2.4.2:
+ dependencies:
+ kleur: 3.0.3
+ sisteransi: 1.0.5
+
+ prosemirror-changeset@2.3.1:
+ dependencies:
+ prosemirror-transform: 1.10.5
+
+ prosemirror-collab@1.3.1:
+ dependencies:
+ prosemirror-state: 1.4.4
+
+ prosemirror-commands@1.7.1:
+ dependencies:
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+
+ prosemirror-dropcursor@1.8.2:
+ dependencies:
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.4
+
+ prosemirror-gapcursor@1.4.0:
+ dependencies:
+ prosemirror-keymap: 1.2.3
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-view: 1.41.4
+
+ prosemirror-history@1.5.0:
+ dependencies:
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.4
+ rope-sequence: 1.3.4
+
+ prosemirror-inputrules@1.5.1:
+ dependencies:
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+
+ prosemirror-keymap@1.2.3:
+ dependencies:
+ prosemirror-state: 1.4.4
+ w3c-keyname: 2.2.8
+
+ prosemirror-markdown@1.13.2:
+ dependencies:
+ '@types/markdown-it': 14.1.2
+ markdown-it: 14.1.0
+ prosemirror-model: 1.25.4
+
+ prosemirror-menu@1.2.5:
+ dependencies:
+ crelt: 1.0.6
+ prosemirror-commands: 1.7.1
+ prosemirror-history: 1.5.0
+ prosemirror-state: 1.4.4
+
+ prosemirror-model@1.25.4:
+ dependencies:
+ orderedmap: 2.1.1
+
+ prosemirror-schema-basic@1.2.4:
+ dependencies:
+ prosemirror-model: 1.25.4
+
+ prosemirror-schema-list@1.5.1:
+ dependencies:
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+
+ prosemirror-state@1.4.4:
+ dependencies:
+ prosemirror-model: 1.25.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.4
+
+ prosemirror-tables@1.8.4:
+ dependencies:
+ prosemirror-keymap: 1.2.3
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.4
+
+ prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4):
+ dependencies:
+ '@remirror/core-constants': 3.0.0
+ escape-string-regexp: 4.0.0
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-view: 1.41.4
+
+ prosemirror-transform@1.10.5:
+ dependencies:
+ prosemirror-model: 1.25.4
+
+ prosemirror-view@1.41.4:
+ dependencies:
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+
+ protobufjs@7.5.4:
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+ '@protobufjs/base64': 1.1.2
+ '@protobufjs/codegen': 2.0.4
+ '@protobufjs/eventemitter': 1.1.0
+ '@protobufjs/fetch': 1.1.0
+ '@protobufjs/float': 1.0.2
+ '@protobufjs/inquire': 1.1.0
+ '@protobufjs/path': 1.1.2
+ '@protobufjs/pool': 1.1.0
+ '@protobufjs/utf8': 1.1.0
+ '@types/node': 25.0.3
+ long: 5.3.2
+
+ proxy-from-env@1.1.0: {}
+
+ punycode.js@2.3.1: {}
+
+ punycode@2.3.1: {}
+
+ pure-svg-code@1.0.6: {}
+
+ qrcode-svg@1.1.0: {}
+
+ qs@6.14.0:
+ dependencies:
+ side-channel: 1.1.0
+
+ quansync@0.2.11: {}
+
+ quansync@1.0.0: {}
+
+ query-string@4.3.4:
+ dependencies:
+ object-assign: 4.1.1
+ strict-uri-encode: 1.1.0
+
+ queue-microtask@1.2.3: {}
+
+ rangetouch@2.0.1: {}
+
+ rate-limiter-flexible@5.0.5: {}
+
+ rc9@2.1.2:
+ dependencies:
+ defu: 6.1.4
+ destr: 2.0.5
+
+ rd@2.0.1:
+ dependencies:
+ '@types/node': 10.17.60
+
+ readable-stream@3.6.2:
+ dependencies:
+ inherits: 2.0.4
+ string_decoder: 1.3.0
+ util-deprecate: 1.0.2
+
+ readdirp@3.6.0:
+ dependencies:
+ picomatch: 2.3.1
+
+ readdirp@4.1.2: {}
+
+ readdirp@5.0.0: {}
+
+ recast@0.23.9:
+ dependencies:
+ ast-types: 0.16.1
+ esprima: 4.0.1
+ source-map: 0.6.1
+ tiny-invariant: 1.3.3
+ tslib: 2.8.1
+
+ reflect.getprototypeof@1.0.10:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ which-builtin-type: 1.2.1
+
+ regex-not@1.0.2:
+ dependencies:
+ extend-shallow: 3.0.2
+ safe-regex: 1.1.0
+
+ regexp-tree@0.1.27: {}
+
+ regexp.prototype.flags@1.5.4:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-errors: 1.3.0
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ set-function-name: 2.0.2
+
+ regjsparser@0.13.0:
+ dependencies:
+ jsesc: 3.1.0
+
+ repeat-element@1.1.4: {}
+
+ repeat-string@1.6.1: {}
+
+ require-directory@2.1.1: {}
+
+ resolve-from@4.0.0: {}
+
+ resolve-pkg-maps@1.0.0: {}
+
+ resolve-url@0.2.1: {}
+
+ ret@0.1.15: {}
+
+ reusify@1.1.0: {}
+
+ rfdc@1.4.1: {}
+
+ rimraf@6.1.2:
+ dependencies:
+ glob: 13.0.0
+ package-json-from-dist: 1.0.1
+
+ robust-predicates@3.0.2: {}
+
+ rollup@4.54.0:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.54.0
+ '@rollup/rollup-android-arm64': 4.54.0
+ '@rollup/rollup-darwin-arm64': 4.54.0
+ '@rollup/rollup-darwin-x64': 4.54.0
+ '@rollup/rollup-freebsd-arm64': 4.54.0
+ '@rollup/rollup-freebsd-x64': 4.54.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.54.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.54.0
+ '@rollup/rollup-linux-arm64-gnu': 4.54.0
+ '@rollup/rollup-linux-arm64-musl': 4.54.0
+ '@rollup/rollup-linux-loong64-gnu': 4.54.0
+ '@rollup/rollup-linux-ppc64-gnu': 4.54.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.54.0
+ '@rollup/rollup-linux-riscv64-musl': 4.54.0
+ '@rollup/rollup-linux-s390x-gnu': 4.54.0
+ '@rollup/rollup-linux-x64-gnu': 4.54.0
+ '@rollup/rollup-linux-x64-musl': 4.54.0
+ '@rollup/rollup-openharmony-arm64': 4.54.0
+ '@rollup/rollup-win32-arm64-msvc': 4.54.0
+ '@rollup/rollup-win32-ia32-msvc': 4.54.0
+ '@rollup/rollup-win32-x64-gnu': 4.54.0
+ '@rollup/rollup-win32-x64-msvc': 4.54.0
+ fsevents: 2.3.3
+
+ rope-sequence@1.3.4: {}
+
+ roughjs@4.6.6:
+ dependencies:
+ hachure-fill: 0.5.2
+ path-data-parser: 0.1.0
+ points-on-curve: 0.2.0
+ points-on-path: 0.2.1
+
+ run-applescript@7.1.0: {}
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ rw@1.3.3: {}
+
+ safe-array-concat@1.1.3:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ has-symbols: 1.1.0
+ isarray: 2.0.5
+
+ safe-buffer@5.2.1: {}
+
+ safe-push-apply@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ isarray: 2.0.5
+
+ safe-regex-test@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-regex: 1.2.1
+
+ safe-regex@1.1.0:
+ dependencies:
+ ret: 0.1.15
+
+ safer-buffer@2.1.2: {}
+
+ sass@1.97.1:
+ dependencies:
+ chokidar: 4.0.3
+ immutable: 5.1.4
+ source-map-js: 1.2.1
+ optionalDependencies:
+ '@parcel/watcher': 2.5.1
+
+ seemly@0.3.10: {}
+
+ select@1.1.2: {}
+
+ semver@6.3.1: {}
+
+ semver@7.7.2: {}
+
+ semver@7.7.3: {}
+
+ set-function-length@1.2.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+
+ set-function-name@2.0.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.2
+
+ set-proto@1.0.0:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+
+ set-value@2.0.1:
+ dependencies:
+ extend-shallow: 2.0.1
+ is-extendable: 0.1.1
+ is-plain-object: 2.0.4
+ split-string: 3.1.0
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ side-channel-list@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-map@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-weakmap@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-map: 1.0.1
+
+ side-channel@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-list: 1.0.0
+ side-channel-map: 1.0.1
+ side-channel-weakmap: 1.0.2
+
+ sign-canvas-plus@2.0.3(typescript@5.9.3):
+ dependencies:
+ vue: 3.5.26(typescript@5.9.3)
+ transitivePeerDependencies:
+ - typescript
+
+ signal-exit@4.1.0: {}
+
+ simple-git-hooks@2.13.1: {}
+
+ simplebar-core@1.3.2:
+ dependencies:
+ lodash: 4.17.21
+ lodash-es: 4.17.22
+
+ simplebar-vue@2.4.2(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ simplebar-core: 1.3.2
+ vue: 3.5.26(typescript@5.9.3)
+ vue-demi: 0.13.11(vue@3.5.26(typescript@5.9.3))
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+
+ sirv@3.0.2:
+ dependencies:
+ '@polka/url': 1.0.0-next.29
+ mrmime: 2.0.1
+ totalist: 3.0.1
+
+ sisteransi@1.0.5: {}
+
+ smooth-signature@1.1.0: {}
+
+ snapdragon-node@2.1.1:
+ dependencies:
+ define-property: 1.0.0
+ isobject: 3.0.1
+ snapdragon-util: 3.0.1
+
+ snapdragon-util@3.0.1:
+ dependencies:
+ kind-of: 3.2.2
+
+ snapdragon@0.8.2:
+ dependencies:
+ base: 0.11.2
+ debug: 2.6.9
+ define-property: 0.2.5
+ extend-shallow: 2.0.1
+ map-cache: 0.2.2
+ source-map: 0.5.7
+ source-map-resolve: 0.5.3
+ use: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ source-map-js@1.2.1: {}
+
+ source-map-resolve@0.5.3:
+ dependencies:
+ atob: 2.1.2
+ decode-uri-component: 0.2.2
+ resolve-url: 0.2.1
+ source-map-url: 0.4.1
+ urix: 0.1.0
+
+ source-map-url@0.4.1: {}
+
+ source-map@0.5.7: {}
+
+ source-map@0.6.1: {}
+
+ speakingurl@14.0.1: {}
+
+ split-string@3.1.0:
+ dependencies:
+ extend-shallow: 3.0.2
+
+ stable-hash-x@0.2.0: {}
+
+ stable@0.1.8: {}
+
+ static-extend@0.1.2:
+ dependencies:
+ define-property: 0.2.5
+ object-copy: 0.1.0
+
+ stop-iteration-iterator@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ internal-slot: 1.1.0
+
+ streamsaver@2.0.6: {}
+
+ strict-uri-encode@1.1.0: {}
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string-width@5.1.2:
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 9.2.2
+ strip-ansi: 7.1.2
+
+ string.prototype.trim@1.2.10:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-data-property: 1.1.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-object-atoms: 1.1.1
+ has-property-descriptors: 1.0.2
+
+ string.prototype.trimend@1.0.9:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ string.prototype.trimstart@1.0.8:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ string_decoder@1.3.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ strip-ansi@3.0.1:
+ dependencies:
+ ansi-regex: 2.1.1
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-ansi@7.1.2:
+ dependencies:
+ ansi-regex: 6.2.2
+
+ strip-final-newline@4.0.0: {}
+
+ strip-indent@4.1.1: {}
+
+ strip-json-comments@3.1.1: {}
+
+ stylis@4.3.6: {}
+
+ superjson@2.2.6:
+ dependencies:
+ copy-anything: 4.0.5
+
+ supports-color@2.0.0: {}
+
+ supports-color@3.2.3:
+ dependencies:
+ has-flag: 1.0.0
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ svelte-eslint-parser@1.4.1:
+ dependencies:
+ eslint-scope: 8.4.0
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ postcss: 8.5.6
+ postcss-scss: 4.0.9(postcss@8.5.6)
+ postcss-selector-parser: 7.1.1
+
+ svg-baker@1.7.0:
+ dependencies:
+ bluebird: 3.7.2
+ clone: 2.1.2
+ he: 1.2.0
+ image-size: 0.5.5
+ loader-utils: 1.4.2
+ merge-options: 1.0.1
+ micromatch: 3.1.0
+ postcss: 5.2.18
+ postcss-prefix-selector: 1.16.1(postcss@5.2.18)
+ posthtml-rename-id: 1.0.12
+ posthtml-svg-mode: 1.0.3
+ query-string: 4.3.4
+ traverse: 0.6.11
+ transitivePeerDependencies:
+ - supports-color
+
+ svg64@2.0.0: {}
+
+ svgo@2.8.0:
+ dependencies:
+ '@trysound/sax': 0.2.0
+ commander: 7.2.0
+ css-select: 4.3.0
+ css-tree: 1.1.3
+ csso: 4.2.0
+ picocolors: 1.1.1
+ stable: 0.1.8
+
+ synckit@0.11.11:
+ dependencies:
+ '@pkgr/core': 0.2.9
+
+ tailwind-merge@3.4.0: {}
+
+ tapable@2.3.0: {}
+
+ tiny-emitter@2.1.0: {}
+
+ tiny-invariant@1.3.3: {}
+
+ tinyexec@1.0.2: {}
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ tippy.js@6.3.7:
+ dependencies:
+ '@popperjs/core': 2.11.8
+
+ to-object-path@0.3.0:
+ dependencies:
+ kind-of: 3.2.2
+
+ to-regex-range@2.1.1:
+ dependencies:
+ is-number: 3.0.0
+ repeat-string: 1.6.1
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ to-regex@3.0.2:
+ dependencies:
+ define-property: 2.0.2
+ extend-shallow: 3.0.2
+ regex-not: 1.0.2
+ safe-regex: 1.1.0
+
+ totalist@3.0.1: {}
+
+ traverse@0.6.11:
+ dependencies:
+ gopd: 1.2.0
+ typedarray.prototype.slice: 1.0.5
+ which-typed-array: 1.1.19
+
+ treemate@0.3.11: {}
+
+ ts-api-utils@2.1.0(typescript@5.9.3):
+ dependencies:
+ typescript: 5.9.3
+
+ ts-declaration-location@1.0.7(typescript@5.9.3):
+ dependencies:
+ picomatch: 4.0.3
+ typescript: 5.9.3
+
+ ts-dedent@2.2.0: {}
+
+ tslib@2.3.0: {}
+
+ tslib@2.8.1: {}
+
+ tsx@4.21.0:
+ dependencies:
+ esbuild: 0.27.2
+ get-tsconfig: 4.13.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
+ typed-array-buffer@1.0.3:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-length@1.0.3:
+ dependencies:
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-offset@1.0.4:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+ reflect.getprototypeof: 1.0.10
+
+ typed-array-length@1.0.7:
+ dependencies:
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ is-typed-array: 1.1.15
+ possible-typed-array-names: 1.1.0
+ reflect.getprototypeof: 1.0.10
+
+ typed-css-modules@0.9.1:
+ dependencies:
+ camelcase: 6.3.0
+ chalk: 4.1.2
+ chokidar: 3.6.0
+ glob: 10.5.0
+ icss-replace-symbols: 1.1.0
+ is-there: 4.5.2
+ mkdirp: 3.0.1
+ postcss: 8.5.6
+ postcss-modules-extract-imports: 3.1.0(postcss@8.5.6)
+ postcss-modules-local-by-default: 4.2.0(postcss@8.5.6)
+ postcss-modules-scope: 3.2.1(postcss@8.5.6)
+ postcss-modules-values: 4.0.0(postcss@8.5.6)
+ yargs: 17.7.2
+
+ typedarray.prototype.slice@1.0.5:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ get-proto: 1.0.1
+ math-intrinsics: 1.1.0
+ typed-array-buffer: 1.0.3
+ typed-array-byte-offset: 1.0.4
+
+ typescript@5.9.3: {}
+
+ uc.micro@2.1.0: {}
+
+ ufo@1.6.1: {}
+
+ unbox-primitive@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-bigints: 1.1.0
+ has-symbols: 1.1.0
+ which-boxed-primitive: 1.1.1
+
+ unconfig-core@7.4.2:
+ dependencies:
+ '@quansync/fs': 1.0.0
+ quansync: 1.0.0
+
+ unconfig@7.4.2:
+ dependencies:
+ '@quansync/fs': 1.0.0
+ defu: 6.1.4
+ jiti: 2.6.1
+ quansync: 1.0.0
+ unconfig-core: 7.4.2
+
+ undici-types@7.16.0: {}
+
+ unicorn-magic@0.3.0: {}
+
+ union-value@1.0.1:
+ dependencies:
+ arr-union: 3.1.0
+ get-value: 2.0.6
+ is-extendable: 0.1.1
+ set-value: 2.0.1
+
+ universalify@2.0.1: {}
+
+ unplugin-icons@22.5.0(@vue/compiler-sfc@3.5.26):
+ dependencies:
+ '@antfu/install-pkg': 1.1.0
+ '@iconify/utils': 3.1.0
+ debug: 4.4.3
+ local-pkg: 1.1.2
+ unplugin: 2.3.11
+ optionalDependencies:
+ '@vue/compiler-sfc': 3.5.26
+ transitivePeerDependencies:
+ - supports-color
+
+ unplugin-utils@0.3.1:
+ dependencies:
+ pathe: 2.0.3
+ picomatch: 4.0.3
+
+ unplugin-vue-components@30.0.0(@babel/parser@7.28.5)(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ chokidar: 4.0.3
+ debug: 4.4.3
+ local-pkg: 1.1.2
+ magic-string: 0.30.21
+ mlly: 1.8.0
+ tinyglobby: 0.2.15
+ unplugin: 2.3.11
+ unplugin-utils: 0.3.1
+ vue: 3.5.26(typescript@5.9.3)
+ optionalDependencies:
+ '@babel/parser': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ unplugin@1.12.0:
+ dependencies:
+ acorn: 8.15.0
+ chokidar: 3.6.0
+ webpack-sources: 3.3.3
+ webpack-virtual-modules: 0.6.2
+
+ unplugin@2.3.11:
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ acorn: 8.15.0
+ picomatch: 4.0.3
+ webpack-virtual-modules: 0.6.2
+
+ unrs-resolver@1.11.1:
+ dependencies:
+ napi-postinstall: 0.3.4
+ optionalDependencies:
+ '@unrs/resolver-binding-android-arm-eabi': 1.11.1
+ '@unrs/resolver-binding-android-arm64': 1.11.1
+ '@unrs/resolver-binding-darwin-arm64': 1.11.1
+ '@unrs/resolver-binding-darwin-x64': 1.11.1
+ '@unrs/resolver-binding-freebsd-x64': 1.11.1
+ '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1
+ '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1
+ '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-arm64-musl': 1.11.1
+ '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1
+ '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-x64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-x64-musl': 1.11.1
+ '@unrs/resolver-binding-wasm32-wasi': 1.11.1
+ '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1
+ '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1
+ '@unrs/resolver-binding-win32-x64-msvc': 1.11.1
+
+ unset-value@1.0.0:
+ dependencies:
+ has-value: 0.3.1
+ isobject: 3.0.1
+
+ update-browserslist-db@1.2.3(browserslist@4.28.1):
+ dependencies:
+ browserslist: 4.28.1
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
+ urix@0.1.0: {}
+
+ url-polyfill@1.1.14: {}
+
+ use@3.1.1: {}
+
+ util-deprecate@1.0.2: {}
+
+ uuid@11.1.0: {}
+
+ vary@1.1.2: {}
+
+ vdirs@0.1.8(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ evtd: 0.2.4
+ vue: 3.5.26(typescript@5.9.3)
+
+ vite-dev-rpc@1.1.0(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)):
+ dependencies:
+ birpc: 2.9.0
+ vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite-hot-client: 2.1.0(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))
+
+ vite-hot-client@2.1.0(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)):
+ dependencies:
+ vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)
+
+ vite-plugin-inspect@11.3.3(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)):
+ dependencies:
+ ansis: 4.2.0
+ debug: 4.4.3
+ error-stack-parser-es: 1.0.5
+ ohash: 2.0.11
+ open: 10.2.0
+ perfect-debounce: 2.0.0
+ sirv: 3.0.2
+ unplugin-utils: 0.3.1
+ vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite-dev-rpc: 1.1.0(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))
+ transitivePeerDependencies:
+ - supports-color
+
+ vite-plugin-progress@0.0.7(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)):
+ dependencies:
+ picocolors: 1.1.1
+ progress: 2.0.3
+ rd: 2.0.1
+ vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)
+
+ vite-plugin-svg-icons@2.0.1(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)):
+ dependencies:
+ '@types/svgo': 2.6.4
+ cors: 2.8.5
+ debug: 4.4.3
+ etag: 1.8.1
+ fs-extra: 10.1.0
+ pathe: 0.2.0
+ svg-baker: 1.7.0
+ svgo: 2.8.0
+ vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)
+ transitivePeerDependencies:
+ - supports-color
+
+ vite-plugin-vue-devtools@8.0.5(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ '@vue/devtools-core': 8.0.5(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))
+ '@vue/devtools-kit': 8.0.5
+ '@vue/devtools-shared': 8.0.5
+ sirv: 3.0.2
+ vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite-plugin-inspect: 11.3.3(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))
+ vite-plugin-vue-inspector: 5.3.2(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2))
+ transitivePeerDependencies:
+ - '@nuxt/kit'
+ - supports-color
+ - vue
+
+ vite-plugin-vue-inspector@5.3.2(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)):
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.5)
+ '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5)
+ '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.5)
+ '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5)
+ '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.5)
+ '@vue/compiler-dom': 3.5.26
+ kolorist: 1.8.0
+ magic-string: 0.30.21
+ vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2)
+ transitivePeerDependencies:
+ - supports-color
+
+ vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(sass@1.97.1)(tsx@4.21.0)(yaml@2.8.2):
+ dependencies:
+ esbuild: 0.27.2
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.54.0
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 25.0.3
+ fsevents: 2.3.3
+ jiti: 2.6.1
+ sass: 1.97.1
+ tsx: 4.21.0
+ yaml: 2.8.2
+
+ vooks@0.2.12(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ evtd: 0.2.4
+ vue: 3.5.26(typescript@5.9.3)
+
+ vscode-jsonrpc@8.2.0: {}
+
+ vscode-languageserver-protocol@3.17.5:
+ dependencies:
+ vscode-jsonrpc: 8.2.0
+ vscode-languageserver-types: 3.17.5
+
+ vscode-languageserver-textdocument@1.0.12: {}
+
+ vscode-languageserver-types@3.17.5: {}
+
+ vscode-languageserver@9.0.1:
+ dependencies:
+ vscode-languageserver-protocol: 3.17.5
+
+ vscode-uri@3.0.8: {}
+
+ vscode-uri@3.1.0: {}
+
+ vue-advanced-cropper@2.8.9(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ classnames: 2.5.1
+ debounce: 1.2.1
+ easy-bem: 1.1.1
+ vue: 3.5.26(typescript@5.9.3)
+
+ vue-demi@0.13.11(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ vue: 3.5.26(typescript@5.9.3)
+
+ vue-demi@0.14.10(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ vue: 3.5.26(typescript@5.9.3)
+
+ vue-draggable-plus@0.6.0(@types/sortablejs@1.15.9):
+ dependencies:
+ '@types/sortablejs': 1.15.9
+
+ vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1)):
+ dependencies:
+ debug: 4.4.3
+ eslint: 9.39.2(jiti@2.6.1)
+ eslint-scope: 8.4.0
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ esquery: 1.6.0
+ semver: 7.7.3
+ transitivePeerDependencies:
+ - supports-color
+
+ vue-flow-layout@0.2.0: {}
+
+ vue-i18n@10.0.8(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ '@intlify/core-base': 10.0.8
+ '@intlify/shared': 10.0.8
+ '@vue/devtools-api': 6.6.4
+ vue: 3.5.26(typescript@5.9.3)
+
+ vue-i18n@11.2.7(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ '@intlify/core-base': 11.2.7
+ '@intlify/shared': 11.2.7
+ '@vue/devtools-api': 6.6.4
+ vue: 3.5.26(typescript@5.9.3)
+
+ vue-router@4.6.4(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ '@vue/devtools-api': 6.6.4
+ vue: 3.5.26(typescript@5.9.3)
+
+ vue-tsc@3.2.1(typescript@5.9.3):
+ dependencies:
+ '@volar/typescript': 2.4.27
+ '@vue/language-core': 3.2.1
+ typescript: 5.9.3
+
+ vue@3.5.26(typescript@5.9.3):
+ dependencies:
+ '@vue/compiler-dom': 3.5.26
+ '@vue/compiler-sfc': 3.5.26
+ '@vue/runtime-dom': 3.5.26
+ '@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@5.9.3))
+ '@vue/shared': 3.5.26
+ optionalDependencies:
+ typescript: 5.9.3
+
+ vueuc@0.4.65(vue@3.5.26(typescript@5.9.3)):
+ dependencies:
+ '@css-render/vue3-ssr': 0.15.14(vue@3.5.26(typescript@5.9.3))
+ '@juggle/resize-observer': 3.4.0
+ css-render: 0.15.14
+ evtd: 0.2.4
+ seemly: 0.3.10
+ vdirs: 0.1.8(vue@3.5.26(typescript@5.9.3))
+ vooks: 0.2.12(vue@3.5.26(typescript@5.9.3))
+ vue: 3.5.26(typescript@5.9.3)
+
+ w3c-keyname@2.2.8: {}
+
+ webpack-sources@3.3.3: {}
+
+ webpack-virtual-modules@0.6.2: {}
+
+ which-boxed-primitive@1.1.1:
+ dependencies:
+ is-bigint: 1.1.0
+ is-boolean-object: 1.2.2
+ is-number-object: 1.1.1
+ is-string: 1.1.1
+ is-symbol: 1.1.1
+
+ which-builtin-type@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ function.prototype.name: 1.1.8
+ has-tostringtag: 1.0.2
+ is-async-function: 2.1.1
+ is-date-object: 1.1.0
+ is-finalizationregistry: 1.1.1
+ is-generator-function: 1.1.2
+ is-regex: 1.2.1
+ is-weakref: 1.1.1
+ isarray: 2.0.5
+ which-boxed-primitive: 1.1.1
+ which-collection: 1.0.2
+ which-typed-array: 1.1.19
+
+ which-collection@1.0.2:
+ dependencies:
+ is-map: 2.0.3
+ is-set: 2.0.3
+ is-weakmap: 2.0.2
+ is-weakset: 2.0.4
+
+ which-typed-array@1.1.19:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ for-each: 0.3.5
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ word-wrap@1.2.5: {}
+
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrap-ansi@8.1.0:
+ dependencies:
+ ansi-styles: 6.2.3
+ string-width: 5.1.2
+ strip-ansi: 7.1.2
+
+ wsl-utils@0.1.0:
+ dependencies:
+ is-wsl: 3.1.0
+
+ xml-name-validator@4.0.0: {}
+
+ y-prosemirror@1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)(y-protocols@1.0.7(yjs@13.6.28))(yjs@13.6.28):
+ dependencies:
+ lib0: 0.2.115
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-view: 1.41.4
+ y-protocols: 1.0.7(yjs@13.6.28)
+ yjs: 13.6.28
+
+ y-protocols@1.0.7(yjs@13.6.28):
+ dependencies:
+ lib0: 0.2.115
+ yjs: 13.6.28
+
+ y18n@5.0.8: {}
+
+ yallist@3.1.1: {}
+
+ yaml@2.8.2: {}
+
+ yargs-parser@21.1.1: {}
+
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
+ yjs@13.6.28:
+ dependencies:
+ lib0: 0.2.115
+
+ yocto-queue@0.1.0: {}
+
+ yoctocolors@2.1.2: {}
+
+ zod@3.25.76: {}
+
+ zod@4.2.1: {}
+
+ zrender@5.6.1:
+ dependencies:
+ tslib: 2.3.0
+
+ zrender@6.0.0:
+ dependencies:
+ tslib: 2.3.0
diff --git a/ruoyi-plus-soybean/pnpm-workspace.yaml b/ruoyi-plus-soybean/pnpm-workspace.yaml
new file mode 100755
index 0000000..dee51e9
--- /dev/null
+++ b/ruoyi-plus-soybean/pnpm-workspace.yaml
@@ -0,0 +1,2 @@
+packages:
+ - "packages/*"
diff --git a/ruoyi-plus-soybean/public/favicon.svg b/ruoyi-plus-soybean/public/favicon.svg
new file mode 100755
index 0000000..1b50f85
--- /dev/null
+++ b/ruoyi-plus-soybean/public/favicon.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 476.22 476.22">
+ <path fill="#0e42d2" d="M389.98,400.6c-64.58,76.59-176.66,93.98-261.1,38.54-57.75-37.91-92.29-103.74-87.69-173.54,1.28-19.35,9.54-58.77,33.33-61.44,20.49-2.29,43.2,14.41,62.99,21.14,64.93,22.1,150.88,21.39,214.51-4.81,17.24-7.1,39.23-23.9,58.05-13.31,25.61,14.41,27.13,67.07,24.38,92.75-3.94,36.78-20.9,72.7-44.47,100.65ZM150.91,269c-17.4-4.01-34.19-9.87-50.5-17.04-2.56-1.12-15.17-8.19-16.64-6.83-23.51,92.25,48.81,179.87,140.64,188.01,3.68.33,9,.72,12.64.79,3.76.07,7.18-.41,10.86-.78-.24-3.7-1.05-7.05-.77-10.85,1.3-17.71,28.61-26.36,26.88-43.25-.89-8.76-14.85-11.03-22.02-11.55-8.23-.6-17.94,1.77-25.24-3.11-5.39-3.61-5.08-10.32-7.37-15.76-4.74-11.26-15.86-17.54-27.79-18.47-22.55-1.77-68.52,7.5-81.87-16.63-11.77-21.27,12.24-35.1,29.25-40.42,3.38-1.06,7.52-1.31,10.88-2.55,1.03-.38,1.28.27,1.04-1.56ZM391.83,245.21c-1.03-.58-12.51,5.5-14.65,6.42-20.82,8.92-42.01,16.02-64.2,20.87-10.31,2.25-21.7,3.15-31.66,5.65-19.17,4.82-17.59,22.78-.42,29.11,10.27,3.78,25.34,3.94,36.56,5.97,23.56,4.26,56.74,11.28,53.56,41.93-.31,2.98-2.13,5.86-2.26,8.2-.08,1.48.18,1.21,1.5,1.13,5.18-9.87,11.46-19.09,15.7-29.46,7.55-18.44,12.19-42.75,11.17-62.68-.19-3.67-2.95-25.81-5.31-27.15Z"/>
+ <path fill="#0e42d2" d="M278.22,21.54c4.79,5.69,9.27,14.39,11.84,21.37.67,1.8,1.49,7.86,2.59,8.61,1.49,1.01,17.78-3.63,21.39-4.05,71.76-8.37,101.88,65.14,44.46,110.3-41.92,32.98-84.43,32.67-135.62,31.41-47.74-1.18-111.25-13.52-129.97-64.78-19.66-53.81,31.01-83.19,77.15-58.5,2,1.07,11.58,7.79,12.54,7.59,1.57-.32,1.05-4.62,1.34-6.14,3.75-19.76,11.37-39.47,27.8-52.04,20.82-15.93,49.31-14.16,66.47,6.23ZM256.71,71.1c1.07-24.31-18.03-42.71-30.8-12.52-5.88,13.9-7.77,32.56-3.36,47,1.73.46,1.24-.41,1.7-.95,10.15-11.8,19.02-25.11,32.46-33.52ZM340.18,123.96c10.12-10.37,17.92-27.48.94-35.85-29-14.31-83.74,17.89-87.91,49.24-1.6,11.97,2.2,14.36,13.71,14.53,21.84.32,57.86-12.13,73.26-27.92ZM173.64,117.92c-4.77-6.36-9.81-11.55-16.53-15.93-7.07-4.62-24.6-13.3-29.47-2.16-3.94,9.02,6.8,23.05,13.43,28.86,12.36,10.83,29.63,16.76,45.66,19.42-.28-11.02-6.62-21.58-13.08-30.19Z"/>
+</svg>
diff --git a/ruoyi-plus-soybean/public/streamsaver/mitm.html b/ruoyi-plus-soybean/public/streamsaver/mitm.html
new file mode 100755
index 0000000..f6d94e2
--- /dev/null
+++ b/ruoyi-plus-soybean/public/streamsaver/mitm.html
@@ -0,0 +1,179 @@
+<!--
+ mitm.html is the lite "man in the middle"
+
+ This is only meant to signal the opener's messageChannel to
+ the service worker - when that is done this mitm can be closed
+ but it's better to keep it alive since this also stops the sw
+ from restarting
+
+ The service worker is capable of intercepting all request and fork their
+ own "fake" response - wish we are going to craft
+ when the worker then receives a stream then the worker will tell the opener
+ to open up a link that will start the download
+-->
+<script>
+ // This will prevent the sw from restarting
+ let keepAlive = () => {
+ keepAlive = () => {};
+ var ping = location.href.substr(0, location.href.lastIndexOf('/')) + '/ping';
+ var interval = setInterval(() => {
+ if (sw) {
+ sw.postMessage('ping');
+ } else {
+ fetch(ping).then(res => res.text(!res.ok && clearInterval(interval)));
+ }
+ }, 10000);
+ };
+
+ // message event is the first thing we need to setup a listner for
+ // don't want the opener to do a random timeout - instead they can listen for
+ // the ready event
+ // but since we need to wait for the Service Worker registration, we store the
+ // message for later
+ let messages = [];
+ window.onmessage = evt => messages.push(evt);
+
+ let sw = null;
+ let scope = '';
+
+ function registerWorker() {
+ return navigator.serviceWorker
+ .getRegistration('./')
+ .then(swReg => {
+ return swReg || navigator.serviceWorker.register('sw.js', { scope: './' });
+ })
+ .then(swReg => {
+ const swRegTmp = swReg.installing || swReg.waiting;
+
+ scope = swReg.scope;
+
+ return (
+ (sw = swReg.active) ||
+ new Promise(resolve => {
+ swRegTmp.addEventListener(
+ 'statechange',
+ (fn = () => {
+ if (swRegTmp.state === 'activated') {
+ swRegTmp.removeEventListener('statechange', fn);
+ sw = swReg.active;
+ resolve();
+ }
+ })
+ );
+ })
+ );
+ });
+ }
+
+ // Now that we have the Service Worker registered we can process messages
+ function onMessage(event) {
+ let { data, ports, origin } = event;
+
+ // It's important to have a messageChannel, don't want to interfere
+ // with other simultaneous downloads
+ if (!ports || !ports.length) {
+ throw new TypeError("[StreamSaver] You didn't send a messageChannel");
+ }
+
+ if (typeof data !== 'object') {
+ throw new TypeError("[StreamSaver] You didn't send a object");
+ }
+
+ // the default public service worker for StreamSaver is shared among others.
+ // so all download links needs to be prefixed to avoid any other conflict
+ data.origin = origin;
+
+ // if we ever (in some feature versoin of streamsaver) would like to
+ // redirect back to the page of who initiated a http request
+ data.referrer = data.referrer || document.referrer || origin;
+
+ // pass along version for possible backwards compatibility in sw.js
+ data.streamSaverVersion = new URLSearchParams(location.search).get('version');
+
+ if (data.streamSaverVersion === '1.2.0') {
+ console.warn('[StreamSaver] please update streamsaver');
+ }
+
+ /** @since v2.0.0 */
+ if (!data.headers) {
+ console.warn(
+ "[StreamSaver] pass `data.headers` that you would like to pass along to the service worker\nit should be a 2D array or a key/val object that fetch's Headers api accepts"
+ );
+ } else {
+ // test if it's correct
+ // should thorw a typeError if not
+ new Headers(data.headers);
+ }
+
+ /** @since v2.0.0 */
+ if (typeof data.filename === 'string') {
+ console.warn(
+ "[StreamSaver] You shouldn't send `data.filename` anymore. It should be included in the Content-Disposition header option"
+ );
+ // Do what File constructor do with fileNames
+ data.filename = data.filename.replace(/\//g, ':');
+ }
+
+ /** @since v2.0.0 */
+ if (data.size) {
+ console.warn(
+ "[StreamSaver] You shouldn't send `data.size` anymore. It should be included in the content-length header option"
+ );
+ }
+
+ /** @since v2.0.0 */
+ if (data.readableStream) {
+ console.warn('[StreamSaver] You should send the readableStream in the messageChannel, not throught mitm');
+ }
+
+ /** @since v2.0.0 */
+ if (!data.pathname) {
+ console.warn('[StreamSaver] Please send `data.pathname` (eg: /pictures/summer.jpg)');
+ data.pathname = Math.random().toString().slice(-6) + '/' + data.filename;
+ }
+
+ // remove all leading slashes
+ data.pathname = data.pathname.replace(/^\/+/g, '');
+
+ // remove protocol
+ let org = origin.replace(/(^\w+:|^)\/\//, '');
+
+ // set the absolute pathname to the download url.
+ data.url = new URL(`${scope + org}/${data.pathname}`).toString();
+
+ if (!data.url.startsWith(`${scope + org}/`)) {
+ throw new TypeError('[StreamSaver] bad `data.pathname`');
+ }
+
+ // This sends the message data as well as transferring
+ // messageChannel.port2 to the service worker. The service worker can
+ // then use the transferred port to reply via postMessage(), which
+ // will in turn trigger the onmessage handler on messageChannel.port1.
+
+ const transferable = data.readableStream ? [ports[0], data.readableStream] : [ports[0]];
+
+ if (!(data.readableStream || data.transferringReadable)) {
+ keepAlive();
+ }
+
+ return sw.postMessage(data, transferable);
+ }
+
+ if (window.opener) {
+ // The opener can't listen to onload event, so we need to help em out!
+ // (telling them that we are ready to accept postMessage's)
+ window.opener.postMessage('StreamSaver::loadedPopup', '*');
+ }
+
+ if (navigator.serviceWorker) {
+ registerWorker().then(() => {
+ window.onmessage = onMessage;
+ messages.forEach(window.onmessage);
+ });
+ }
+
+ // FF v102 just started to supports transferable streams, but still needs to ping sw.js
+ // even tough the service worker dose not have to do any kind of work and listen to any
+ // messages... #305
+ keepAlive();
+</script>
diff --git a/ruoyi-plus-soybean/public/streamsaver/sw.js b/ruoyi-plus-soybean/public/streamsaver/sw.js
new file mode 100755
index 0000000..f6cb4c3
--- /dev/null
+++ b/ruoyi-plus-soybean/public/streamsaver/sw.js
@@ -0,0 +1,132 @@
+/* eslint-disable */
+/* global self ReadableStream Response */
+
+self.addEventListener('install', () => {
+ self.skipWaiting();
+});
+
+self.addEventListener('activate', event => {
+ event.waitUntil(self.clients.claim());
+});
+
+const map = new Map();
+
+// This should be called once per download
+// Each event has a dataChannel that the data will be piped through
+self.onmessage = event => {
+ // We send a heartbeat every x second to keep the
+ // service worker alive if a transferable stream is not sent
+ if (event.data === 'ping') {
+ return;
+ }
+
+ const data = event.data;
+ const downloadUrl =
+ data.url || `${self.registration.scope + Math.random()}/${typeof data === 'string' ? data : data.filename}`;
+ const port = event.ports[0];
+ const metadata = Array.from({ length: 3 }); // [stream, data, port]
+
+ metadata[1] = data;
+ metadata[2] = port;
+
+ // Note to self:
+ // old streamsaver v1.2.0 might still use `readableStream`...
+ // but v2.0.0 will always transfer the stream through MessageChannel #94
+ if (event.data.readableStream) {
+ metadata[0] = event.data.readableStream;
+ } else if (event.data.transferringReadable) {
+ port.onmessage = evt => {
+ port.onmessage = null;
+ metadata[0] = evt.data.readableStream;
+ };
+ } else {
+ metadata[0] = createStream(port);
+ }
+
+ map.set(downloadUrl, metadata);
+ port.postMessage({ download: downloadUrl });
+};
+
+function createStream(port) {
+ // ReadableStream is only supported by chrome 52
+ return new ReadableStream({
+ start(controller) {
+ // When we receive data on the messageChannel, we write
+ port.onmessage = ({ data }) => {
+ if (data === 'end') {
+ return controller.close();
+ }
+
+ if (data === 'abort') {
+ controller.error('Aborted the download');
+ return;
+ }
+
+ controller.enqueue(data);
+ };
+ },
+ cancel(reason) {
+ console.log('user aborted', reason);
+ port.postMessage({ abort: true });
+ }
+ });
+}
+
+self.onfetch = event => {
+ const url = event.request.url;
+
+ // this only works for Firefox
+ if (url.endsWith('/ping')) {
+ return event.respondWith(new Response('pong'));
+ }
+
+ const hijacke = map.get(url);
+
+ if (!hijacke) return null;
+
+ const [stream, data, port] = hijacke;
+
+ map.delete(url);
+
+ // Not comfortable letting any user control all headers
+ // so we only copy over the length & disposition
+ const responseHeaders = new Headers({
+ 'Content-Type': 'application/octet-stream; charset=utf-8',
+
+ // To be on the safe side, The link can be opened in a iframe.
+ // but octet-stream should stop it.
+ 'Content-Security-Policy': "default-src 'none'",
+ 'X-Content-Security-Policy': "default-src 'none'",
+ 'X-WebKit-CSP': "default-src 'none'",
+ 'X-XSS-Protection': '1; mode=block',
+ 'Cross-Origin-Embedder-Policy': 'require-corp'
+ });
+
+ const headers = new Headers(data.headers || {});
+
+ if (headers.has('Content-Length')) {
+ responseHeaders.set('Content-Length', headers.get('Content-Length'));
+ }
+
+ if (headers.has('Content-Disposition')) {
+ responseHeaders.set('Content-Disposition', headers.get('Content-Disposition'));
+ }
+
+ // data, data.filename and size should not be used anymore
+ if (data.size) {
+ console.warn('Depricated');
+ responseHeaders.set('Content-Length', data.size);
+ }
+
+ let fileName = typeof data === 'string' ? data : data.filename;
+ if (fileName) {
+ console.warn('Depricated');
+ // Make filename RFC5987 compatible
+ fileName = encodeURIComponent(fileName).replace(/['()]/g, escape).replace(/\*/g, '%2A');
+ responseHeaders.set('Content-Disposition', `attachment; filename*=UTF-8''${fileName}`);
+ }
+
+ event.respondWith(new Response(stream, { headers: responseHeaders }));
+
+ port.postMessage({ debug: 'Download started' });
+};
diff --git a/ruoyi-plus-soybean/src/App.vue b/ruoyi-plus-soybean/src/App.vue
new file mode 100755
index 0000000..eb499a2
--- /dev/null
+++ b/ruoyi-plus-soybean/src/App.vue
@@ -0,0 +1,59 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { NConfigProvider, darkTheme } from 'naive-ui';
+import type { WatermarkProps } from 'naive-ui';
+import { useAppStore } from './store/modules/app';
+import { useThemeStore } from './store/modules/theme';
+import { naiveDateLocales, naiveLocales } from './locales/naive';
+
+defineOptions({
+ name: 'App'
+});
+
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+
+const naiveDarkTheme = computed(() => (themeStore.darkMode ? darkTheme : undefined));
+
+const naiveLocale = computed(() => {
+ return naiveLocales[appStore.locale];
+});
+
+const naiveDateLocale = computed(() => {
+ return naiveDateLocales[appStore.locale];
+});
+
+const watermarkProps = computed<WatermarkProps>(() => {
+ return {
+ content: themeStore.watermarkContent,
+ cross: true,
+ fullscreen: true,
+ fontSize: 14,
+ lineHeight: 14,
+ width: 384,
+ height: 384,
+ xOffset: 12,
+ yOffset: 60,
+ rotate: -13,
+ zIndex: 9999,
+ fontColor: themeStore.darkMode ? 'rgba(200, 200, 200, 0.03)' : 'rgba(200, 200, 200, 0.2)'
+ };
+});
+</script>
+
+<template>
+ <NConfigProvider
+ :theme="naiveDarkTheme"
+ :theme-overrides="themeStore.naiveTheme"
+ :locale="naiveLocale"
+ :date-locale="naiveDateLocale"
+ class="h-full"
+ >
+ <AppProvider>
+ <RouterView class="bg-layout" />
+ <NWatermark v-if="themeStore.watermark.visible" v-bind="watermarkProps" />
+ </AppProvider>
+ </NConfigProvider>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/assets/imgs/soybean.jpg b/ruoyi-plus-soybean/src/assets/imgs/soybean.jpg
new file mode 100755
index 0000000..75457d5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/imgs/soybean.jpg
Binary files differ
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/activity.svg b/ruoyi-plus-soybean/src/assets/svg-icon/activity.svg
new file mode 100755
index 0000000..abe892f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/activity.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/at-sign.svg b/ruoyi-plus-soybean/src/assets/svg-icon/at-sign.svg
new file mode 100755
index 0000000..625214d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/at-sign.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="prefix__prefix__feather prefix__prefix__feather-at-sign"><circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 006 0v-1a10 10 0 10-3.92 7.94"/></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/avatar.svg b/ruoyi-plus-soybean/src/assets/svg-icon/avatar.svg
new file mode 100755
index 0000000..66fe6f2
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/avatar.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 134.49 134.49"><path d="M134.49 67.25A67.25 67.25 0 1167.25 0a67.23 67.23 0 0167.24 67.25" fill="#f3f3f2"/><path fill="#f1caae" d="M64.47 103.16zm2.4-13.01c8.11-6.21 11-10.25 12.24-11a17.94 17.94 0 01-.69-4.23c-4 3.34-5.73 4.49-11 4.31-5.69.19-7.28-1.15-12-5.14l-.07-.09a19 19 0 01-.72 5.14c1.23.75 4.13 4.79 12.24 11.01z"/><path d="M61.56 121.66c.31-2 .61-4 .9-5.91v-.11c.93-6 1.7-10.86 2-12.48a1.17 1.17 0 01-.36-.34.6.6 0 01-.09-.13c-.16-.36-.32-.69-.48-1 0-.06-.07-.13-.1-.2a16.73 16.73 0 01-2-4.43c0-.15 0-.3-.07-.46a4 4 0 00-1 2.65c.33 3.56-10.83-9.56-10.65-14.6a38.55 38.55 0 01-4 1.9 219.52 219.52 0 0015.85 35.11zm-37.93-27.6h-.09.05s.03.01.04 0zM73.3 99.2a4 4 0 00-1-2.64c0 .16 0 .31-.06.46A7.17 7.17 0 0172 98a27.91 27.91 0 01-1.71 3.52l-.11.21c-.16.31-.32.65-.48 1a1 1 0 01-.09.13 1.1 1.1 0 01-.37.34c.29 1.6 1.06 6.35 2 12.32a2.34 2.34 0 000 .26c.23 1.54.48 3.15.73 4.81a208.85 208.85 0 0015.5-34.21A37.69 37.69 0 0184 84.7c0 5.12-11 18.03-10.7 14.5zm36.4-5.09a5.22 5.22 0 01.63.11l-.73-.28s.08.06.1.17z" fill="#9d9d9d"/><path d="M23.6 94.09h-.05a4.67 4.67 0 00-.84.15c-3 .81-5.08 8.11-6.94 16.26a67.51 67.51 0 0016.1 13.93 33.36 33.36 0 00-.42-4.63c-1.13-6.25-8.79-24.87-7.85-25.71zm94.03 17.67c-2-8.62-4-16.67-7.21-17.52h-.09a5.22 5.22 0 00-.63-.11c.39 1.91-6.82 19.65-7.91 25.69a34.76 34.76 0 00-.44 5.4" fill="#959494"/><path d="M71.29 119.06h-1.65v-3.73h1.58v.15c-.92-6-1.69-10.72-2-12.32l-.1.07a1.29 1.29 0 01-.28.08v.09H68l-.89.06h-.56l-.88-.06h-.86v-.09a1.2 1.2 0 01-.27-.08l-.11-.07c-.29 1.62-1.06 6.44-2 12.48v-.31h1.59v3.73h-1.56v-3.31c-.29 1.87-.59 3.86-.9 5.91 0-.34.1-.69.15-1v1.28l.75 1.25v-2.62h1.65v3.73h-1c.57.91 1.14 1.78 1.7 2.58v-1.09h1.69v2.82c.25-.29.5-.6.75-.91v-1.91h1.42c.34-.48.68-1 1-1.49v-3.73h1.65v1.22l.73-1.22c-.25-1.66-.5-3.27-.73-4.81zm-1.65-13.74c.15.91.33 2 .53 3.3h-.53zm0 4.79h.77c.18 1.16.38 2.4.58 3.73h-1.35zm-3.14-5.22v3.73h-1.65v-3.73zm-2.39 9h-1.36c.21-1.33.4-2.57.59-3.73h.77zm0-5.22h-.54l.54-3.31zm2.39 15.66h-1.65v-3.73h1.65zm0-5.22h-1.65v-3.73h1.65zm0-5.22h-1.65v-3.73h1.65zm2.39 10.44h-1.64v-3.73h1.64zm0-5.22h-1.64v-3.73h1.64zm0-5.22h-1.64v-3.73h1.64zm0-5.22h-1.64v-3.73h1.64z" fill="#f0f0f0"/><path d="M63.53 101.69zm-2.12-5.14zm10.86.51A7.17 7.17 0 0172 98a7.17 7.17 0 00.27-.94zm.07-.5zm-2.13 5.14zm-5.63 1.53a1.2 1.2 0 00.27.08v-3.64h1.65v3.73h-.79l.88.06h.56l.89-.06h-.79v-3.73h1.64v3.64a1.29 1.29 0 00.28-.08l.1-.07a1.1 1.1 0 00.37-.34v-3.15h1.64c-.29.57-.62 1.16-1 1.82A27.91 27.91 0 0072 98v-1.85c-.23-.29-.48-.57-.74-.86v2.89h-1.62v-3.73h.82c-.55-.52-1.11-1-1.63-1.49h-1.58v-1.5a3.56 3.56 0 01-.38-.47 2.79 2.79 0 01-.37.47V93h-1.59c-.52.47-1.08 1-1.62 1.49h.82v3.73h-1.65v-2.93c-.27.28-.52.57-.75.86V98a7.13 7.13 0 01-.23-.91 16.73 16.73 0 002 4.43c-.34-.65-.67-1.25-1-1.82h1.64v3.15a1.17 1.17 0 00.36.34zm2.67-8.78h1.64v3.73h-1.64zm-2.4 0h1.65v3.73h-1.65z" fill="#fff"/><path fill="#040000" d="M66.5 99.67h-1.65v3.73h1.65v-3.73zm-1.65 5.22h1.65v3.73h-1.65zm0-10.44h1.65v3.73h-1.65zm0 15.66h1.65v3.73h-1.65zm0 15.66v1.09c.49.7 1 1.35 1.45 2l.2-.23v-2.82zm0-5.22h1.65v3.73h-1.65zm0-5.22h1.65v3.73h-1.65zm-3.29 6.33l.15.26v-1.28c-.05.36-.1.68-.15 1.02zm.9-23.48h1.65v-3.73h-.82c-.29.28-.56.56-.83.84zm-.75-2.03l-.3.4c0 .16 0 .31.07.46a7.13 7.13 0 00.23.91zm2.4 3.52h-1.64c.29.57.62 1.17 1 1.82 0 .07.07.14.1.2.16.32.32.65.48 1a.6.6 0 00.09.13zm0 20.88h-1.65v2.62q.34.57.69 1.11h1zm0-1.49v-3.73h-1.59v3.73zm0-10.44v-3.31l-.54 3.31zm-1.36 5.22h1.36v-3.73h-.77c-.19 1.16-.34 2.4-.59 3.73zm6.89 6.71v3.73c.53-.8 1.06-1.63 1.6-2.51v-1.22zm0-5.22v3.73h1.65v-3.32a2.34 2.34 0 010-.26v-.15zM66.5 91.46c-.4.43-1 .94-1.59 1.5h1.59zM68.83 93c-.63-.56-1.19-1.07-1.58-1.5V93zm-1.58 17.11h1.65v3.73h-1.65zm0 10.44h1.65v3.73h-1.65zm0-5.22h1.65v3.73h-1.65zm1.64-15.66h-1.64v3.73h1.64v-3.73zm-1.64 5.22h1.65v3.73h-1.65zm0 22.79c.47-.6.94-1.23 1.42-1.91h-1.42zm0-33.23h1.65v3.73h-1.65zm2.39 10.87v3.3h.53c-.17-1.27-.38-2.39-.53-3.3zm0-5.65v3.15a1 1 0 00.09-.13c.16-.36.32-.68.48-1l.11-.21c.34-.66.67-1.25 1-1.82zM72 98a7.17 7.17 0 00.24-.91c0-.15 0-.3.06-.46-.1-.13-.2-.27-.31-.4V98zm-2.36.18h1.65v-2.89l-.83-.84h-.82zm.77 11.93h-.77v3.73H71c-.21-1.33-.41-2.57-.59-3.73z"/><path d="M60.45 99.2a4 4 0 011-2.65l.3-.4c.23-.29.48-.58.75-.86s.54-.56.83-.84c.54-.52 1.1-1 1.62-1.49s1.19-1.07 1.59-1.5a2.79 2.79 0 00.37-.47 3.56 3.56 0 00.38.47c.39.43 1 .94 1.58 1.5s1.08 1 1.63 1.49l.83.84c.26.29.51.57.74.86.11.13.21.27.31.4a4 4 0 011 2.64C73 102.73 84 89.82 84 84.7v-.36c-.09-.72-2.2-1.82-4.15-5.11-.15-.26-.36-.27-.68-.08-1.23.75-4.13 4.79-12.24 11-8.11-6.22-11-10.26-12.24-11-.31-.19-.53-.18-.68.08-2 3.29-4.05 4.39-4.14 5.11a2.17 2.17 0 000 .26c-.25 5.04 10.91 18.16 10.58 14.6z" fill="#838384"/><path d="M62.49 132.11a13.73 13.73 0 003.81-3.29c-.47-.61-1-1.26-1.45-2s-1.13-1.67-1.7-2.58q-.34-.54-.69-1.11l-.75-1.25-.15-.26A219.52 219.52 0 0145.81 86.5c-.49-1.25-21 7-22.17 7.56-.94.84 6.72 19.46 7.85 25.71a33.36 33.36 0 01.42 4.63A66.72 66.72 0 0062 134.27c.15-1.27.3-2.06.49-2.16z" fill="#282629"/><path d="M101.35 125.2a34.76 34.76 0 01.44-5.4c1.09-6 8.3-23.78 7.91-25.69 0-.08 0-.15-.09-.17-1.17-.52-21.69-8.84-22.13-7.56A208.85 208.85 0 0172 120.55l-.73 1.22c-.54.88-1.07 1.71-1.6 2.51-.34.51-.68 1-1 1.49-.48.68-1 1.31-1.42 1.91-.25.31-.5.62-.75.91l-.2.23a13.73 13.73 0 01-3.81 3.29c-.19.1-.34.89-.47 2.16 1.73.13 3.47.22 5.23.22a66.88 66.88 0 0034.1-9.29" fill="#1a1a1b"/><path d="M67.41 79.23c5.28.18 7-1 11-4.31l1-.83c4.93-4.19 2.44-12.94 5.44-12.39s6.29-7.44 4.18-9.45c-1-.09-1.87.88-2.66 2.2-.89 1.49-.54 3.79-1.42 3.74-1.19-4-.93-7.62-.69-11.81C76 48.8 67.47 50.06 60.59 45c-1.41-1-2.77-2.4-4.91-2.36-2.47 0-3.46 1.5-3.11 3.87.17 1.19.25 2.8.32 3.5A10.07 10.07 0 0151 56.1c-.37.38-1.27 2-1.8 2.09s-.14-2.47-1.62-4.69c-1.16-1.75-1.7-1.37-1.7-1.37C43.56 54 47 62.25 50 61.7s.55 8.1 5.37 12.31l.08.08c4.68 3.99 6.27 5.33 11.96 5.14z" fill="#f9d2b5"/><path d="M47.61 53.5c1.48 2.22 1.18 4.8 1.62 4.69s1.43-1.71 1.8-2.09a10.07 10.07 0 001.86-6.1c-.07-.7-.15-2.31-.32-3.5-.35-2.37.64-3.83 3.11-3.87 2.14 0 3.5 1.32 4.91 2.36 6.88 5.05 15.4 3.79 23.66 1.37-.24 4.19-.5 7.78.69 11.81.88.05.53-2.25 1.42-3.74.79-1.32 1.63-2.29 2.66-2.2a2.87 2.87 0 01.38.08c2.46-5.22 2.72-13.7.29-18.93s-6.3-7.68-10.14-12c.35 1.18-1.24 2.95-2.75 3.08-1.26.12-3.35-.56-4.61-.71-6.36-.77-10-1.06-15.18.77a19.36 19.36 0 00-7.22 4.22 19.88 19.88 0 00-5.18 10.65 26.75 26.75 0 00-.19 6.38c.12 1.14.81 5.36 1.49 6.38 0-.02.54-.4 1.7 1.35z" fill="#554c44"/></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/banner.svg b/ruoyi-plus-soybean/src/assets/svg-icon/banner.svg
new file mode 100755
index 0000000..192b637
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/banner.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><path fill="#ebebeb" d="M0 382.4h500v.25H0zM416.78 398.49h33.12v.25h-33.12zM322.53 401.21h8.69v.25h-8.69zM396.59 389.21h19.19v.25h-19.19zM52.46 390.89h43.19v.25H52.46zM104.56 390.89h6.33v.25h-6.33zM131.47 395.11h93.68v.25h-93.68zM237 337.8H43.91a5.71 5.71 0 01-5.7-5.71V60.66a5.71 5.71 0 015.7-5.66H237a5.71 5.71 0 015.71 5.71v271.38a5.71 5.71 0 01-5.71 5.71zM43.91 55.2a5.46 5.46 0 00-5.45 5.46v271.43a5.46 5.46 0 005.45 5.46H237a5.47 5.47 0 005.46-5.46V60.66A5.47 5.47 0 00237 55.2zM453.31 337.8h-193.1a5.72 5.72 0 01-5.71-5.71V60.66a5.72 5.72 0 015.71-5.66h193.1a5.71 5.71 0 015.69 5.66v271.43a5.71 5.71 0 01-5.69 5.71zM260.21 55.2a5.47 5.47 0 00-5.46 5.46v271.43a5.47 5.47 0 005.46 5.46h193.1a5.47 5.47 0 005.46-5.46V60.66a5.47 5.47 0 00-5.46-5.46z"/><path fill="#e6e6e6" d="M409.9 275.41H301.09V78.79H409.9z"/><path fill="#f0f0f0" d="M407.56 275.41H296.08V78.79h111.48z"/><path fill="#e6e6e6" d="M409.9 280.07H301.09v-4.66H409.9z"/><path fill="#f0f0f0" d="M405.75 280.07H294.27v-4.66h111.48z"/><path fill="#fafafa" d="M400.52 85.82v182.56H303.1V85.82z"/><path fill="#fff" d="M330.66 268.38l27.42-182.56h19.48l-27.43 182.56h-19.47z"/><path fill="#fff" d="M333.75 268.38l27.43-182.56h19.47l-27.43 182.56h-19.47zM354.31 268.38l27.43-182.56h7.59l-27.42 182.56h-7.6z"/><path fill="#e6e6e6" d="M304 85.82v182.56h-.89V85.82z"/><path fill="#ebebeb" opacity=".6" d="M294.41 91.12h108.81l.64-2.57h-108.8l-.65 2.57zM294.41 95.32h108.81l.64-2.56h-108.8l-.65 2.56zM294.41 99.53h108.81l.64-2.56h-108.8l-.65 2.56zM294.41 103.74h108.81l.64-2.57h-108.8l-.65 2.57zM294.41 107.95h108.81l.64-2.57h-108.8l-.65 2.57zM294.41 112.15h108.81l.64-2.56h-108.8l-.65 2.56z"/><path fill="#f0f0f0" d="M274.53 67h156.92v3.79H274.53z"/><path fill="#fafafa" opacity=".8" d="M420.36 63.81h8.25v242.84h-8.25z"/><path fill="#f0f0f0" opacity=".8" d="M420.36 63.81h2.14v242.84h-2.14z"/><path fill="#fafafa" opacity=".8" d="M412.11 63.81h8.25v242.84h-8.25z"/><path fill="#f0f0f0" opacity=".8" d="M412.11 63.81h2.14v242.84h-2.14z"/><path fill="#fafafa" opacity=".8" d="M403.86 63.81h8.25v242.84h-8.25zM292.84 63.81h8.25v242.84h-8.25z"/><path fill="#f0f0f0" opacity=".8" d="M292.84 63.81h2.14v242.84h-2.14z"/><path fill="#fafafa" opacity=".8" d="M284.6 63.81h8.25v242.84h-8.25z"/><path fill="#f0f0f0" opacity=".8" d="M284.6 63.81h2.14v242.84h-2.14z"/><path fill="#fafafa" opacity=".8" d="M276.35 63.81h8.25v242.84h-8.25z"/><path fill="#e6e6e6" d="M194.15 275.41H85.34V78.79h108.81z"/><path fill="#f0f0f0" d="M191.8 275.41H80.32V78.79H191.8z"/><path fill="#e6e6e6" d="M194.15 280.07H85.34v-4.66h108.81z"/><path fill="#f0f0f0" d="M189.99 280.07H78.51v-4.66h111.48z"/><path fill="#fafafa" d="M184.77 85.82v182.56H87.35V85.82z"/><path fill="#fff" d="M114.91 268.38l27.42-182.56h19.48l-27.43 182.56h-19.47z"/><path fill="#fff" d="M118 268.38l27.42-182.56h19.48l-27.43 182.56H118zM138.56 268.38l27.43-182.56h7.59l-27.42 182.56h-7.6z"/><path fill="#e6e6e6" d="M88.25 85.81v182.56h-.89V85.81z"/><path fill="#ebebeb" opacity=".6" d="M78.66 91.12h108.81l.64-2.57H79.31l-.65 2.57zM78.66 95.32h108.81l.64-2.56H79.31l-.65 2.56zM78.66 99.53h108.81l.64-2.56H79.31l-.65 2.56zM78.66 103.74h108.81l.64-2.57H79.31l-.65 2.57zM78.66 107.95h108.81l.64-2.57H79.31l-.65 2.57zM78.66 112.15h108.81l.64-2.56H79.31l-.65 2.56z"/><path fill="#f0f0f0" d="M58.78 67H215.7v3.79H58.78z"/><path fill="#fafafa" opacity=".8" d="M204.61 63.81h8.25v242.84h-8.25z"/><path fill="#f0f0f0" opacity=".8" d="M204.61 63.81h2.14v242.84h-2.14z"/><path fill="#fafafa" opacity=".8" d="M196.36 63.81h8.25v242.84h-8.25z"/><path fill="#f0f0f0" opacity=".8" d="M196.36 63.81h2.14v242.84h-2.14z"/><path fill="#fafafa" opacity=".8" d="M188.11 63.81h8.25v242.84h-8.25zM77.09 63.81h8.25v242.84h-8.25z"/><path fill="#f0f0f0" opacity=".8" d="M77.09 63.81h2.14v242.84h-2.14z"/><path fill="#fafafa" opacity=".8" d="M68.84 63.81h8.25v242.84h-8.25z"/><path fill="#f0f0f0" opacity=".8" d="M68.84 63.81h2.14v242.84h-2.14z"/><path fill="#fafafa" opacity=".8" d="M60.59 63.81h8.25v242.84h-8.25z"/><path fill="#f0f0f0" d="M392.58 375.61h-16.81v-84h16.81z"/><path fill="#f0f0f0" d="M386.44 382.4H243.98v-6.79h142.46z"/><path fill="#f5f5f5" d="M237.85 291.61h137.92v84H237.85z"/><path fill="#f0f0f0" d="M250.41 300.51H363.2v27.94H250.41zM250.41 334.85H363.2v27.94H250.41z"/><path d="M293.44 304.36h26.73a2.41 2.41 0 002.41-2.41 2.42 2.42 0 00-2.41-2.41h-26.73A2.42 2.42 0 00291 302a2.41 2.41 0 002.44 2.36zM293.44 338.71h26.73a2.42 2.42 0 002.41-2.41 2.41 2.41 0 00-2.41-2.41h-26.73a2.41 2.41 0 00-2.44 2.41 2.42 2.42 0 002.44 2.41z" fill="#f5f5f5"/><path fill="#f0f0f0" d="M375.76 291.62H233.58v-3.28h142.18z"/><path fill="#e6e6e6" d="M375.76 288.33h21.28v3.28h-21.28z"/><path d="M108.34 376.83a7.25 7.25 0 01-7.23-7.23v-70.76a7.25 7.25 0 017.23-7.23 7.24 7.24 0 017.22 7.23v70.76a7.24 7.24 0 01-7.22 7.23z" fill="#f5f5f5"/><path d="M112.47 374a4.9 4.9 0 01-4.88-4.88v-69.78a4.9 4.9 0 014.88-4.88 4.9 4.9 0 014.89 4.88v69.76a4.9 4.9 0 01-4.89 4.9z" fill="#ebebeb"/><path d="M118.83 376.83a7.24 7.24 0 01-7.22-7.23v-70.76a7.24 7.24 0 017.22-7.23 7.25 7.25 0 017.23 7.23v70.76a7.25 7.25 0 01-7.23 7.23z" fill="#f5f5f5"/><path d="M123 374a4.89 4.89 0 01-4.88-4.88v-69.78a4.89 4.89 0 014.88-4.88 4.89 4.89 0 014.88 4.88v69.76A4.89 4.89 0 01123 374z" fill="#ebebeb"/><rect x="122.1" y="291.61" width="14.45" height="85.22" rx="7.23" fill="#f5f5f5"/><path d="M133.47 374a4.89 4.89 0 01-4.88-4.88v-69.78a4.89 4.89 0 014.88-4.88 4.89 4.89 0 014.88 4.88v69.76a4.89 4.89 0 01-4.88 4.9z" fill="#ebebeb"/><path d="M139.83 376.83a7.25 7.25 0 01-7.23-7.23v-70.76a7.25 7.25 0 017.23-7.23 7.25 7.25 0 017.23 7.23v70.76a7.25 7.25 0 01-7.23 7.23z" fill="#f5f5f5"/><path d="M144 374a4.9 4.9 0 01-4.89-4.88v-69.78a4.9 4.9 0 014.89-4.88 4.9 4.9 0 014.88 4.88v69.76A4.9 4.9 0 01144 374z" fill="#ebebeb"/><path d="M150.33 376.83a7.25 7.25 0 01-7.23-7.23v-70.76a7.25 7.25 0 017.23-7.23 7.25 7.25 0 017.23 7.23v70.76a7.25 7.25 0 01-7.23 7.23z" fill="#f5f5f5"/><path d="M154.47 374a4.9 4.9 0 01-4.89-4.88v-69.78a4.9 4.9 0 014.89-4.88 4.9 4.9 0 014.88 4.88v69.76a4.9 4.9 0 01-4.88 4.9z" fill="#ebebeb"/><path d="M160.83 376.83a7.25 7.25 0 01-7.23-7.23v-70.76a7.25 7.25 0 017.23-7.23 7.24 7.24 0 017.22 7.23v70.76a7.24 7.24 0 01-7.22 7.23z" fill="#f5f5f5"/><path d="M165 374a4.9 4.9 0 01-4.88-4.88v-69.78a4.9 4.9 0 014.88-4.88 4.9 4.9 0 014.89 4.88v69.76A4.9 4.9 0 01165 374z" fill="#ebebeb"/><path d="M171.33 376.83a7.25 7.25 0 01-7.23-7.23v-70.76a7.25 7.25 0 017.23-7.23 7.24 7.24 0 017.22 7.23v70.76a7.24 7.24 0 01-7.22 7.23z" fill="#f5f5f5"/><rect x="157.39" y="364.15" width="24.4" height="3.2" rx="1.6" fill="#f5f5f5"/><path d="M180.19 306.38H159a1.6 1.6 0 01-1.6-1.6 1.6 1.6 0 011.6-1.6h21.2a1.6 1.6 0 011.6 1.6 1.6 1.6 0 01-1.61 1.6z" fill="#f5f5f5"/><g><ellipse cx="250" cy="416.24" rx="193.89" ry="11.32" fill="#f5f5f5"/></g><g><path d="M409.83 248.66h-239a9.25 9.25 0 01-9.32-9.93l8.76-141.08a10.75 10.75 0 0110.55-9.94h239a9.26 9.26 0 019.32 9.94l-8.76 141.08a10.75 10.75 0 01-10.55 9.93z" fill="currentColor"/><path d="M409.83 248.66h-239a9.25 9.25 0 01-9.32-9.93l8.76-141.08a10.75 10.75 0 0110.55-9.94h239a9.26 9.26 0 019.32 9.94l-8.76 141.08a10.75 10.75 0 01-10.55 9.93z" fill="#fff" opacity=".8"/><path d="M201.46 128.36H173a2.61 2.61 0 01-2.64-2.81l.81-13a3 3 0 013-2.82h28.46z" fill="#fff" opacity=".4"/><path d="M419.67 90.2H211.28a8 8 0 00-7.91 7.45l-2 32.37H172.9a3 3 0 00-3 2.82l-.75 12.2a2.61 2.61 0 002.64 2.82h28.46l-5.64 90.87a6.92 6.92 0 007 7.45H410a8 8 0 007.91-7.45l8.76-141.08a6.91 6.91 0 00-7-7.45z" fill="#fff"/><path d="M199 167.36h-28.42a2.62 2.62 0 01-2.65-2.82l.76-12.2a3 3 0 013-2.82h28.46zM197.83 186.86h-28.46a2.62 2.62 0 01-2.65-2.82l.76-12.21a3 3 0 013-2.81h28.46zM196.62 206.36h-28.46a2.63 2.63 0 01-2.65-2.82l.76-12.21a3.05 3.05 0 013-2.82h28.46zM195.41 225.85H167a2.62 2.62 0 01-2.7-2.85l.76-12.2a3 3 0 013-2.82h28.47z" fill="#fff" opacity=".4"/><path d="M172.63 145.59a1.13 1.13 0 01-1.15-1.23l.66-10.71a1.48 1.48 0 011.44-1.36h24.12a1.13 1.13 0 011.15 1.23l-.66 10.72a1.48 1.48 0 01-1.44 1.35z" fill="currentColor" opacity=".2"/><path d="M253 107h-34.43a4.47 4.47 0 01-4.49-4.79 5.17 5.17 0 015.08-4.78h34.41a4.46 4.46 0 014.49 4.78A5.19 5.19 0 01253 107z" fill="currentColor" opacity=".3"/><path d="M417 102.22a5.17 5.17 0 01-5.08 4.79 4.46 4.46 0 01-4.49-4.79 5.16 5.16 0 015.08-4.78 4.45 4.45 0 014.49 4.78z" fill="currentColor"/><path d="M319.53 188.84H218a8.83 8.83 0 01-8.9-9.49l3.05-49.35a10.23 10.23 0 0110.07-9.48h101.56a8.81 8.81 0 018.89 9.48l-3.06 49.34a10.26 10.26 0 01-10.08 9.5zm-97.34-67.9a9.79 9.79 0 00-9.63 9.07l-3.06 49.34a8.43 8.43 0 008.51 9.07h101.55a9.79 9.79 0 009.63-9.07l3.07-49.35a8.44 8.44 0 00-8.51-9.07z" fill="currentColor" opacity=".4"/><path d="M230.33 153.34l14.57 9.43a33 33 0 0111.33-10.27L248.4 137a52.81 52.81 0 00-18.07 16.34z" fill="currentColor"/><path d="M230.33 153.34l14.57 9.43a33 33 0 0111.33-10.27L248.4 137a52.81 52.81 0 00-18.07 16.34z" fill="#fff" opacity=".8"/><path d="M221.55 178.6h17.84a31.51 31.51 0 015.51-15.83l-14.57-9.43a50.28 50.28 0 00-8.78 25.26z" fill="#263238"/><path d="M272.36 130.76a49.82 49.82 0 00-24 6.22l7.83 15.52a31.31 31.31 0 0115-3.89 27.86 27.86 0 0128.13 30h17.84c1.72-26.43-18.37-47.85-44.8-47.85zM260.78 207h-52.3a.85.85 0 01-.85-.91l.23-3.68a1 1 0 011-.91h52.3z" fill="currentColor"/><path d="M260.78 207h66a1 1 0 001-.91l.23-3.68a.85.85 0 00-.85-.91h-66z" fill="currentColor" opacity=".2"/><path d="M296.3 221.51h-88.72a.84.84 0 01-.85-.91l.23-3.67a1 1 0 011-.91h88.72z" fill="currentColor"/><path d="M296.3 221.51h29.58a1 1 0 001-.91l.23-3.67a.84.84 0 00-.85-.91h-29.62z" fill="currentColor" opacity=".2"/><path d="M217.1 236h-10.42a.85.85 0 01-.85-.91l.23-3.68a1 1 0 011-.91h10.42z" fill="currentColor"/><path d="M217.1 236H325a1 1 0 001-.91l.23-3.68a.85.85 0 00-.86-.91H217.44z" fill="currentColor" opacity=".2"/><path d="M348.77 236.45a.38.38 0 01-.39-.41l7.16-115.31a.44.44 0 01.44-.41.38.38 0 01.38.41L349.2 236a.44.44 0 01-.43.45z" fill="currentColor"/><path d="M356.83 150.94a3 3 0 01-2.93 2.76 2.57 2.57 0 01-2.59-2.76 3 3 0 012.94-2.76 2.55 2.55 0 012.58 2.76z" fill="#fff"/><path d="M353.88 154.11a3 3 0 01-3-3.17 3.43 3.43 0 013.37-3.18 3 3 0 013 3.18 3.42 3.42 0 01-3.37 3.17zm.34-5.52a2.53 2.53 0 00-2.49 2.35 2.18 2.18 0 002.2 2.34 2.53 2.53 0 002.49-2.34 2.18 2.18 0 00-2.2-2.35z" fill="currentColor"/><path d="M355.29 175.78a3 3 0 01-2.93 2.76 2.57 2.57 0 01-2.59-2.76 3 3 0 012.93-2.78 2.56 2.56 0 012.59 2.78z" fill="#fff"/><path d="M352.34 179a3 3 0 01-3-3.17 3.43 3.43 0 013.37-3.18 3 3 0 013 3.18 3.42 3.42 0 01-3.37 3.17zm.34-5.52a2.53 2.53 0 00-2.49 2.35 2.17 2.17 0 002.2 2.34 2.53 2.53 0 002.49-2.34 2.18 2.18 0 00-2.2-2.4z" fill="currentColor"/><path fill="#fff" d="M351.78 187.86l-3.65 5.71h6.6l-2.95-5.71z"/><path d="M354.7 194h-6.6a.39.39 0 01-.34-.2.42.42 0 010-.42l3.66-5.71a.42.42 0 01.71 0l3 5.71a.44.44 0 01-.4.62zm-5.83-.82H354l-2.3-4.48zM398.45 236.45a.38.38 0 01-.39-.41l7.16-115.31a.44.44 0 01.44-.41.38.38 0 01.39.41L398.89 236a.45.45 0 01-.44.45z" fill="currentColor"/><path d="M403.67 196.71a2.56 2.56 0 00-2.58-2.76 3 3 0 00-2.94 2.76 2.57 2.57 0 002.59 2.76 3 3 0 002.93-2.76z" fill="#fff"/><path d="M400.72 199.88a3 3 0 01-3-3.17 3.42 3.42 0 013.37-3.17 3 3 0 013 3.17 3.42 3.42 0 01-3.37 3.17zm.34-5.52a2.54 2.54 0 00-2.49 2.35 2.19 2.19 0 002.2 2.35 2.54 2.54 0 002.49-2.35 2.19 2.19 0 00-2.2-2.35z" fill="currentColor"/><path d="M407.53 134.6a2.56 2.56 0 00-2.59-2.76 3 3 0 00-2.94 2.76 2.57 2.57 0 002.59 2.76 3 3 0 002.94-2.76z" fill="#fff"/><path d="M404.57 137.77a2.94 2.94 0 01-3-3.17 3.43 3.43 0 013.37-3.18 3 3 0 013 3.18 3.43 3.43 0 01-3.37 3.17zm.35-5.52a2.53 2.53 0 00-2.49 2.35 2.17 2.17 0 002.2 2.34 2.53 2.53 0 002.49-2.34 2.18 2.18 0 00-2.2-2.35z" fill="currentColor"/><path fill="#fff" d="M402.64 168.92l-2.94-5.72h6.59l-3.65 5.72z"/><path d="M402.61 169.33a.38.38 0 01-.34-.21l-2.95-5.71a.43.43 0 010-.41.44.44 0 01.37-.21h6.6a.39.39 0 01.35.21.43.43 0 010 .41l-3.64 5.71a.47.47 0 01-.39.21zm-2.22-5.71l2.3 4.47 2.86-4.47zM373.61 236.45a.38.38 0 01-.39-.41l7.16-115.31a.44.44 0 01.44-.41.37.37 0 01.38.41L374.05 236a.45.45 0 01-.44.45z" fill="currentColor"/><path d="M381.24 158a3 3 0 01-2.94 2.76 2.55 2.55 0 01-2.58-2.76 3 3 0 012.93-2.76 2.57 2.57 0 012.59 2.76z" fill="#fff"/><path d="M378.28 161.2a3 3 0 01-3-3.18 3.42 3.42 0 013.37-3.17 3 3 0 013 3.17 3.43 3.43 0 01-3.37 3.18zm.34-5.53a2.54 2.54 0 00-2.49 2.35 2.19 2.19 0 002.2 2.35 2.54 2.54 0 002.49-2.35 2.18 2.18 0 00-2.2-2.35z" fill="currentColor"/><path d="M377.81 213.26a3 3 0 01-2.93 2.76 2.56 2.56 0 01-2.59-2.76 3 3 0 012.93-2.76 2.57 2.57 0 012.59 2.76z" fill="#fff"/><path d="M374.85 216.43a3 3 0 01-3-3.17 3.43 3.43 0 013.37-3.18 3 3 0 013 3.18 3.42 3.42 0 01-3.37 3.17zm.34-5.52a2.53 2.53 0 00-2.49 2.35 2.18 2.18 0 002.2 2.34 2.53 2.53 0 002.49-2.34 2.18 2.18 0 00-2.2-2.35z" fill="currentColor"/><path fill="#fff" d="M380.46 126.12l-3.66 5.72h6.6l-2.94-5.72z"/><path d="M383.37 132.25h-6.59a.39.39 0 01-.35-.21.43.43 0 010-.41l3.65-5.71a.43.43 0 01.72 0l2.94 5.71a.4.4 0 010 .41.47.47 0 01-.37.21zm-5.82-.83h5.16L380.4 127z" fill="currentColor"/></g><g><path d="M111.54 155.81l-1 4.87-1 5c-.66 3.32-1.22 6.64-1.73 9.9-.26 1.63-.46 3.24-.63 4.83s-.32 3.13-.4 4.62a40 40 0 000 4.08c0 1.17.26 2.17.21 2.28l-.67-1.15c-.3-.3-.85-.58-.94-.45s.26.36.9.53a13.51 13.51 0 002.51.35 29.74 29.74 0 003.22-.08 65.84 65.84 0 007.23-1c1.25-.24 2.52-.49 3.78-.77l3.74-.81.5-.11a5.25 5.25 0 014.24 9.34l-1.87 1.38-1.84 1.26c-1.26.8-2.53 1.58-3.85 2.3a55.3 55.3 0 01-8.46 3.78 41.08 41.08 0 01-4.87 1.31 26.44 26.44 0 01-5.69.54 20.16 20.16 0 01-7-1.34 16.42 16.42 0 01-7.2-5.61 7.56 7.56 0 01-.52-.86l-.15-.29a28 28 0 01-2.55-8 47.2 47.2 0 01-.64-6.62q-.11-3.13 0-6.1t.41-5.85c.34-3.84.91-7.58 1.56-11.29.34-1.85.68-3.69 1.1-5.52.2-.92.4-1.84.63-2.76l.74-2.83a10.47 10.47 0 0120.39 4.73z" fill="currentColor"/><path d="M111.54 155.81l-1 4.87-1 5c-.66 3.32-1.22 6.64-1.73 9.9-.26 1.63-.46 3.24-.63 4.83s-.32 3.13-.4 4.62a40 40 0 000 4.08c0 1.17.26 2.17.21 2.28l-.67-1.15c-.3-.3-.85-.58-.94-.45s.26.36.9.53a13.51 13.51 0 002.51.35 29.74 29.74 0 003.22-.08 65.84 65.84 0 007.23-1c1.25-.24 2.52-.49 3.78-.77l3.74-.81.5-.11a5.25 5.25 0 014.24 9.34l-1.87 1.38-1.84 1.26c-1.26.8-2.53 1.58-3.85 2.3a55.3 55.3 0 01-8.46 3.78 41.08 41.08 0 01-4.87 1.31 26.44 26.44 0 01-5.69.54 20.16 20.16 0 01-7-1.34 16.42 16.42 0 01-7.2-5.61 7.56 7.56 0 01-.52-.86l-.15-.29a28 28 0 01-2.55-8 47.2 47.2 0 01-.64-6.62q-.11-3.13 0-6.1t.41-5.85c.34-3.84.91-7.58 1.56-11.29.34-1.85.68-3.69 1.1-5.52.2-.92.4-1.84.63-2.76l.74-2.83a10.47 10.47 0 0120.39 4.73z" opacity=".2"/><path d="M86.23 411.58S76.77 403 77 400.28l-7.86-4-4.68 8.08 4.34 2.43 1.2-.39.79 1.37c3.92 2 12.48 5.91 15.47 5.8a1.92 1.92 0 00-.03-1.99z" fill="#263238"/><path d="M86.91 310.62c5.72-41 3.85-65.87 9.86-86.06l37.2-3s-16.49 65-24 92.06-31.23 88.42-31.23 88.42l-10.35-5.3s8.45-54.42 18.52-86.12z" fill="#263238"/><path d="M117.25 251.87c-2.53 5.85-7.47 43.42-7.45 62.18 0-.13.08-.28.12-.42 2.65-9.51 6.42-23.72 10.18-38.13z" opacity=".2"/><path d="M146 412.92s-15.43-3.5-16.14-6h-9.79l-.81 9.33h5.44l1-.86 1.22.86c4.4 0 17.12 0 19.86-1.6.02-1.42-.78-1.73-.78-1.73z" fill="#263238"/><path d="M119.87 311.77c-8.17-55.51-8.17-89.88-8.17-89.88l36.4-.59s-4.37 66.2-5.89 93.93c-1.57 28.83-10.57 92.29-10.57 92.29H119s-3-60.1.87-95.75z" fill="#263238"/><path d="M115.92 117.67c-1.73 10.89-4.87 16.73-7 22.14 3.67 3.42 11.53 7.65 19.46 7.54s6.49-5.48 4.36-8.25c-5.69-2.87-5.91-5.77-4.52-8.31z" fill="#7f3e3b"/><path d="M102.32 141.35s5.06-5.95 7.06-6.95 17.13 1 20.7 1.72 7.92 6 7.92 6zM141 116.76c-.16.72.09 1.38.56 1.48s1-.4 1.14-1.12-.09-1.38-.56-1.48-1 .36-1.14 1.12z" fill="#263238"/><path d="M140.71 117.75a26.08 26.08 0 002.06 6.87c-1.54.91-3.58-.1-3.58-.1z" fill="#630f0f"/><path d="M143.46 112.63a.41.41 0 01-.4 0 3.39 3.39 0 00-3.11.1.42.42 0 01-.59-.12.43.43 0 01.12-.6 4.23 4.23 0 013.92-.18.43.43 0 01.22.57.38.38 0 01-.16.23z" fill="#263238"/><path d="M117.18 109.46c-1.43 8.92-2.73 14.07.72 19.55 5.18 8.24 17 6.56 21.07-1.78 3.68-7.5 5.41-20.83-2.45-26.19a12.46 12.46 0 00-19.34 8.42z" fill="#7f3e3b"/><path d="M133.39 118c7.22 1.56 5.12-7.36 3.39-8.25 4.34 2.6 7.22-.42 7-3.14 3.32-.55 3.15-6 .87-6.17-1.44-3.77-8.17-6.94-9.42-4.61-3.63-4.22-13.35-3.5-11.94-.44-3.69-.72-11.55-.39-8.92 5.17-5.61.72-5.34 10.32-1.68 11.32-3.83 2.12-.12 9.36 1.53 13.9s18.09.5 19.17-7.78z" fill="#263238"/><path d="M129.28 117.09a7.61 7.61 0 001.18 5.45c1.41 2 3.59.75 4.44-1.66.76-2.16.94-5.8-1.07-6.89s-4.18.66-4.55 3.1z" fill="#7f3e3b"/><path d="M102.83 140.35c12.3-1.51 29.75-1.38 41.33.64A13.17 13.17 0 01155 152.91c1.44 17.67-2.47 48.55-5.85 79.64-27.25 9.14-54.91-.13-54.91-.13-.61-6.27 2.77-26.82 4.88-37.79-1.55-11.32-5.48-24.29-7.69-39.34a13.15 13.15 0 0111.4-14.94z" fill="currentColor"/><path d="M139.84 165.09c1.86 10.64 7.75 23.32 13.14 31.71.74-7.35 1.36-14.35 1.75-20.81z" opacity=".2"/><path d="M229.93 140.44a17.77 17.77 0 00-19.06 30c-1.78 2.4-7 9.45-8.59 12.17-2 3.29-3 6.25-1.62 7.07s3.48-1.51 5.43-4.8c1.62-2.71 5.34-10.65 6.59-13.37a17.77 17.77 0 0017.25-31.06zm4.27 23.23a15.54 15.54 0 11-5.41-21.31 15.53 15.53 0 015.41 21.31z" fill="currentColor"/><path d="M234.93 164.1a16.39 16.39 0 11-5.71-22.46 16.38 16.38 0 015.71 22.46z" fill="currentColor" opacity=".2"/><path d="M209.43 179.66l-2.17-1.92a3.36 3.36 0 00-3-.76l-5 1.14a6.28 6.28 0 00-4.4 3.72l-.48 1.16c3.37 3.43 9.54 4.66 9.54 4.66l1.73.54a2.79 2.79 0 003.57-1.76l1.13-3.39a3.27 3.27 0 00-.92-3.39z" fill="#7f3e3b"/><path d="M154.47 149.29l2 4.39 2.11 4.48c1.41 3 2.9 5.89 4.39 8.72q1.13 2.12 2.32 4.11c.77 1.33 1.56 2.61 2.36 3.79a38.85 38.85 0 002.35 3.12c.7.86 1.42 1.48 1.44 1.58l-1.24-.51a12 12 0 004.36 1.1 43.06 43.06 0 006.37.08c2.28-.1 4.63-.34 7-.62 1.19-.15 2.4-.29 3.61-.47l3.58-.48.49-.07a5.26 5.26 0 013.6 9.6l-1.82 1.2-1.79 1.08c-1.21.69-2.43 1.36-3.69 2s-2.53 1.21-3.84 1.75-2.66 1-4 1.49a46 46 0 01-8.9 2 28.27 28.27 0 01-10.86-.66 4.81 4.81 0 01-.75-.28l-.48-.23a26.69 26.69 0 01-6.86-4.74 46.93 46.93 0 01-4.48-4.82 83.07 83.07 0 01-6.74-9.64c-2-3.23-3.74-6.51-5.39-9.8-.81-1.66-1.62-3.31-2.36-5-.37-.84-.75-1.69-1.1-2.54l-1.08-2.67a10.47 10.47 0 0119.22-8.3z" fill="currentColor"/></g><g><path d="M362.85 378.84c-1.54-12.09 5.2-21.84 11.72-31.28 7.67-11.1 15.6-22.57 10.91-38.32a33 33 0 00-6.31-11.77 40.88 40.88 0 01-13.75 18c-8.7 6.48-19 8.52-26.17 5.21-4.05-1.86-9-6.25-9.75-16.53-.74-10.52 5.37-18 16.33-20s24.27 2 33 11.41a43.49 43.49 0 001.84-20.25l1-.14a44.23 44.23 0 01-2.07 21.24 33.94 33.94 0 016.84 12.59c4.83 16.2-3.24 27.88-11.05 39.17-6.71 9.71-13 18.88-11.55 30.58zm-11.33-94.2a31.2 31.2 0 00-5.51.48c-10.41 1.89-16.21 9-15.52 18.93.54 7.75 3.71 13.18 9.18 15.69 6.87 3.16 16.75 1.16 25.16-5.1a39.92 39.92 0 0013.6-18 36.79 36.79 0 00-26.91-12z" fill="#263238"/><path d="M381.57 278.67c-7.92-7.07-8.83-16.4-4.19-18.37 6.43-2.73 8.83 8.63 4.19 18.37zM375.56 347c-5.66-9-6-20.22.91-21 6.29-.74 7.67 9.92-.91 21z" fill="currentColor"/><path d="M381.75 281.64c-1.09-6.71 2.21-11.71 5.31-10.82 4.29 1.23.94 7.84-5.31 10.82zM366.18 314.07c-3.47-7.48-1.25-14.41 2.67-14.39 5.42.04 3.63 8.87-2.67 14.39zM352.86 284.24c-7.14 1.21-12.5-2.28-11.57-5.59 1.29-4.59 8.36-1.03 11.57 5.59z" fill="currentColor"/><path d="M352.86 284.24c-4.38-2.34-5-5.82-2.76-6.75 3.15-1.29 4.65 2.9 2.76 6.75zM387.28 314c-1.73-4.67.1-7.7 2.41-6.89 3.21 1.13 1.56 5.25-2.41 6.89zM331.11 310.73c-4.95.54-7.44-2-6.09-4 1.88-2.86 5.48-.25 6.09 4zM392.202 396.072l2.36-17.36.99.135-2.36 17.36zM390.413 408.877l1.141-8.393.991.135-1.14 8.393zM344 371.23l5.79 42.34a3.09 3.09 0 003.06 2.67h31.26a3.09 3.09 0 003.06-2.67l5.79-42.34a3.09 3.09 0 00-3.06-3.5H347a3.09 3.09 0 00-3 3.5z" fill="currentColor"/></g></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/bell.svg b/ruoyi-plus-soybean/src/assets/svg-icon/bell.svg
new file mode 100755
index 0000000..bffd0ce
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/bell.svg
@@ -0,0 +1 @@
+<svg data-v-0a0a4a97="" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bell-icon size-4"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"></path><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/cast.svg b/ruoyi-plus-soybean/src/assets/svg-icon/cast.svg
new file mode 100755
index 0000000..4f008d3
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/cast.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="prefix__prefix__feather prefix__prefix__feather-cast"><path d="M2 16.1A5 5 0 015.9 20M2 12.05A9 9 0 019.95 20M2 8V6a2 2 0 012-2h16a2 2 0 012 2v12a2 2 0 01-2 2h-6M2 20h.01"/></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/chrome.svg b/ruoyi-plus-soybean/src/assets/svg-icon/chrome.svg
new file mode 100755
index 0000000..6314173
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/chrome.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="4"/><path d="M21.17 8H12M3.95 6.06L8.54 14m2.34 7.94L15.46 14"/></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/copy.svg b/ruoyi-plus-soybean/src/assets/svg-icon/copy.svg
new file mode 100755
index 0000000..ab25601
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/copy.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/custom-icon.svg b/ruoyi-plus-soybean/src/assets/svg-icon/custom-icon.svg
new file mode 100755
index 0000000..b33a43f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/custom-icon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 24 24"><path fill="currentColor" d="M19 10c0 1.38-2.12 2.5-3.5 2.5s-2.75-1.12-2.75-2.5h-1.5c0 1.38-1.37 2.5-2.75 2.5S5 11.38 5 10h-.75c-.16.64-.25 1.31-.25 2a8 8 0 008 8 8 8 0 008-8c0-.69-.09-1.36-.25-2H19m-7-6C9.04 4 6.45 5.61 5.07 8h13.86C17.55 5.61 14.96 4 12 4m10 8a10 10 0 01-10 10A10 10 0 012 12 10 10 0 0112 2a10 10 0 0110 10m-10 5.23c-1.75 0-3.29-.73-4.19-1.81L9.23 14c.45.72 1.52 1.23 2.77 1.23s2.32-.51 2.77-1.23l1.42 1.42c-.9 1.08-2.44 1.81-4.19 1.81z"/></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/empty-data.svg b/ruoyi-plus-soybean/src/assets/svg-icon/empty-data.svg
new file mode 100755
index 0000000..293486c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/empty-data.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><ellipse cx="250.11" cy="316.94" rx="236.67" ry="136.64" fill="#f5f5f5"/><g fill="#e6e6e6"><ellipse cx="428.84" cy="396.29" rx="47.07" ry="27.17"/><path d="M129.09 343.48l119.83 69.19a9.49 9.49 0 008.56 0l156.35-90c2.36-1.36 2.36-3.56 0-4.92l-120.39-69.33a9.49 9.49 0 00-8.56 0l-155.79 90.12c-2.36 1.37-2.36 3.58 0 4.94zM103.61 326.57l-60.4-34.87c-2.39-1.38-2.39-3.63 0-5 15-8.73 64.11-37.21 64.74-37.36s49.86 28.63 64.9 37.43c2.38 1.4 2.37 3.64 0 5l-60.55 34.79a9.57 9.57 0 01-8.69.01zM311.3 219.16c13.35-7.7 35-7.7 48.33 0 11.31 6.53 13 16.5 5.16 24.08a5.74 5.74 0 011.27.52l42.16 24.34c1.79 1 1.79 2.71 0 3.74l-2.64 1.52a6.62 6.62 0 01-6.37-.18L360 247.49a2.37 2.37 0 01-.45-.36c-13.36 7.63-34.9 7.61-48.2-.07s-13.35-20.19-.05-27.9z"/></g><path d="M305.55 224.18v8.3c0 4.5 3 9 8.93 12.45 11.91 6.87 31.22 6.87 43.13 0 5.95-3.44 8.93-8 8.93-12.45v-8.3c0-4.51-3-9-8.93-12.45-11.91-6.88-31.22-6.88-43.13 0-6 3.43-8.93 7.94-8.93 12.45zm58 0c0 3.6-2.65 7.12-7.45 9.89-5.32 3.07-12.46 4.76-20.09 4.76s-14.78-1.69-20.09-4.76c-4.81-2.77-7.46-6.29-7.46-9.89s2.65-7.12 7.46-9.9c5.31-3.07 12.45-4.76 20.09-4.76s14.77 1.69 20.09 4.76c4.84 2.78 7.49 6.29 7.49 9.9z" fill="currentColor"/><path d="M337 241.77v8.29c-8.13.15-16.35-1.55-22.56-5.13-6-3.44-8.93-8-8.93-12.45v-8.3a8.06 8.06 0 010-.85c-.39 4.78 2.57 9.64 8.9 13.3s14.5 5.29 22.59 5.14z" opacity=".05"/><path d="M357.61 211.73c-11.91-6.88-31.22-6.88-43.13 0s-11.91 18 0 24.9 31.22 6.87 43.13 0 11.91-18.03 0-24.9zm-1.47 22.34c-5.32 3.07-12.46 4.76-20.09 4.76s-14.77-1.69-20.09-4.76c-4.81-2.77-7.46-6.29-7.46-9.89s2.65-7.12 7.46-9.9c5.32-3.07 12.45-4.76 20.09-4.76s14.77 1.69 20.09 4.76c4.81 2.78 7.45 6.29 7.45 9.9s-2.59 7.12-7.45 9.89z" fill="#fff" opacity=".6"/><path d="M363.6 224.18a8.16 8.16 0 01-1.2 4.15 16.47 16.47 0 00-6.27-5.75c-5.31-3.07-12.45-4.76-20.09-4.76s-14.76 1.69-20.08 4.76a16.74 16.74 0 00-6.28 5.75 8.33 8.33 0 01-1.18-4.15c0-3.61 2.65-7.12 7.46-9.9 5.32-3.07 12.45-4.76 20.08-4.76s14.78 1.69 20.09 4.76c4.87 2.78 7.47 6.29 7.47 9.9z" fill="currentColor"/><path d="M356.14 218c-5.32-3.07-12.46-4.76-20.09-4.76S321.28 215 316 218c-4 2.29-6.46 5.08-7.21 8 .75 2.93 3.24 5.72 7.21 8 5.32 3.07 12.45 4.76 20.09 4.76s14.77-1.69 20.09-4.76c4-2.29 6.46-5.08 7.21-8-.79-2.88-3.28-5.67-7.25-8z" fill="#fff" opacity=".5"/><path d="M356.7 241.91a4.77 4.77 0 012.12-3.71 1.51 1.51 0 011.51-.16l10.26 5.85a1.53 1.53 0 01.63 1.38 4.75 4.75 0 01-2.13 3.71 1.51 1.51 0 01-1.51.15l-10.26-5.86a1.51 1.51 0 01-.62-1.36z" fill="#37474f"/><path d="M357.34 243.29a1.51 1.51 0 01-.64-1.38 4.13 4.13 0 01.37-1.6l13.23 7.57a4.07 4.07 0 01-1.19 1.12 1.51 1.51 0 01-1.51.15l-10.26-5.86z" fill="#263238"/><path d="M368.31 246l-.22.35z"/><path d="M403.69 266.47L368.32 246a5.37 5.37 0 011.82-1.9 2 2 0 011.93-.22l34 18.18z" fill="currentColor"/><path d="M403.69 266.47L368.32 246a5.37 5.37 0 011.82-1.9 2 2 0 011.93-.22l34 18.18z" fill="#fff" opacity=".6"/><path d="M403.69 266.47l-2.61 4.62-32.87-20.32a2 2 0 01-.83-1.8 5.17 5.17 0 01.27-1.56c0-.09.08-.2.11-.29.11-.25.22-.49.34-.73l.21-.35z" fill="currentColor"/><path d="M403.69 266.47l-2.61 4.62-32.87-20.32a2 2 0 01-.83-1.8 5.17 5.17 0 01.27-1.56c0-.09.08-.2.11-.29.11-.25.22-.49.34-.73l.21-.35z" opacity=".05"/><path d="M403.64 270.83a8.06 8.06 0 003.62-6.34c0-2.32-1.64-3.26-3.65-2.09a8.06 8.06 0 00-3.62 6.33c.01 2.33 1.64 3.27 3.65 2.1z" fill="currentColor"/><path d="M403.64 270.83a8.06 8.06 0 003.62-6.34c0-2.32-1.64-3.26-3.65-2.09a8.06 8.06 0 00-3.62 6.33c.01 2.33 1.64 3.27 3.65 2.1z" opacity=".3"/><g><g fill="currentColor"><path d="M172.93 385.59a5.81 5.81 0 01-2.85-.69l-92.29-53.29a2.07 2.07 0 010-3.86l21.76-12.57.5.87-21.76 12.56a1.37 1.37 0 00-.83 1.07 1.4 1.4 0 00.83 1.07L170.58 384a5.25 5.25 0 004.7 0L193 373.8l.5.87-17.72 10.23a5.84 5.84 0 01-2.85.69z"/><path d="M172.93 395a5.81 5.81 0 01-2.85-.69l-108.54-62.7a2.07 2.07 0 010-3.86l29.9-17.27.5.87L62 328.61a1.1 1.1 0 000 2.14l108.54 62.66a5.25 5.25 0 004.7 0l25.83-14.91.5.86-25.83 14.92a5.84 5.84 0 01-2.81.72z"/><path d="M172.93 404.35a5.81 5.81 0 01-2.85-.69L45.29 331.61a2.29 2.29 0 01-1.29-1.93 2.32 2.32 0 011.32-1.94l38-21.95.5.87-38 21.95a1.1 1.1 0 000 2.13l124.79 72.05a5.25 5.25 0 004.7 0l34-19.6.5.86-34 19.61a5.84 5.84 0 01-2.88.69z"/></g></g><g><path fill="#263238" d="M42.94 268.7l65 37.53 64.99-37.53-64.99-37.52-65 37.52z"/><path d="M107.93 306.22v17.3a8.84 8.84 0 01-4.32-1l-28.17-16.29L45.54 289a5.73 5.73 0 01-2.6-4.5v-15.8l63.2 36.49z" fill="#455a64"/><path d="M172.93 268.7v15.76a5.76 5.76 0 01-2.6 4.5l-25.84 14.92-32.23 18.61a8.86 8.86 0 01-4.33 1v-17.3l2-1.16z" fill="#37474f"/><path d="M47 276.24v2.12a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25V277a1 1 0 00-.43-.75l-.49-.28c-.23-.12-.43.03-.43.27zM49.19 277.5v2.12a.94.94 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM51.38 278.76v2.13a1 1 0 00.43.75l.49.28c.24.13.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25zM53.57 280v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.11a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.02-.43.23z" fill="currentColor"/><path d="M55.76 281.28v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.19-.13-.43-.03-.43.25z" fill="#263238"/><path d="M58 282.54v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a.93.93 0 00-.43-.75l-.49-.29c-.29-.13-.43-.02-.43.25z" fill="currentColor"/><path d="M60.14 283.81v2.12a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM62.32 285.07v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM64.51 286.33v2.12a1 1 0 00.43.75l.49.29c.24.13.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25z" fill="#263238"/><path d="M66.7 287.59v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM68.89 288.85V291a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.16-.43-.04-.43.23z" fill="currentColor"/><path d="M71.08 290.11v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a.93.93 0 00-.43-.75l-.49-.29c-.24-.13-.43-.02-.43.25zM73.27 291.38v2.12a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.13-.43-.03-.43.25z" fill="#263238"/><path d="M75.45 292.64v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM77.64 293.9v2.1a.94.94 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.12-.44-.01-.44.27zM79.83 295.16v2.13a1 1 0 00.43.75l.49.28c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.26-.14-.43-.03-.43.25zM82 296.42v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.22-.17-.43-.02-.43.25zM47 282.54v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.29c-.23-.13-.43-.02-.43.25z" fill="currentColor"/><path d="M49.19 283.81v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25z" fill="#263238"/><path d="M51.38 285.07v2.12a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25z" fill="currentColor"/><path d="M53.57 286.33v2.12a.93.93 0 00.43.75l.49.29c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM55.76 287.59v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.19-.14-.43-.03-.43.25z" fill="#263238"/><path d="M58 288.85V291a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.29-.16-.43-.04-.43.23zM60.14 290.11v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.29c-.24-.13-.43-.02-.43.25zM62.32 291.38v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.48-.28c-.24-.13-.44-.03-.44.25zM64.51 292.64v2.12a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25z" fill="currentColor"/><path d="M66.7 293.9v2.1a.93.93 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.12-.43-.01-.43.27zM68.89 295.16v2.13a1 1 0 00.43.75l.49.28c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25z" fill="#263238"/><path d="M71.08 296.42v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.17-.43-.02-.43.25zM73.27 297.68v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.13-.43-.02-.43.25z" fill="currentColor"/><path d="M75.45 298.94v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.48-.29c-.24-.13-.44-.02-.44.25zM77.64 300.21v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25V301a1 1 0 00-.44-.75l-.48-.28c-.24-.15-.44-.04-.44.24zM79.83 301.47v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.26-.14-.43-.03-.43.25zM82 302.73v2.12a.93.93 0 00.43.75l.49.29c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.22-.14-.43-.03-.43.25z" fill="#263238"/><path d="M89.33 303.62l1-.57a1.32 1.32 0 011.32.14 4.16 4.16 0 011.88 3.26 1.3 1.3 0 01-.55 1.21l-.92.53-.17-.27a2.3 2.3 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.75 1.75 0 01.06-.48z" fill="currentColor"/><path d="M89.33 303.62l1-.57a1.32 1.32 0 011.32.14 4.16 4.16 0 011.88 3.26 1.3 1.3 0 01-.55 1.21l-.92.53-.17-.27a2.3 2.3 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.75 1.75 0 01.06-.48z" opacity=".2"/><path d="M90.7 303.73a4.14 4.14 0 011.88 3.27c0 1.2-.84 1.68-1.88 1.08a4.16 4.16 0 01-1.88-3.26c0-1.21.84-1.69 1.88-1.09zM97.5 308.33l1-.57a1.29 1.29 0 011.32.13 4.16 4.16 0 011.88 3.26 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.8 1.8 0 01.06-.48z" fill="currentColor"/><path d="M97.5 308.33l1-.57a1.29 1.29 0 011.32.13 4.16 4.16 0 011.88 3.26 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.8 1.8 0 01.06-.48z" opacity=".2"/><path d="M98.87 308.44a4.16 4.16 0 011.88 3.26c0 1.2-.84 1.69-1.88 1.09a4.17 4.17 0 01-1.87-3.26c0-1.2.83-1.69 1.87-1.09z" fill="currentColor"/><path fill="#263238" d="M42.94 240.56l65 37.52 64.99-37.52-64.99-37.52-65 37.52z"/><path fill="#455a64" d="M42.94 259.32l65 37.53v-18.77l-65-37.52v18.76z"/><path fill="#37474f" d="M107.94 296.85l64.99-37.53v-18.76l-64.99 37.52v18.77z"/><path fill="currentColor" d="M51.07 268.7v-4.69l56.87 32.84v4.69L51.07 268.7zM168.87 261.67v4.69l-60.93 35.18v-4.69l60.93-35.18z"/><path opacity=".2" d="M168.87 261.67v4.69l-60.93 35.18v-4.69l60.93-35.18z"/><path d="M47 248.1v2.12a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.23-.14-.43-.03-.43.25z" fill="#263238"/><path d="M49.19 249.36v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.11-.44-.03-.44.25zM51.38 250.62v2.12a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25z" fill="currentColor"/><path d="M53.57 251.88V254a1 1 0 00.43.75l.49.28c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.13-.43-.02-.43.26zM55.76 253.14v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.19-.14-.43-.02-.43.25z" fill="#263238"/><path d="M58 254.4v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.29-.15-.43-.02-.43.25zM60.14 255.66v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.29c-.24-.13-.43-.02-.43.25zM62.32 256.93v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM64.51 258.19v2.12a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25V259a1 1 0 00-.44-.75l-.49-.28c-.23-.17-.43-.06-.43.22z" fill="currentColor"/><path d="M66.7 259.45v2.12a.93.93 0 00.43.75l.49.29c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM68.89 260.71v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM71.08 262v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13A1 1 0 0072 262l-.49-.28c-.24-.14-.43-.02-.43.28z" fill="#263238"/><path d="M73.27 263.23v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25V264a1 1 0 00-.43-.75l-.49-.25c-.24-.15-.43 0-.43.23zM75.45 264.5v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM77.64 265.76v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25z" fill="currentColor"/><path d="M79.83 267v2.12a.93.93 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.1a1 1 0 00-.43-.75l-.49-.28c-.26-.14-.43-.03-.43.23z" fill="#263238"/><path d="M82 268.28v2.12a.93.93 0 00.43.75l.49.29c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.22-.14-.43-.03-.43.25zM47 254.4v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.23-.15-.43-.02-.43.25zM49.19 255.66v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.48-.29c-.24-.13-.44-.02-.44.25zM51.38 256.93v2.12a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25zM53.57 258.19v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25V259a1 1 0 00-.43-.75l-.49-.28c-.24-.17-.43-.06-.43.22z" fill="currentColor"/><path d="M55.76 259.45v2.12a.93.93 0 00.43.75l.49.29c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.19-.14-.43-.03-.43.25zM58 260.71v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.29-.14-.43-.03-.43.25z" fill="#263238"/><path d="M60.14 262v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.17-.43-.05-.43.25zM62.32 263.23v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25V264a.94.94 0 00-.44-.75l-.48-.29c-.24-.11-.44.04-.44.27zM64.51 264.5v2.12a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25zM66.7 265.76v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM68.89 267v2.12a.93.93 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.1a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.23z" fill="currentColor"/><path d="M71.08 268.28v2.12a.93.93 0 00.43.75l.49.29c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25z" fill="#263238"/><path d="M73.27 269.54v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.02-.43.25zM75.45 270.8v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28a.27.27 0 00-.44.25zM77.64 272.06v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.48-.29c-.24-.13-.44-.02-.44.25zM79.83 273.33v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.26-.14-.43-.03-.43.25zM82 274.59v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.22-.14-.43-.03-.43.25zM89.33 275.48l1-.58a1.32 1.32 0 011.32.14 4.14 4.14 0 011.88 3.25 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.86 1.86 0 01.06-.48z" fill="currentColor"/><path d="M89.33 275.48l1-.58a1.32 1.32 0 011.32.14 4.14 4.14 0 011.88 3.25 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.86 1.86 0 01.06-.48z" opacity=".2"/><path d="M90.7 275.58a4.16 4.16 0 011.88 3.26c0 1.2-.84 1.69-1.88 1.09a4.16 4.16 0 01-1.88-3.26c0-1.2.84-1.67 1.88-1.09zM97.5 280.19l1-.57a1.29 1.29 0 011.32.13 4.16 4.16 0 011.88 3.25 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.75 1.75 0 01.06-.48z" fill="currentColor"/><path d="M97.5 280.19l1-.57a1.29 1.29 0 011.32.13 4.16 4.16 0 011.88 3.25 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.75 1.75 0 01.06-.48z" opacity=".2"/><path d="M98.87 280.3a4.16 4.16 0 011.88 3.26c0 1.2-.84 1.68-1.88 1.08a4.15 4.15 0 01-1.87-3.25c0-1.21.83-1.69 1.87-1.09z" fill="currentColor"/><path fill="#263238" d="M42.94 212.42l65 37.52 64.99-37.52-64.99-37.53-65 37.53z"/><path fill="#455a64" d="M42.94 231.18l65 37.52v-18.76l-65-37.52v18.76z"/><path fill="#37474f" d="M107.94 268.7l64.99-37.52v-18.76l-64.99 37.52v18.76z"/><path fill="currentColor" d="M51.07 240.56v-4.69l56.87 32.83v4.69l-56.87-32.83zM168.87 233.52v4.69l-60.93 35.18v-4.69l60.93-35.18z"/><path opacity=".2" d="M168.87 233.52v4.69l-60.93 35.18v-4.69l60.93-35.18z"/><path d="M47 220v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.23-.19-.43-.07-.43.25zM49.19 221.21v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25V222a.94.94 0 00-.44-.75l-.48-.29c-.24-.13-.44-.02-.44.25zM51.38 222.48v2.12a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25z" fill="currentColor"/><path d="M53.57 223.74v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM55.76 225v2.12a.93.93 0 00.43.75l.49.29c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.19-.14-.43-.03-.43.25z" fill="#263238"/><path d="M58 226.26v2.13a1 1 0 00.43.75l.49.28c.24.13.43 0 .43-.25V227a1 1 0 00-.43-.75l-.49-.28c-.29-.1-.43.03-.43.29zM60.14 227.52v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.02-.43.25zM62.32 228.78v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28c-.24-.13-.44-.02-.44.25zM64.51 230.05v2.12a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25zM66.7 231.31v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.06-.43.25zM68.89 232.57v2.12a.93.93 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM71.08 233.83V236a.93.93 0 00.43.75l.49.25c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.15-.43-.04-.43.24z" fill="currentColor"/><path d="M73.27 235.09v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25z" fill="#263238"/><path d="M75.45 236.35v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28a.27.27 0 00-.44.25zM77.64 237.61v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.48-.29c-.24-.13-.44-.02-.44.25zM79.83 238.88V241a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.26-.14-.43-.03-.43.25zM82 240.14v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.22-.14-.43-.03-.43.25zM47 226.26v2.13a1 1 0 00.43.75l.49.28a.27.27 0 00.43-.25V227a1 1 0 00-.43-.75l-.49-.28c-.23-.1-.43.03-.43.29z" fill="currentColor"/><path d="M49.19 227.52v2.13a1 1 0 00.44.75l.48.28a.27.27 0 00.44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28a.27.27 0 00-.44.25zM51.38 228.78v2.13a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.49-.28a.27.27 0 00-.43.25z" fill="#263238"/><path d="M53.57 230.05v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a.93.93 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM55.76 231.31v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.19-.14-.43-.06-.43.25z" fill="currentColor"/><path d="M58 232.57v2.12a.93.93 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.29-.14-.43-.03-.43.25z" fill="#263238"/><path d="M60.14 233.83V236a.93.93 0 00.43.75l.49.29a.27.27 0 00.43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.19-.43-.08-.43.2zM62.32 235.09v2.13a1 1 0 00.44.75l.48.28a.27.27 0 00.44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM64.51 236.35v2.13a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.49-.28c-.23-.1-.43-.02-.43.25zM66.7 237.61v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a.93.93 0 00-.43-.75l-.49-.29c-.24-.13-.43-.02-.43.25z" fill="currentColor"/><path d="M68.89 238.88V241a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM71.08 240.14v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25z" fill="#263238"/><path d="M73.27 241.4v2.12a.93.93 0 00.43.75l.49.29a.27.27 0 00.43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.15-.43-.03-.43.25zM75.45 242.66v2.13a1 1 0 00.44.75l.48.28c.24.13.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM77.64 243.92v2.13a1 1 0 00.44.75l.48.28a.27.27 0 00.44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28a.27.27 0 00-.44.25z" fill="currentColor"/><path d="M79.83 245.18v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25V246a1 1 0 00-.43-.75l-.49-.28c-.26-.17-.43-.06-.43.21z" fill="#263238"/><path d="M82 246.44v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a.93.93 0 00-.43-.75l-.49-.29c-.22-.13-.43-.02-.43.25zM89.33 247.33l1-.57a1.32 1.32 0 011.32.13 4.16 4.16 0 011.88 3.26 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.8 1.8 0 01.06-.48z" fill="currentColor"/><path d="M89.33 247.33l1-.57a1.32 1.32 0 011.32.13 4.16 4.16 0 011.88 3.26 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.8 1.8 0 01.06-.48z" opacity=".2"/><path d="M90.7 247.44a4.16 4.16 0 011.88 3.26c0 1.2-.84 1.69-1.88 1.09a4.16 4.16 0 01-1.88-3.26c0-1.2.84-1.69 1.88-1.09zM97.5 252.05l1-.57a1.32 1.32 0 011.32.14 4.16 4.16 0 011.88 3.26 1.3 1.3 0 01-.55 1.21l-.92.52-.17-.27a2.3 2.3 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.75 1.75 0 01.06-.48z" fill="currentColor"/><path d="M97.5 252.05l1-.57a1.32 1.32 0 011.32.14 4.16 4.16 0 011.88 3.26 1.3 1.3 0 01-.55 1.21l-.92.52-.17-.27a2.3 2.3 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.75 1.75 0 01.06-.48z" opacity=".2"/><path d="M98.87 252.16a4.16 4.16 0 011.88 3.26c0 1.2-.84 1.68-1.88 1.08a4.17 4.17 0 01-1.87-3.26c0-1.24.83-1.68 1.87-1.08z" fill="currentColor"/><path fill="#263238" d="M42.94 184.27l65 37.53 64.99-37.53-64.99-37.52-65 37.52z"/><path fill="#455a64" d="M42.94 203.04l65 37.52V221.8l-65-37.53v18.77z"/><path fill="#37474f" d="M107.94 240.56l64.99-37.52v-18.77l-64.99 37.53v18.76z"/><path fill="currentColor" d="M51.07 212.42v-4.7l56.87 32.84v4.69l-56.87-32.83zM168.87 205.38v4.69l-60.93 35.18v-4.69l60.93-35.18z"/><path opacity=".2" d="M168.87 205.38v4.69l-60.93 35.18v-4.69l60.93-35.18z"/><path d="M47 191.81v2.13a1 1 0 00.43.75l.49.28a.27.27 0 00.43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.23-.14-.43-.03-.43.25z" fill="currentColor"/><path d="M49.19 193.07v2.13a1 1 0 00.44.75l.48.28a.27.27 0 00.44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28a.27.27 0 00-.44.25z" fill="#263238"/><path d="M51.38 194.33v2.13a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.49-.28a.27.27 0 00-.43.25z" fill="currentColor"/><path d="M53.57 195.59v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a.93.93 0 00-.43-.75l-.49-.29c-.24-.13-.43-.02-.43.25zM55.76 196.86V199a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.19-.16-.43-.05-.43.23zM58 198.12v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.29-.14-.43-.03-.43.25zM60.14 199.38v2.12a.93.93 0 00.43.75l.49.29a.27.27 0 00.43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.13-.43-.03-.43.25z" fill="#263238"/><path d="M62.32 200.64v2.13a1 1 0 00.44.75l.48.28a.27.27 0 00.44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM64.51 201.9v2.1a1 1 0 00.43.75l.49.28a.27.27 0 00.44-.25v-2.13a1 1 0 00-.44-.75l-.49-.28c-.23-.11-.43.01-.43.28zM66.7 203.16v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25V204a.93.93 0 00-.43-.75l-.49-.29c-.24-.18-.43-.07-.43.2zM68.89 204.43v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a.93.93 0 00-.43-.75l-.49-.28c-.24-.18-.43-.03-.43.25z" fill="currentColor"/><path d="M71.08 205.69v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM73.27 207v2.12a.93.93 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.19-.43-.08-.43.25zM75.45 208.21v2.13a1 1 0 00.44.75l.48.28c.24.13.44 0 .44-.25V209a1 1 0 00-.44-.75l-.48-.28c-.24-.15-.44-.04-.44.24z" fill="#263238"/><path d="M77.64 209.47v2.13a1 1 0 00.44.75l.48.28a.27.27 0 00.44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28a.27.27 0 00-.44.25z" fill="currentColor"/><path d="M79.83 210.73v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.26-.14-.43-.02-.43.25zM82 212v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a.93.93 0 00-.43-.75l-.49-.29c-.22-.14-.43-.03-.43.25zM47 198.12v2.12a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.23-.14-.43-.03-.43.25zM49.19 199.38v2.12a.94.94 0 00.44.75l.48.29c.24.13.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28c-.24-.13-.44-.03-.44.25z" fill="#263238"/><path d="M51.38 200.64v2.13a1 1 0 00.43.75l.49.28a.27.27 0 00.44-.25v-2.13a1 1 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25z" fill="currentColor"/><path d="M53.57 201.9v2.1a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.11-.43.01-.43.28zM55.76 203.16v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25V204a.93.93 0 00-.43-.75l-.49-.29c-.19-.18-.43-.07-.43.2zM58 204.43v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a.93.93 0 00-.43-.75l-.49-.28c-.29-.18-.43-.03-.43.25zM60.14 205.69v2.12a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25z" fill="#263238"/><path d="M62.32 207v2.12a.94.94 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.19-.44-.08-.44.25z" fill="currentColor"/><path d="M64.51 208.21v2.13a1 1 0 00.43.75l.49.28c.24.13.44 0 .44-.25V209a1 1 0 00-.44-.75l-.49-.28c-.23-.15-.43-.04-.43.24zM66.7 209.47v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.02-.43.25zM68.89 210.73v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.02-.43.25z" fill="#263238"/><path d="M71.08 212v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12A.93.93 0 0072 212l-.49-.29c-.24-.1-.43.01-.43.29zM73.27 213.26v2.12a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25V214a1 1 0 00-.43-.75l-.49-.25c-.24-.13-.43 0-.43.26z" fill="currentColor"/><path d="M75.45 214.52v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM77.64 215.78v2.12a.94.94 0 00.44.75l.48.29c.24.13.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM79.83 217v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.26-.1-.43.01-.43.25zM82 218.3v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.22-.14-.43-.05-.43.25z" fill="#263238"/><path d="M89.33 219.19l1-.57a1.29 1.29 0 011.32.13 4.16 4.16 0 011.88 3.25 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.75 1.75 0 01.06-.48z" fill="currentColor"/><path d="M89.33 219.19l1-.57a1.29 1.29 0 011.32.13 4.16 4.16 0 011.88 3.25 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.75 1.75 0 01.06-.48z" opacity=".2"/><path d="M90.7 219.3a4.16 4.16 0 011.88 3.26c0 1.2-.84 1.68-1.88 1.08a4.14 4.14 0 01-1.88-3.25c0-1.2.84-1.69 1.88-1.09zM97.5 223.91l1-.58a1.34 1.34 0 011.32.13 4.16 4.16 0 011.88 3.26 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.86 1.86 0 01.06-.48z" fill="currentColor"/><path d="M97.5 223.91l1-.58a1.34 1.34 0 011.32.13 4.16 4.16 0 011.88 3.26 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.86 1.86 0 01.06-.48z" opacity=".2"/><path d="M98.87 224a4.16 4.16 0 011.88 3.26c0 1.2-.84 1.69-1.88 1.09A4.17 4.17 0 0197 225.1c0-1.2.83-1.69 1.87-1.1z" fill="currentColor"/><path fill="#263238" d="M42.94 156.13l65 37.53 64.99-37.53-64.99-37.52-65 37.52z"/><path fill="#455a64" d="M42.94 174.89l65 37.53v-18.76l-65-37.53v18.76z"/><path fill="#37474f" d="M107.94 212.42l64.99-37.53v-18.76l-64.99 37.53v18.76z"/><path fill="currentColor" d="M51.07 184.27v-4.69l56.87 32.84v4.69l-56.87-32.84zM168.87 177.24v4.69l-60.93 35.18v-4.69l60.93-35.18z"/><path opacity=".2" d="M168.87 177.24v4.69l-60.93 35.18v-4.69l60.93-35.18z"/><path d="M47 163.67v2.12a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.23-.14-.43-.03-.43.25zM49.19 164.93v2.12a.94.94 0 00.44.75l.48.29c.24.13.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25z" fill="currentColor"/><path d="M51.38 166.19v2.13a1 1 0 00.43.75l.49.28c.24.13.44 0 .44-.25V167a1 1 0 00-.44-.75l-.49-.28c-.23-.17-.43-.06-.43.22z" fill="#263238"/><path d="M53.57 167.45v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.02-.43.25zM55.76 168.71v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.19-.13-.43-.02-.43.25z" fill="currentColor"/><path d="M58 170v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a.93.93 0 00-.43-.75l-.49-.28c-.29-.16-.43-.05-.43.25zM60.14 171.24v2.12a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25V172a1 1 0 00-.43-.75l-.49-.28c-.24-.12-.43.03-.43.27z" fill="#263238"/><path d="M62.32 172.5v2.12a.94.94 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25z" fill="currentColor"/><path d="M64.51 173.76v2.12a1 1 0 00.43.75l.49.29c.24.13.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25z" fill="#263238"/><path d="M66.7 175v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.11a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.02-.43.23zM68.89 176.28v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM71.08 177.54v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a.93.93 0 00-.43-.75l-.49-.29c-.24-.13-.43-.02-.43.25zM73.27 178.81v2.12a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25z" fill="currentColor"/><path d="M75.45 180.07v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM77.64 181.33v2.12a.94.94 0 00.44.75l.48.29c.24.13.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM79.83 182.59v2.13a1 1 0 00.43.75l.49.28c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.26-.14-.43-.03-.43.25z" fill="#263238"/><path d="M82 183.85V186a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.22-.16-.43-.04-.43.23zM47 170v2.12a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.23-.16-.43-.05-.43.25zM49.19 171.24v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25V172a1 1 0 00-.44-.75l-.48-.28c-.24-.12-.44.03-.44.27zM51.38 172.5v2.12a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25z" fill="currentColor"/><path d="M53.57 173.76v2.12a.93.93 0 00.43.75l.49.29c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM55.76 175v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.11a1 1 0 00-.43-.75l-.49-.28c-.19-.14-.43-.02-.43.23z" fill="#263238"/><path d="M58 176.28v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.29-.14-.43-.03-.43.25zM60.14 177.54v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.29c-.24-.13-.43-.02-.43.25zM62.32 178.81v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25z" fill="currentColor"/><path d="M64.51 180.07v2.12a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25zM66.7 181.33v2.12a.93.93 0 00.43.75l.49.29c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM68.89 182.59v2.13a1 1 0 00.43.75l.49.28c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM71.08 183.85V186a1 1 0 00.43.75l.49.25c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.13-.43-.01-.43.26z" fill="#263238"/><path d="M73.27 185.11v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.13-.43-.02-.43.25zM75.45 186.37v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.48-.29c-.24-.12-.44-.02-.44.25zM77.64 187.64v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM79.83 188.9v2.1a.93.93 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.26-.12-.43-.01-.43.27zM82 190.16v2.12a.93.93 0 00.43.75l.49.29c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.22-.14-.43-.03-.43.25zM89.33 191.05l1-.57a1.32 1.32 0 011.32.14 4.16 4.16 0 011.88 3.26 1.3 1.3 0 01-.55 1.21l-.92.52-.17-.27a2.3 2.3 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.75 1.75 0 01.06-.48z" fill="currentColor"/><path d="M89.33 191.05l1-.57a1.32 1.32 0 011.32.14 4.16 4.16 0 011.88 3.26 1.3 1.3 0 01-.55 1.21l-.92.52-.17-.27a2.3 2.3 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.75 1.75 0 01.06-.48z" opacity=".2"/><path d="M90.7 191.16a4.16 4.16 0 011.88 3.26c0 1.2-.84 1.68-1.88 1.08a4.16 4.16 0 01-1.88-3.26c0-1.24.84-1.68 1.88-1.08zM97.5 195.76l1-.57a1.32 1.32 0 011.32.13 4.16 4.16 0 011.88 3.26 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.8 1.8 0 01.06-.48z" fill="currentColor"/><path d="M97.5 195.76l1-.57a1.32 1.32 0 011.32.13 4.16 4.16 0 011.88 3.26 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.8 1.8 0 01.06-.48z" opacity=".2"/><path d="M98.87 195.87a4.16 4.16 0 011.88 3.26c0 1.2-.84 1.69-1.88 1.09A4.17 4.17 0 0197 197c0-1.24.83-1.73 1.87-1.13z" fill="currentColor"/><path d="M170.33 126.49L110.5 92a5.8 5.8 0 00-5.2 0l-59.76 34.49a5.76 5.76 0 00-2.6 4.5v15.76l65 37.52 65-37.52V131a5.76 5.76 0 00-2.61-4.51z" fill="#37474f"/><path d="M108 164.63v19.64l-65-37.52V131a5.81 5.81 0 012.13-4.17c-1 .83-.79 1.94.47 2.67l59.78 34.5a5.31 5.31 0 002.62.63z" fill="#455a64"/><path d="M45.54 129.49L105.38 164a5.72 5.72 0 005.19 0l59.76-34.52c1.44-.83 1.44-2.17 0-3L110.5 92a5.8 5.8 0 00-5.2 0l-59.76 34.49c-1.44.83-1.44 2.17 0 3z" fill="#263238"/><path fill="currentColor" d="M51.07 156.13v-4.69l56.87 32.83v4.69l-56.87-32.83zM168.87 149.09v4.69l-60.93 35.18v-4.69l60.93-35.18z"/><path opacity=".2" d="M168.87 149.09v4.69l-60.93 35.18v-4.69l60.93-35.18z"/><path d="M47 135.52v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.29c-.23-.13-.43-.02-.43.25zM49.19 136.79v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.48-.28c-.24-.14-.44-.03-.44.25zM51.38 138.05v2.12a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25z" fill="#263238"/><path d="M53.57 139.31v2.12a.93.93 0 00.43.75l.49.29c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.06-.43.25zM55.76 140.57v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.19-.14-.43-.03-.43.25z" fill="currentColor"/><path d="M58 141.83V144a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.29-.18-.43-.06-.43.21zM60.14 143.09v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.29c-.24-.13-.43-.02-.43.25zM62.32 144.36v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.48-.28c-.24-.11-.44-.03-.44.25zM64.51 145.62v2.12a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.12a1 1 0 00-.44-.75l-.49-.28c-.23-.14-.43-.03-.43.25zM66.7 146.88V149a.93.93 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25z" fill="#263238"/><path d="M68.89 148.14v2.13a1 1 0 00.43.75l.49.28c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM71.08 149.4v2.13a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.15-.43-.02-.43.25z" fill="currentColor"/><path d="M73.27 150.66v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.13-.43-.02-.43.25z" fill="#263238"/><path d="M75.45 151.92v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.48-.29c-.24-.13-.44-.02-.44.25z" fill="currentColor"/><path d="M77.64 153.19v2.12a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25V154a1 1 0 00-.44-.75l-.48-.28c-.24-.17-.44-.06-.44.22zM79.83 154.45v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.26-.14-.43-.03-.43.25zM82 155.71v2.12a.93.93 0 00.43.75l.49.29c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.22-.14-.43-.03-.43.25z" fill="#263238"/><path d="M47 141.83V144a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.23-.18-.43-.06-.43.21z" fill="currentColor"/><path d="M49.19 143.09v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.48-.29c-.24-.13-.44-.02-.44.25zM51.38 144.36v2.12a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.49-.28c-.23-.11-.43-.03-.43.25zM53.57 145.62v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM55.76 146.88V149a.93.93 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.19-.14-.43-.03-.43.25z" fill="#263238"/><path d="M58 148.14v2.13a1 1 0 00.43.75l.49.28c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.29-.14-.43-.03-.43.25z" fill="currentColor"/><path d="M60.14 149.4v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.15-.43-.02-.43.25z" fill="#263238"/><path d="M62.32 150.66v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.13a1 1 0 00-.44-.75l-.48-.28c-.24-.13-.44-.02-.44.25z" fill="currentColor"/><path d="M64.51 151.92v2.13a1 1 0 00.43.75l.49.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.49-.29a.27.27 0 00-.43.25z" fill="#263238"/><path d="M66.7 153.19v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25V154a1 1 0 00-.43-.75l-.49-.28c-.24-.17-.43-.06-.43.22z" fill="currentColor"/><path d="M68.89 154.45v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM71.08 155.71v2.12a.93.93 0 00.43.75l.49.29c.24.13.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.14-.43-.03-.43.25zM73.27 157v2.13a1 1 0 00.43.75l.49.28c.23.14.43 0 .43-.25v-2.13a1 1 0 00-.43-.75l-.49-.28c-.24-.17-.43-.06-.43.25zM75.45 158.23v2.13a1 1 0 00.44.75l.48.28a.27.27 0 00.44-.25V159a1 1 0 00-.44-.75l-.48-.28a.27.27 0 00-.44.26zM77.64 159.49v2.13a1 1 0 00.44.75l.48.28c.24.14.44 0 .44-.25v-2.12a.94.94 0 00-.44-.75l-.48-.29c-.24-.13-.44-.02-.44.25z" fill="#263238"/><path d="M79.83 160.76v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.12a.93.93 0 00-.43-.75l-.49-.28c-.26-.14-.43-.03-.43.25z" fill="currentColor"/><path d="M82 162v2.12a1 1 0 00.43.75l.49.28c.24.14.43 0 .43-.25v-2.1a1 1 0 00-.43-.75l-.49-.28c-.22-.14-.43-.03-.43.23z" fill="#263238"/><path d="M89.33 162.91l1-.58a1.34 1.34 0 011.32.13 4.16 4.16 0 011.88 3.26 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.86 1.86 0 01.06-.48z" fill="currentColor"/><path d="M89.33 162.91l1-.58a1.34 1.34 0 011.32.13 4.16 4.16 0 011.88 3.26 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.86 1.86 0 01.06-.48z" opacity=".2"/><path d="M90.7 163a4.16 4.16 0 011.88 3.26c0 1.2-.84 1.69-1.88 1.09a4.16 4.16 0 01-1.88-3.26c0-1.19.84-1.68 1.88-1.09zM97.5 167.62l1-.57a1.32 1.32 0 011.32.14 4.16 4.16 0 011.88 3.26 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.75 1.75 0 01.06-.48z" fill="currentColor"/><path d="M97.5 167.62l1-.57a1.32 1.32 0 011.32.14 4.16 4.16 0 011.88 3.26 1.33 1.33 0 01-.55 1.22l-.92.53-.17-.28a1.93 1.93 0 01-.54-.21 4.16 4.16 0 01-1.88-3.26 1.75 1.75 0 01.06-.48z" opacity=".2"/><path d="M98.87 167.73a4.16 4.16 0 011.88 3.26c0 1.2-.84 1.68-1.88 1.08a4.17 4.17 0 01-1.87-3.26c0-1.2.83-1.68 1.87-1.08z" fill="currentColor"/></g><g><path d="M134.91 338.6a8.43 8.43 0 003.94 6.54l110.29 63.68a8.75 8.75 0 007.88 0l150.91-87.13a7.4 7.4 0 000-13.08l-110.3-63.67a8.67 8.67 0 00-7.87 0l-150.91 87.13a8.42 8.42 0 00-3.94 6.53z" fill="currentColor"/><path d="M253.08 401.24v8.52a8 8 0 01-3.94-.94l-110.29-63.68a7.37 7.37 0 01-.4-12.82c-1.77 1.26-1.63 3.12.4 4.29l110.29 63.68a8 8 0 003.94.95z" fill="#fff" opacity=".2"/><path d="M131.22 332.21a8.45 8.45 0 01-3.69-6.68V193.91a8.72 8.72 0 013.93-6.82L282.38 100a7.4 7.4 0 0111.32 6.54v131.58a8.7 8.7 0 01-3.94 6.82l-150.91 87.13a8.48 8.48 0 01-7.63.14z" fill="currentColor"/><path d="M138.38 332.3a7.37 7.37 0 01-10.85-6.77V193.91a8 8 0 011.15-3.88l7.39 4.26a8 8 0 00-1.16 3.87v131.63c0 2.33 1.52 3.38 3.47 2.51z" fill="#fff" opacity=".2"/><path d="M134.91 198.17v131.62c0 2.51 1.76 3.53 3.94 2.28l150.91-87.13a8.7 8.7 0 003.94-6.82V106.5c0-2.51-1.76-3.53-3.94-2.27l-150.91 87.12a8.7 8.7 0 00-3.94 6.82z" fill="#fff" opacity=".4"/><path d="M138.85 336.61l110.29 63.68a8.75 8.75 0 007.88 0l150.91-87.13c2.17-1.26 2.17-3.29 0-4.55l-110.3-63.67a8.67 8.67 0 00-7.87 0l-150.91 87.13c-2.18 1.25-2.18 3.29 0 4.54z" fill="currentColor"/><path d="M138.85 336.61l110.29 63.68a8.75 8.75 0 007.88 0l150.91-87.13c2.17-1.26 2.17-3.29 0-4.55l-110.3-63.67a8.67 8.67 0 00-7.87 0l-150.91 87.13c-2.18 1.25-2.18 3.29 0 4.54z" fill="#fff" opacity=".6"/><path d="M141 198.66l145.5-84c1.3-.76 2.36-.15 2.36 1.36V230a5.23 5.23 0 01-2.36 4.09l-145.5 84c-1.31.75-2.37.14-2.37-1.36V202.75a5.2 5.2 0 012.37-4.09z" fill="#455a64"/><path d="M151.65 333.2l140.08-80.87a4.34 4.34 0 013.94 0l7.16 4.13a1.2 1.2 0 010 2.28l-140.1 80.86a4.34 4.34 0 01-3.94 0l-7.14-4.13a1.2 1.2 0 010-2.27z" fill="currentColor"/><path d="M151.65 333.2l140.08-80.87a4.34 4.34 0 013.94 0l7.16 4.13a1.2 1.2 0 010 2.28l-140.1 80.86a4.34 4.34 0 01-3.94 0l-7.14-4.13a1.2 1.2 0 010-2.27z" fill="#fff" opacity=".4"/><path d="M170.12 343.86L310.19 263a4.34 4.34 0 013.94 0l44.07 25.44a1.2 1.2 0 010 2.27l-139.92 80.76a4.37 4.37 0 01-3.94 0l-44.22-25.34a1.2 1.2 0 010-2.27z" fill="currentColor"/><path d="M170.12 343.86L310.19 263a4.34 4.34 0 013.94 0l44.07 25.44a1.2 1.2 0 010 2.27l-139.92 80.76a4.37 4.37 0 01-3.94 0l-44.22-25.34a1.2 1.2 0 010-2.27z" fill="#fff" opacity=".4"/><path d="M295.68 299.37l4.39-2.53c.19-.11.17-.29 0-.4l-5.07-2.93a.79.79 0 00-.7 0l-4.41 2.49c-.19.1-.18.29 0 .4l5.07 2.93a.78.78 0 00.72.04zM287 289.31l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.76.76 0 00-.69-.01zm-14.17 8.18l-7.42 4.29c-.19.11-.18.29 0 .4l5.07 2.93a.75.75 0 00.7 0l7.42-4.28c.19-.11.18-.29 0-.4l-5.07-2.93a.79.79 0 00-.67-.01zm7.76-4.48l-4.39 2.54c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.03-2.95a.79.79 0 00-.7 0zM263.41 303l-4.41 2.48c-.19.11-.18.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.39-2.54c.18-.1.17-.29 0-.4l-5.05-2.87a.78.78 0 00-.7 0zm-6.41 3.7l-4.38 2.53c-.19.11-.18.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.41-2.56c.18-.1.17-.28 0-.4l-5.07-2.93a.78.78 0 00-.73-.02zm20 11.85l4.39-2.53c.18-.11.17-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.75.75 0 00.7 0zm-6.41 3.71l4.39-2.54c.18-.1.17-.29 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.65.01zm-6.41 3.7l4.39-2.54c.18-.1.17-.28 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.65.05zm-6.41 3.7l4.39-2.53c.18-.11.17-.29 0-.41l-5.16-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.74.01zm31.55-26.59l4.39-2.53c.19-.11.17-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .4l5.07 2.93a.75.75 0 00.64 0zm-5.91 11.78l9.78-5.65c.19-.1.18-.28 0-.4l-12.32-7.11a.75.75 0 00-.7 0L275 304.6c-.18.11-.17.29 0 .4l6.52 3.77c.2.11.21.29 0 .4l-4.05 2.34c-.18.1-.17.29 0 .4l5.07 2.93a.78.78 0 00.82.01zm-32.77-4.5l-4.38 2.53c-.19.11-.18.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.93a.78.78 0 00-.76.01zm-6.41 3.7l-4.38 2.53c-.19.11-.18.29 0 .41l5.07 2.92a.76.76 0 00.7 0l4.38-2.51c.18-.11.17-.29 0-.41l-5.07-2.93a.78.78 0 00-.75-.01zm-32 41.5a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.1-.17.29 0 .4zm-19.33-11.89l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.42-2.52c.18-.1.17-.29 0-.4l-5.07-2.93a.78.78 0 00-.73-.01zm10.32 8.68a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.41 2.52c-.19.11-.17.29 0 .4zm20.25 5.46l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.71.01zm-7.11 3.69a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.76.76 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41zM199.31 340l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.75.75 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.4L200 340a.75.75 0 00-.69 0zm32.05-18.51L227 324c-.19.11-.18.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.92a.75.75 0 00-.73-.03zm-25.64 14.81l-4.39 2.53c-.18.1-.17.29 0 .4l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.4l-5.07-2.93a.75.75 0 00-.7-.04zm32.05-18.51l-4.38 2.53c-.19.11-.18.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.93a.78.78 0 00-.71-.03zM225 325.15l-4.38 2.53c-.19.11-.18.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.92a.75.75 0 00-.71-.01zm-6.41 3.7l-4.38 2.54c-.19.1-.18.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.4l-5.07-2.93a.79.79 0 00-.76-.01zm-6.41 3.7l-4.38 2.54c-.19.1-.18.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.4l-5.07-2.93a.79.79 0 00-.76-.01zm32.77 4.51l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.02 2.87a.75.75 0 00.7.06zm56.3-55.93l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.76.76 0 00-.74 0zm-7.76 4.48l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.76.76 0 00-.74-.01zm14.17-8.18l-4.44 2.57c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.69-.03zm-33.18 34.21l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.75.75 0 00.66 0zm40.29-37.9a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41zm-63.41 59.62l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.47 2.51c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.73.03zM329.92 288l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.1-.17.29 0 .4l5.07 2.93a.78.78 0 00.71 0zm-13.66-.48l4.39-2.53c.19-.11.17-.29 0-.4l-5.07-2.93a.79.79 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .41l5.07 2.93a.78.78 0 00.69-.04zm5.7-9.56a.75.75 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.19-.11.17-.29 0-.41zm1.35-.78l12.32 7.11a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-12.32-7.11a.75.75 0 00-.7 0l-4.38 2.53c-.21.07-.2.22 0 .37zm-14.87 8.16l-4.38 2.54c-.19.1-.18.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.19-.11.17-.29 0-.4l-5.07-2.93a.79.79 0 00-.71-.04zm-48.24 27.83l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.38-2.54c.19-.1.18-.28 0-.4l-5.07-2.93a.78.78 0 00-.69.01zm62.61-21.51a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4zm-56.2 17.81l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.38-2.54c.19-.1.18-.29 0-.4l-5.07-2.93a.78.78 0 00-.69.01zm-48.05 42.38a.78.78 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.4l-5.1-2.92a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4zm-9.64-9.07l-4.39 2.53c-.19.1-.17.29 0 .4l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.69 0zm16.75 5.38l4.38-2.53c.19-.11.18-.29 0-.4L225 342.3a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.69-.01zm6.41-3.7l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.92a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7-.01zm6.41-3.7l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.92a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7-.01zm-23.16-1.69l-4.39 2.54c-.19.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.09-2.92a.79.79 0 00-.67-.02zm6.41-3.7l-4.39 2.54c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.79.79 0 00-.69-.01zm25.64-14.8L243 323.1c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.7.01zm-6.41 3.7l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.66.01zm12.82-7.4l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.38-2.54c.19-.1.18-.28 0-.4l-5.07-2.93a.78.78 0 00-.69.01zm-25.64 14.8l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.76.76 0 00-.69-.01zm6.41-3.7l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.06-2.9a.76.76 0 00-.7 0zm-4.68 26.13l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.71.01zm-37.62-3.8l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.39-2.54c.21-.09.2-.27 0-.39zm9.61 2.82l-5.07-2.93a.78.78 0 00-.7 0l-6.75 3.89c-.19.11-.18.29 0 .41l5.08 2.92a.75.75 0 00.7 0l6.74-3.9c.21-.09.2-.28 0-.39zm-17.39-8.48l-5.07-2.93a.78.78 0 00-.7 0l-5.4 3.11c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l5.4-3.12c.21-.1.2-.29 0-.4zm4.14-2.39a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4zm22.18 14.08l-5.07-2.93a.75.75 0 00-.7 0l-8.44 4.87c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l8.43-4.87c.21-.11.21-.29.01-.4zm4.21 5.93l-5.07-2.92a.76.76 0 00-.7 0l-5.4 3.12c-.19.11-.18.29 0 .4l5.08 2.93a.75.75 0 00.7 0l5.39-3.11c.21-.12.2-.3-.01-.42zm21.27-47.53a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41zm-12.82 7.41a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.76.76 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41zM195 338.55a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4L195 332.7a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.29 0 .4zm34.84-20.12a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41zm-12.82 7.41a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41L217 320a.76.76 0 00-.7 0l-4.3 2.5c-.18.11-.17.29 0 .41zm1.48 36.9a.75.75 0 00-.7 0l-6.75 3.89c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l6.75-3.9c.18-.11.17-.29 0-.4zm-10.68-31.59a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4zm-6.41 3.7a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.06-2.92a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4zm135.85-40.67a.76.76 0 00-.7 0l-10.8 6.23c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l10.79-6.23c.19-.11.18-.29 0-.41zm-14.17 8.18a.76.76 0 00-.7 0l-4.39 2.51c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41zm-86.83 48l4.38-2.54c.19-.1.18-.28 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.74.05zm74-40.64a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4zm33.4-19.28a.76.76 0 00-.7 0l-4.35 2.56c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41zm11.48-.78l-12.32-7.11a.78.78 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .4l12.32 7.11a.75.75 0 00.7 0l4.38-2.53c.25-.06.24-.24.04-.36zm-68.84 33.89a.79.79 0 00-.7 0l-5.39 3.12c-.19.11-.18.29 0 .4l5.07 2.93a.79.79 0 00.7 0l5.4-3.12c.19-.11.18-.29 0-.4zm16.2-9.35a.79.79 0 00-.7 0l-6.75 3.9c-.19.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.7 0l6.74-3.9c.19-.11.18-.29 0-.4zm-31 17.92a.79.79 0 00-.7 0L234.7 353c-.19.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l36.1-20.84c.18-.11.17-.29 0-.4zm-38.12 22a.79.79 0 00-.7 0l-5.4 3.12c-.19.11-.18.29 0 .41l5.07 2.92a.76.76 0 00.7 0l5.4-3.12c.19-.11.18-.29 0-.4zm45.54-26.3a.78.78 0 00-.7 0l-5.4 3.12c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l5.4-3.11c.18-.11.17-.29 0-.41zm-53 30.58a.78.78 0 00-.7 0l-5.39 3.12c-.19.1-.18.29 0 .4l5.07 2.93a.78.78 0 00.7 0l5.4-3.11c.19-.11.18-.29 0-.41zm67.82-39.15a.78.78 0 00-.7 0l-5.4 3.12c-.19.1-.18.28 0 .4l5.08 2.93a.76.76 0 00.69 0l5.4-3.11c.19-.11.18-.29 0-.41zm22.94-13.24a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4zm14-10.2l4.39-2.54c.19-.1.17-.28 0-.4L330 290a.78.78 0 00-.7 0l-4.3 2.51c-.19.11-.18.29 0 .4l5.07 2.93a.79.79 0 00.68.02zm-28.66-.19l4.39-2.53c.19-.11.17-.29 0-.4l-5.07-2.93a.79.79 0 00-.7 0l-4.38 2.54c-.19.1-.18.28 0 .4l5.07 2.93a.78.78 0 00.76.04zm-14.52 25.12l12.82-7.4c.19-.11.17-.29 0-.41l-5.07-2.92a.75.75 0 00-.7 0l-12.82 7.4c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.77.04zm36.07-21.25a.76.76 0 00.7 0l4.39-2.53c.19-.11.17-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .41zm-6.54-4.16l4.38-2.54c.19-.1.18-.29 0-.4l-5.07-2.93a.78.78 0 00-.7 0L311.3 292c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.73.05zm-35.94 29.11l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.78.04zm28.31-16.77a.76.76 0 00.7 0l4.39-2.53c.19-.11.17-.29 0-.41l-5.07-2.92a.75.75 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .41zM249.11 343l4.38-2.54c.19-.1.18-.29 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.71.01zm6.41-3.71l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.41 2.57c-.18.11-.17.29 0 .4l5.07 2.93a.75.75 0 00.73-.04zm-12.82 7.41l4.38-2.54c.19-.1.18-.28 0-.4l-5.08-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.72.01zm25.64-14.81l4.38-2.53c.19-.11.18-.29 0-.4l-5.09-2.96a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.73.02zm-6.41 3.7l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.29 0 .4l5.07 2.93a.78.78 0 00.71-.01zm12.82-7.4l4.38-2.53c.19-.11.18-.29 0-.4l-5.13-2.93a.76.76 0 00-.7 0l-4.3 2.52c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.68.01zm29.33-54.75a.76.76 0 00.69 0l4.39-2.54c.19-.1.18-.28 0-.4l-1.35-.78a.78.78 0 00-.7 0l-4.39 2.53c-.19.11-.17.29 0 .4zm-31.54 20.36a.75.75 0 00.7 0l4.38-2.53c.19-.1.18-.29 0-.4l-5.09-2.87a.75.75 0 00-.7 0l-4.39 2.53c-.19.11-.18.29 0 .4zm12.82-7.4a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.79.79 0 00-.7 0l-4.39 2.54c-.19.1-.18.29 0 .4zm-20.65 11.92a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.41 2.54c-.18.1-.17.29 0 .4zM279 290.1a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.08-2.93a.73.73 0 00-.69 0l-4.39 2.53c-.19.11-.18.29 0 .4zm-27.06 15.62a.79.79 0 00.7 0l4.36-2.52c.19-.1.18-.29 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4zm6.41-3.7a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4zm32.95-21.18a.76.76 0 00.7 0l4.39-2.53c.19-.11.18-.29 0-.41l-1.35-.78a.78.78 0 00-.7 0l-4.39 2.53c-.19.11-.18.29 0 .4zm19.24-11.1a.79.79 0 00.7 0l4.38-2.54c.19-.1.18-.28 0-.4l-1.42-.8a.75.75 0 00-.7 0l-4.39 2.53c-.19.11-.17.29 0 .4zm10.65 6.15a.79.79 0 00.7 0l4.38-2.54c.19-.1.18-.28 0-.4l-5.14-2.95a.78.78 0 00-.7 0l-4.43 2.56c-.18.11-.17.29 0 .4zm16 16.27l4.39-2.54c.19-.1.18-.28 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .4l5.07 2.93a.79.79 0 00.66.01zm-39.49-15a.74.74 0 00.69 0l4.39-2.53c.19-.11.18-.29 0-.41l-1.35-.78a.78.78 0 00-.7 0l-4.44 2.56c-.19.11-.18.29 0 .4zm-52.19 32.28a.79.79 0 00.7 0l4.38-2.54c.19-.1.18-.28 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4z" fill="currentColor"/><path d="M295.68 299.37l4.39-2.53c.19-.11.17-.29 0-.4l-5.07-2.93a.79.79 0 00-.7 0l-4.41 2.49c-.19.1-.18.29 0 .4l5.07 2.93a.78.78 0 00.72.04zM287 289.31l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.76.76 0 00-.69-.01zm-14.17 8.18l-7.42 4.29c-.19.11-.18.29 0 .4l5.07 2.93a.75.75 0 00.7 0l7.42-4.28c.19-.11.18-.29 0-.4l-5.07-2.93a.79.79 0 00-.67-.01zm7.76-4.48l-4.39 2.54c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.03-2.95a.79.79 0 00-.7 0zM263.41 303l-4.41 2.48c-.19.11-.18.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.39-2.54c.18-.1.17-.29 0-.4l-5.05-2.87a.78.78 0 00-.7 0zm-6.41 3.7l-4.38 2.53c-.19.11-.18.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.41-2.56c.18-.1.17-.28 0-.4l-5.07-2.93a.78.78 0 00-.73-.02zm20 11.85l4.39-2.53c.18-.11.17-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.75.75 0 00.7 0zm-6.41 3.71l4.39-2.54c.18-.1.17-.29 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.65.01zm-6.41 3.7l4.39-2.54c.18-.1.17-.28 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.65.05zm-6.41 3.7l4.39-2.53c.18-.11.17-.29 0-.41l-5.16-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.74.01zm31.55-26.59l4.39-2.53c.19-.11.17-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .4l5.07 2.93a.75.75 0 00.64 0zm-5.91 11.78l9.78-5.65c.19-.1.18-.28 0-.4l-12.32-7.11a.75.75 0 00-.7 0L275 304.6c-.18.11-.17.29 0 .4l6.52 3.77c.2.11.21.29 0 .4l-4.05 2.34c-.18.1-.17.29 0 .4l5.07 2.93a.78.78 0 00.82.01zm-32.77-4.5l-4.38 2.53c-.19.11-.18.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.93a.78.78 0 00-.76.01zm-6.41 3.7l-4.38 2.53c-.19.11-.18.29 0 .41l5.07 2.92a.76.76 0 00.7 0l4.38-2.51c.18-.11.17-.29 0-.41l-5.07-2.93a.78.78 0 00-.75-.01zm-32 41.5a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.1-.17.29 0 .4zm-19.33-11.89l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.42-2.52c.18-.1.17-.29 0-.4l-5.07-2.93a.78.78 0 00-.73-.01zm10.32 8.68a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.41 2.52c-.19.11-.17.29 0 .4zm20.25 5.46l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.71.01zm-7.11 3.69a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.76.76 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41zM199.31 340l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.75.75 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.4L200 340a.75.75 0 00-.69 0zm32.05-18.51L227 324c-.19.11-.18.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.92a.75.75 0 00-.73-.03zm-25.64 14.81l-4.39 2.53c-.18.1-.17.29 0 .4l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.4l-5.07-2.93a.75.75 0 00-.7-.04zm32.05-18.51l-4.38 2.53c-.19.11-.18.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.93a.78.78 0 00-.71-.03zM225 325.15l-4.38 2.53c-.19.11-.18.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.92a.75.75 0 00-.71-.01zm-6.41 3.7l-4.38 2.54c-.19.1-.18.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.4l-5.07-2.93a.79.79 0 00-.76-.01zm-6.41 3.7l-4.38 2.54c-.19.1-.18.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.4l-5.07-2.93a.79.79 0 00-.76-.01zm32.77 4.51l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.02 2.87a.75.75 0 00.7.06zm56.3-55.93l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.76.76 0 00-.74 0zm-7.76 4.48l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.76.76 0 00-.74-.01zm14.17-8.18l-4.44 2.57c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.69-.03zm-33.18 34.21l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.75.75 0 00.66 0zm40.29-37.9a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41zm-63.41 59.62l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.47 2.51c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.73.03zM329.92 288l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.1-.17.29 0 .4l5.07 2.93a.78.78 0 00.71 0zm-13.66-.48l4.39-2.53c.19-.11.17-.29 0-.4l-5.07-2.93a.79.79 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .41l5.07 2.93a.78.78 0 00.69-.04zm5.7-9.56a.75.75 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.19-.11.17-.29 0-.41zm1.35-.78l12.32 7.11a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-12.32-7.11a.75.75 0 00-.7 0l-4.38 2.53c-.21.07-.2.22 0 .37zm-14.87 8.16l-4.38 2.54c-.19.1-.18.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.39-2.53c.19-.11.17-.29 0-.4l-5.07-2.93a.79.79 0 00-.71-.04zm-48.24 27.83l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.38-2.54c.19-.1.18-.28 0-.4l-5.07-2.93a.78.78 0 00-.69.01zm62.61-21.51a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4zm-56.2 17.81l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.38-2.54c.19-.1.18-.29 0-.4l-5.07-2.93a.78.78 0 00-.69.01zm-48.05 42.38a.78.78 0 00.7 0l4.39-2.53c.18-.11.17-.29 0-.4l-5.1-2.92a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4zm-9.64-9.07l-4.39 2.53c-.19.1-.17.29 0 .4l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.69 0zm16.75 5.38l4.38-2.53c.19-.11.18-.29 0-.4L225 342.3a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.69-.01zm6.41-3.7l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.92a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7-.01zm6.41-3.7l4.39-2.53c.18-.11.17-.29 0-.41l-5.07-2.92a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7-.01zm-23.16-1.69l-4.39 2.54c-.19.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.09-2.92a.79.79 0 00-.67-.02zm6.41-3.7l-4.39 2.54c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.79.79 0 00-.69-.01zm25.64-14.8L243 323.1c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.7.01zm-6.41 3.7l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.66.01zm12.82-7.4l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.38-2.54c.19-.1.18-.28 0-.4l-5.07-2.93a.78.78 0 00-.69.01zm-25.64 14.8l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.76.76 0 00-.69-.01zm6.41-3.7l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.06-2.9a.76.76 0 00-.7 0zm-4.68 26.13l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.71.01zm-37.62-3.8l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.7 0l4.39-2.54c.21-.09.2-.27 0-.39zm9.61 2.82l-5.07-2.93a.78.78 0 00-.7 0l-6.75 3.89c-.19.11-.18.29 0 .41l5.08 2.92a.75.75 0 00.7 0l6.74-3.9c.21-.09.2-.28 0-.39zm-17.39-8.48l-5.07-2.93a.78.78 0 00-.7 0l-5.4 3.11c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l5.4-3.12c.21-.1.2-.29 0-.4zm4.14-2.39a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4zm22.18 14.08l-5.07-2.93a.75.75 0 00-.7 0l-8.44 4.87c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l8.43-4.87c.21-.11.21-.29.01-.4zm4.21 5.93l-5.07-2.92a.76.76 0 00-.7 0l-5.4 3.12c-.19.11-.18.29 0 .4l5.08 2.93a.75.75 0 00.7 0l5.39-3.11c.21-.12.2-.3-.01-.42zm21.27-47.53a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41zm-12.82 7.41a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.76.76 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41zM195 338.55a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4L195 332.7a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.29 0 .4zm34.84-20.12a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41zm-12.82 7.41a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41L217 320a.76.76 0 00-.7 0l-4.3 2.5c-.18.11-.17.29 0 .41zm1.48 36.9a.75.75 0 00-.7 0l-6.75 3.89c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l6.75-3.9c.18-.11.17-.29 0-.4zm-10.68-31.59a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4zm-6.41 3.7a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.06-2.92a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4zm135.85-40.67a.76.76 0 00-.7 0l-10.8 6.23c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l10.79-6.23c.19-.11.18-.29 0-.41zm-14.17 8.18a.76.76 0 00-.7 0l-4.39 2.51c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41zm-86.83 48l4.38-2.54c.19-.1.18-.28 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.74.05zm74-40.64a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4zm33.4-19.28a.76.76 0 00-.7 0l-4.35 2.56c-.18.11-.17.29 0 .41l5.07 2.92a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.41zm11.48-.78l-12.32-7.11a.78.78 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .4l12.32 7.11a.75.75 0 00.7 0l4.38-2.53c.25-.06.24-.24.04-.36zm-68.84 33.89a.79.79 0 00-.7 0l-5.39 3.12c-.19.11-.18.29 0 .4l5.07 2.93a.79.79 0 00.7 0l5.4-3.12c.19-.11.18-.29 0-.4zm16.2-9.35a.79.79 0 00-.7 0l-6.75 3.9c-.19.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.7 0l6.74-3.9c.19-.11.18-.29 0-.4zm-31 17.92a.79.79 0 00-.7 0L234.7 353c-.19.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l36.1-20.84c.18-.11.17-.29 0-.4zm-38.12 22a.79.79 0 00-.7 0l-5.4 3.12c-.19.11-.18.29 0 .41l5.07 2.92a.76.76 0 00.7 0l5.4-3.12c.19-.11.18-.29 0-.4zm45.54-26.3a.78.78 0 00-.7 0l-5.4 3.12c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l5.4-3.11c.18-.11.17-.29 0-.41zm-53 30.58a.78.78 0 00-.7 0l-5.39 3.12c-.19.1-.18.29 0 .4l5.07 2.93a.78.78 0 00.7 0l5.4-3.11c.19-.11.18-.29 0-.41zm67.82-39.15a.78.78 0 00-.7 0l-5.4 3.12c-.19.1-.18.28 0 .4l5.08 2.93a.76.76 0 00.69 0l5.4-3.11c.19-.11.18-.29 0-.41zm22.94-13.24a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4zm14-10.2l4.39-2.54c.19-.1.17-.28 0-.4L330 290a.78.78 0 00-.7 0l-4.3 2.51c-.19.11-.18.29 0 .4l5.07 2.93a.79.79 0 00.68.02zm-28.66-.19l4.39-2.53c.19-.11.17-.29 0-.4l-5.07-2.93a.79.79 0 00-.7 0l-4.38 2.54c-.19.1-.18.28 0 .4l5.07 2.93a.78.78 0 00.76.04zm-14.52 25.12l12.82-7.4c.19-.11.17-.29 0-.41l-5.07-2.92a.75.75 0 00-.7 0l-12.82 7.4c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.77.04zm36.07-21.25a.76.76 0 00.7 0l4.39-2.53c.19-.11.17-.29 0-.41l-5.07-2.93a.78.78 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .41zm-6.54-4.16l4.38-2.54c.19-.1.18-.29 0-.4l-5.07-2.93a.78.78 0 00-.7 0L311.3 292c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.73.05zm-35.94 29.11l4.38-2.53c.19-.11.18-.29 0-.41l-5.07-2.92a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .41l5.07 2.93a.78.78 0 00.78.04zm28.31-16.77a.76.76 0 00.7 0l4.39-2.53c.19-.11.17-.29 0-.41l-5.07-2.92a.75.75 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .41zM249.11 343l4.38-2.54c.19-.1.18-.29 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.71.01zm6.41-3.71l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.41 2.57c-.18.11-.17.29 0 .4l5.07 2.93a.75.75 0 00.73-.04zm-12.82 7.41l4.38-2.54c.19-.1.18-.28 0-.4l-5.08-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4l5.07 2.93a.79.79 0 00.72.01zm25.64-14.81l4.38-2.53c.19-.11.18-.29 0-.4l-5.09-2.96a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.73.02zm-6.41 3.7l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.79.79 0 00-.7 0l-4.39 2.54c-.18.1-.17.29 0 .4l5.07 2.93a.78.78 0 00.71-.01zm12.82-7.4l4.38-2.53c.19-.11.18-.29 0-.4l-5.13-2.93a.76.76 0 00-.7 0l-4.3 2.52c-.18.1-.17.28 0 .4l5.07 2.93a.78.78 0 00.68.01zm29.33-54.75a.76.76 0 00.69 0l4.39-2.54c.19-.1.18-.28 0-.4l-1.35-.78a.78.78 0 00-.7 0l-4.39 2.53c-.19.11-.17.29 0 .4zm-31.54 20.36a.75.75 0 00.7 0l4.38-2.53c.19-.1.18-.29 0-.4l-5.09-2.87a.75.75 0 00-.7 0l-4.39 2.53c-.19.11-.18.29 0 .4zm12.82-7.4a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.79.79 0 00-.7 0l-4.39 2.54c-.19.1-.18.29 0 .4zm-20.65 11.92a.78.78 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.41 2.54c-.18.1-.17.29 0 .4zM279 290.1a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.08-2.93a.73.73 0 00-.69 0l-4.39 2.53c-.19.11-.18.29 0 .4zm-27.06 15.62a.79.79 0 00.7 0l4.36-2.52c.19-.1.18-.29 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4zm6.41-3.7a.75.75 0 00.7 0l4.38-2.53c.19-.11.18-.29 0-.4l-5.07-2.93a.75.75 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4zm32.95-21.18a.76.76 0 00.7 0l4.39-2.53c.19-.11.18-.29 0-.41l-1.35-.78a.78.78 0 00-.7 0l-4.39 2.53c-.19.11-.18.29 0 .4zm19.24-11.1a.79.79 0 00.7 0l4.38-2.54c.19-.1.18-.28 0-.4l-1.42-.8a.75.75 0 00-.7 0l-4.39 2.53c-.19.11-.17.29 0 .4zm10.65 6.15a.79.79 0 00.7 0l4.38-2.54c.19-.1.18-.28 0-.4l-5.14-2.95a.78.78 0 00-.7 0l-4.43 2.56c-.18.11-.17.29 0 .4zm16 16.27l4.39-2.54c.19-.1.18-.28 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.38 2.53c-.19.11-.18.29 0 .4l5.07 2.93a.79.79 0 00.66.01zm-39.49-15a.74.74 0 00.69 0l4.39-2.53c.19-.11.18-.29 0-.41l-1.35-.78a.78.78 0 00-.7 0l-4.44 2.56c-.19.11-.18.29 0 .4zm-52.19 32.28a.79.79 0 00.7 0l4.38-2.54c.19-.1.18-.28 0-.4l-5.07-2.93a.78.78 0 00-.7 0l-4.39 2.53c-.18.11-.17.29 0 .4z" fill="#fff" opacity=".8"/><path d="M273.9 347.9l43.29-25a5.23 5.23 0 014.72 0l24.82 14.33c1.3.75 1.3 2 0 2.73l-43.28 25a5.19 5.19 0 01-4.73 0l-24.82-14.33c-1.3-.76-1.3-1.98 0-2.73z" fill="currentColor"/><path d="M273.9 347.9l43.29-25a5.23 5.23 0 014.72 0l24.82 14.33c1.3.75 1.3 2 0 2.73l-43.28 25a5.19 5.19 0 01-4.73 0l-24.82-14.33c-1.3-.76-1.3-1.98 0-2.73z" fill="#fff" opacity=".4"/><path d="M131.22 332.2a6 6 0 001.21.52 5.5 5.5 0 01-1.21-.51 2.4 2.4 0 01-.37-.25 3.37 3.37 0 00.37.24z" fill="currentColor"/><path d="M293.66 105.86c-.28-2-1.92-2.78-3.9-1.64l-150.91 87.14a7.93 7.93 0 00-2.79 2.92l-7.38-4.28a8 8 0 012.78-2.94L282.38 100a7.36 7.36 0 0111.28 5.9z" fill="#fff" opacity=".6"/><path d="M138.11 332.57l.06-.05z" fill="currentColor"/><path d="M411.87 315.15a8.45 8.45 0 01-3.94 6.54L257 408.82a8.13 8.13 0 01-3.94.92v-8.51a8 8 0 003.94-.94l150.91-87.13c2-1.18 2.16-3 .4-4.29a8.52 8.52 0 013.56 6.28z" fill="#fff" opacity=".8"/><g><path d="M144.37 303.34c0 1.86 1.31 2.61 2.92 1.68L280.78 228a6.45 6.45 0 002.92-5v-90l-139.33 80.47z" fill="#37474f"/><path d="M195.13 194.51v4.32c0 .88.63 1.24 1.39.8l27.72-16.09V176l-27.72 16.1a3.1 3.1 0 00-1.39 2.41z" fill="#fafafa"/><path d="M231.55 171.79c.77-.44 1.39-.08 1.39.8v4.32a3.06 3.06 0 01-1.39 2.41l-7.31 4.22V176z" fill="currentColor"/><path d="M231 177.72l-.82-.26a4.14 4.14 0 00.13-1.05 1.92 1.92 0 00-.74-1.74 1.16 1.16 0 00-1.19.06 4.17 4.17 0 00-1.63 3.39 1.92 1.92 0 00.75 1.74 1 1 0 00.51.14 1.33 1.33 0 00.67-.2A3.41 3.41 0 00230 178l.82.25h.09a.32.32 0 00.29-.22.29.29 0 00-.2-.31zm-2.62 1.56a.54.54 0 01-.58.05 1.4 1.4 0 01-.44-1.21 3.67 3.67 0 011.32-2.87.85.85 0 01.37-.11.54.54 0 01.22.05 1.44 1.44 0 01.44 1.22 3.65 3.65 0 01-1.32 2.87z" fill="#fafafa"/><path d="M144.37 207.37a6.45 6.45 0 012.92-5l133.49-77.08c1.61-.93 2.92-.17 2.92 1.68V133l-139.33 80.47z" fill="#263238"/><path d="M267.31 136.18a3 3 0 00-1.37 2.37c0 .87.61 1.22 1.37.79a3 3 0 001.36-2.37c0-.87-.61-1.22-1.36-.79zM272.09 133.43a3 3 0 00-1.37 2.36c0 .88.61 1.23 1.37.79a3 3 0 001.36-2.36c0-.87-.61-1.22-1.36-.79zM276.87 130.68a3 3 0 00-1.37 2.32c0 .87.61 1.23 1.37.79a3 3 0 001.36-2.37c0-.83-.61-1.18-1.36-.74z" fill="currentColor"/><path d="M147.29 305l133.49-77a6.45 6.45 0 002.92-5v-65.15l-139.33 80.43v65.06c0 1.86 1.31 2.66 2.92 1.66z" fill="#fafafa"/><path d="M198.83 193l20.36-11.75c.78-.45 1.41-.14 1.41.71a3 3 0 01-1.41 2.35l-20.33 11.67c-.79.45-1.43.15-1.43-.67a3 3 0 011.4-2.31z" fill="#455a64" opacity=".4"/><path d="M195.26 244.81c-.13.07-.23.08-.3 0l-.16-.17-3.3-5.64v7.49a.85.85 0 01-.1.4.56.56 0 01-.22.27l-1 .56a.17.17 0 01-.22 0 .4.4 0 01-.09-.29v-11.76a.93.93 0 01.09-.4.6.6 0 01.22-.28l.83-.47c.13-.08.24-.09.31 0l.15.17 3.31 5.65v-7.49a1 1 0 01.09-.4.62.62 0 01.22-.27l1-.55a.17.17 0 01.22 0 .37.37 0 01.1.29v11.74a.82.82 0 01-.1.4.54.54 0 01-.22.28zM198 235.26a12 12 0 01.25-2.06 8.77 8.77 0 01.65-1.93 7.27 7.27 0 011.06-1.67 5.4 5.4 0 011.51-1.26 2.89 2.89 0 011.5-.49 1.51 1.51 0 011.06.44 2.82 2.82 0 01.65 1.19 7 7 0 01.26 1.78v3.25a12 12 0 01-.25 2.06 8.85 8.85 0 01-.65 1.94 7 7 0 01-1.07 1.66 5.81 5.81 0 01-1.5 1.25 3 3 0 01-1.51.49 1.48 1.48 0 01-1.06-.43 2.6 2.6 0 01-.65-1.19 7.7 7.7 0 01-.25-1.77v-.75-.86-.87-.78zm5.35-3a2.39 2.39 0 00-.55-1.71c-.34-.29-.78-.27-1.33.05a3.45 3.45 0 00-1.33 1.48 5.73 5.73 0 00-.55 2.35V237.5a2.32 2.32 0 00.55 1.7c.34.28.78.26 1.33-.06a3.45 3.45 0 001.33-1.48 5.75 5.75 0 00.55-2.33v-1.55c0-.55-.02-1.06-.03-1.51zM212.39 222.19a3.43 3.43 0 011.52-.52 1.47 1.47 0 011.08.39 2.73 2.73 0 01.66 1.2 8.35 8.35 0 01.27 1.91v2.69a13.08 13.08 0 01-.27 2.23 9.6 9.6 0 01-.66 2 6.57 6.57 0 01-1.06 1.62 5.81 5.81 0 01-1.48 1.21l-3 1.72a.18.18 0 01-.23 0 .39.39 0 01-.09-.29v-11.8a1 1 0 01.09-.4.63.63 0 01.23-.27zm1.94 3.9a4.13 4.13 0 00-.15-1 1.49 1.49 0 00-.36-.66.87.87 0 00-.61-.23 1.88 1.88 0 00-.87.3l-1.59.91v8.18l1.64-1a2.82 2.82 0 00.84-.69 3.84 3.84 0 00.59-.91 4.9 4.9 0 00.36-1.08 6.68 6.68 0 00.15-1.18c.03-.86.03-1.73 0-2.64zM221.18 217.12c.12-.07.22-.07.28 0a1 1 0 01.17.37l2.89 9.82a.41.41 0 010 .12 1 1 0 01-.09.4.63.63 0 01-.23.27l-.85.49c-.16.09-.27.11-.33 0a.56.56 0 01-.13-.22l-.51-1.73-3.73 2.15-.5 2.32a1.94 1.94 0 01-.14.37.79.79 0 01-.33.35l-.85.49a.17.17 0 01-.22 0 .4.4 0 01-.09-.29.53.53 0 010-.13l2.89-13.16a3.26 3.26 0 01.16-.56.7.7 0 01.29-.32zm.65 7.61l-1.3-4.43-1.3 5.93zM230.27 211.87a.17.17 0 01.22 0 .4.4 0 01.1.29v1.29a1 1 0 01-.1.4.65.65 0 01-.22.28l-2.13 1.22v10a.93.93 0 01-.09.4.61.61 0 01-.23.28l-.95.55a.18.18 0 01-.23 0 .39.39 0 01-.09-.29v-10l-2.13 1.23a.17.17 0 01-.22 0 .39.39 0 01-.1-.29v-1.29a1 1 0 01.1-.4.62.62 0 01.22-.27zM234.81 209.25c.13-.07.22-.08.28 0a.85.85 0 01.17.37l2.89 9.82a.37.37 0 010 .12 1 1 0 01-.1.4.62.62 0 01-.22.27l-.85.49c-.16.09-.27.1-.33 0a.45.45 0 01-.13-.23l-.52-1.69-3.7 2.2-.51 2.31a1.9 1.9 0 01-.13.38.72.72 0 01-.33.34l-.85.49a.17.17 0 01-.22 0 .37.37 0 01-.1-.29.61.61 0 010-.14l2.89-13.15a2.92 2.92 0 01.17-.56.68.68 0 01.28-.32zm.65 7.6l-1.3-4.43-1.3 5.93z" fill="currentColor"/></g></g><g><path d="M458.41 276.92c1.42-28.8-.73-35.76-8.29-41.21l-3.34 16.63s3.05 19.75 2.52 26.3c-.45 5.75-1.18 12.94-2.64 17.56a3.55 3.55 0 01-1.57 1.94c-2 1.18-2.8 2.41-4.31 3.92-1.18 1.18-2.16 1.46-2 1.85a1.9 1.9 0 002.41.91 13.08 13.08 0 002.8-1.44 17.63 17.63 0 01-2.94 4.81c-2.82 3.3-3 5.36 2.54 5.53 3.47.11 6.28-2 9.06-9.54a61.67 61.67 0 002.63-8.52c1.47-7.53 2.9-14.11 3.13-18.74z" fill="#a35b5a"/><path d="M443.72 233.38c3.67-.45 8.63.43 11.72 6.25s3.65 15.73 3.79 25.58c0 0-6.5 5.19-14.11 2.39z" fill="currentColor"/><path d="M443.72 233.38c3.67-.45 8.63.43 11.72 6.25s3.65 15.73 3.79 25.58c0 0-6.5 5.19-14.11 2.39z" opacity=".5"/><path d="M450.79 384.23c.54.22.66 1.28.7 3.37 0 1.59.3 4.36-1.57 4.87s-4.58.17-5.73-1.35a73.53 73.53 0 00-4.48-5.88c-1.79-2-3.74-3.9-4.42-6.69-.61-2.51 0-3.58 1.69-4.27 2.37-.95 6 2.4 7.81 3 1.57.43 5.45 6.72 6 6.95z" fill="#263238"/><path d="M450.83 382.29l-.06 2.57a6.31 6.31 0 01-6.5 0c-.1-.25-.59-3.69-.59-3.69z" fill="#a35b5a"/><path d="M429.64 398.57s.68 2.25 1 4.22c.28 1.56.67 4.17.1 5.32s-2.71 1.87-6 1.77c-2.13-.07-5.48 1.31-7.79 2.52s-7.27 1.67-9.75 1.33c-2.92-.41-5.92-2.18-6.36-3.4s.21-2.47 7.18-5.26c.06 0 9.66-2.91 13.08-6.52z" fill="#263238"/><path d="M429.7 395.79s-.05 1.91-.07 3-1.34 2.69-4.67 2.49c-2.34-.13-4-.8-3.82-2.73l.06-2.77z" fill="#a35b5a"/><path d="M453.27 348.22a70.32 70.32 0 00-2.73-13.46s1.17-11.53 1.86-22.47c.74-11.82.58-26.7.87-29.64 0 0-38-8.58-39 5.07s.41 48.52.73 54.54 1.23 11.35 2.25 21.11 3.93 32.41 3.93 32.41a9 9 0 008.5 0s3.3-27.28 3.18-34.21a59.54 59.54 0 00-2.29-15.19s1-11.26 1.83-21.05 1.65-14.49 1.89-18.75l.83-.39s.46 16.94.77 22.95 1.15 11.8 2.63 23c1.29 9.71 5.32 30.29 5.32 30.29 4.31 2.46 7-.15 7-.15s3.42-25.64 2.43-34.06z" fill="currentColor"/><path d="M453.27 348.22a70.32 70.32 0 00-2.73-13.46s1.17-11.53 1.86-22.47c.74-11.82.58-26.7.87-29.64 0 0-38-8.58-39 5.07s.41 48.52.73 54.54 1.23 11.35 2.25 21.11 3.93 32.41 3.93 32.41a9 9 0 008.5 0s3.3-27.28 3.18-34.21a59.54 59.54 0 00-2.29-15.19s1-11.26 1.83-21.05 1.65-14.49 1.89-18.75l.83-.39s.46 16.94.77 22.95 1.15 11.8 2.63 23c1.29 9.71 5.32 30.29 5.32 30.29 4.31 2.46 7-.15 7-.15s3.42-25.64 2.43-34.06z" opacity=".7"/><path d="M434.31 306.59s9.36-4.36 14.09-8.37c0 0-4.44 6-10.15 8.53 0 0-2.48 21-2.46 20.56l-.69-20.74z" opacity=".4"/><path fill="#a35b5a" d="M427.82 228.64l1.98 12.48 11.25-.57-1.31-14.6-11.92 2.69z"/><path d="M452.93 239.91c-1-3.4-5.24-7.18-9.55-6.48-1 .16-2 .33-2.93.5-.51 2.1-5.89 3.72-11.4 2.43-1.66.43-3.19.88-4.57 1.34-4 1.31-7.61 4.63-8.23 17.54 0 0-2 30.1-2.15 35.92 9.78 4.13 20 4.24 25.74 3.13 11.86-2.31 13.4-7.18 13.4-7.18s.64-21.66.49-31c-.12-7.98-.09-13.86-.8-16.2z" fill="#263238"/><path d="M419.5 213.32s-2.95 4.48-2.61 4.92 2.5.83 2.5.83z" fill="#8c3f3e"/><path d="M420.88 216.2a3.52 3.52 0 113.51-3.52 3.52 3.52 0 01-3.51 3.52zm0-6.25a2.74 2.74 0 102.73 2.73 2.73 2.73 0 00-2.73-2.68z" fill="#263238"/><path d="M427.69 200.94c-6 1.26-7.69 3.88-8.25 13.37-.59 9.9.42 13 1.72 14.35.88.9 5.76 1.06 8.23.39 3.08-.85 10.05-3.32 13.31-8.31 3.84-5.86 4.85-13.81.74-17.07-5.79-4.58-13.32-3.24-15.75-2.73z" fill="#a35b5a"/><path d="M422.27 229.15a17.81 17.81 0 005.71.16 4.74 4.74 0 001.92-2.19s-1.13 1.58-7.63 2.03z" fill="#8c3f3e"/><path fill="#263238" d="M417.69 211.95l10.62 1.37-.43.75-10.19-1.32v-.8z"/><path d="M416.64 205.61a9 9 0 003.39 3.25c1.49.78 3 .82 4.64 2.42a7.42 7.42 0 011.46 2.09c.07.17 1.33 1.87 1.4 1.61 0 0 .94-3.66 4-3.2s3.53 4.42 1.57 6.83-3.79 1.48-3.79 1.48.1 5.24 2.42 8.2c0 0 2.64.86 7.49-.18 3-.64 7.21-4.37 9-8.31 3.75-8.36 4-15.88-1.32-18.13-.58-3.85-3.61-5.58-7.06-6.37a17.06 17.06 0 00-5.57-.07 15.48 15.48 0 01-6.2-.58 8.83 8.83 0 00-3.32-.7c-1.13.11-2.29 1.06-2.13 2.18a5.32 5.32 0 00-6 .12c-2.12 1.58-2.16 3.95-1.38 6.31a14.3 14.3 0 001.4 3.05z" fill="#263238"/><path d="M447 203l3.58-2.11A2.08 2.08 0 00447 203zM427.47 217.07l.11-4.15a2.08 2.08 0 10-.11 4.15z" fill="#263238"/><path d="M423.88 240.14c-7.53-5.08-22.38-16-24.18-17.14-2.5-1.53-6.33-1.88-8.68-.63-2.18 1.17-.95 4.09 3.27 9.07 4.09 4.82 21.86 23 21.86 23z" fill="#a35b5a"/><path d="M424.29 238.36c-5.24-3.32-16-10-16-10s-7.42 5.33-5.7 14.28c0 0 10.78 10.33 14 13 2.18 1.76 8.47-1 9.36-7.32 1.17-7.81-1.66-9.96-1.66-9.96z" fill="currentColor"/><path d="M424.29 238.36c-5.24-3.32-16-10-16-10s-7.42 5.33-5.7 14.28c0 0 10.78 10.33 14 13 2.18 1.76 8.47-1 9.36-7.32 1.17-7.81-1.66-9.96-1.66-9.96z" opacity=".5"/><path d="M390.12 224.05c.57-4.13 13.26-12.87 17.6-15.56a32.39 32.39 0 005.45-4.17c.66-.64 1.24-1.35 1.89-2s1.21-1.54 2-2.22a13.68 13.68 0 013.16-2.3 10.76 10.76 0 017-.5c.54.12 1.08.27 1.64.34 1.17.15 2.5 0 3.41.72a.82.82 0 01.36.6c0 .38-.37.62-.71.79-2.67 1.3-5.82 1.74-8.05 3.71-.17.15-1.21 1.52-.64 1.67s2.51-.65 4.61-.59c2.53.07.86 1.53 0 2a15.3 15.3 0 01-4.38 1.69c-2.36.57-4.42.83-6 1.88s-15.64 15.27-15.64 15.27z" fill="#a35b5a"/></g></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/expectation.svg b/ruoyi-plus-soybean/src/assets/svg-icon/expectation.svg
new file mode 100755
index 0000000..1d87d5e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/expectation.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><g id="freepik--background-complete--inject-2"><path d="M71,378.37a21.42,21.42,0,0,1,13.27-1.61c2.41.4,4.92,1.12,6.56,2.93s2,5,.06,6.54a6.23,6.23,0,0,1-4.13,1c-3.51,0-7.4-.66-10.2,1.45-1.6,1.21-2.67,3.21-4.58,3.8a4.51,4.51,0,0,1-5-2.21,7.7,7.7,0,0,1-.56-5.7A8.69,8.69,0,0,1,71,378.37Z" style="fill:#ebebeb"></path><path d="M61.72,360.52c2.47,3.53,7.27,2.36,9.49.21s3.17-5.27,4-8.25c2-6.79,3.93-13.92,2.47-20.84A11.51,11.51,0,0,0,75,326a6.06,6.06,0,0,0-5.73-1.91c-2.42.64-3.92,3-5,5.26a49.7,49.7,0,0,0-4.91,21.33c0,3.43.39,7,2.33,9.87" style="fill:#ebebeb"></path><path d="M54.94,365.58c2.68-3.18,3.19-7.82,2-11.81a22.88,22.88,0,0,0-7-10.14,37.77,37.77,0,0,0-12.52-7.39,13.53,13.53,0,0,0-5.64-.95,5.89,5.89,0,0,0-4.72,2.86c-1.32,2.51-.06,5.56,1.31,8.05a96.09,96.09,0,0,0,8.22,12.52c2.47,3.18,5.29,6.31,9,7.75s7.66,1.26,9.65-1.34" style="fill:#ebebeb"></path><path d="M61.55,413.7a7.74,7.74,0,0,0,.23-1.6c.11-1.17.25-2.63.42-4.37a62.38,62.38,0,0,1,1-6.41,47.67,47.67,0,0,1,2.33-7.57,27.92,27.92,0,0,1,3.86-6.85,14.38,14.38,0,0,1,4.89-4.06,13.4,13.4,0,0,1,4.11-1.28,7.4,7.4,0,0,0,1.6-.24,8.22,8.22,0,0,0-1.63,0,12.39,12.39,0,0,0-4.29,1.12,14.32,14.32,0,0,0-5.15,4.09,27.15,27.15,0,0,0-4,7,46.69,46.69,0,0,0-2.31,7.69,52.24,52.24,0,0,0-.85,6.5c-.14,1.85-.2,3.36-.22,4.39A8.51,8.51,0,0,0,61.55,413.7Z" style="fill:#e0e0e0"></path><path d="M61.85,410.34a2.75,2.75,0,0,0,0-.75c0-.55,0-1.26,0-2.12,0-1.85,0-4.52.1-7.81.16-6.59.73-13.69,1.82-23.69,1-9.41,2.44-20.36,3.91-27.48.31-1.61.63-3.07.93-4.35s.53-2.38.76-3.27.37-1.52.5-2.06a3.93,3.93,0,0,0,.13-.74,3.75,3.75,0,0,0-.26.7l-.62,2c-.27.88-.55,2-.87,3.25s-.69,2.72-1,4.34a238.19,238.19,0,0,0-4.1,25.49c-1.1,10-1.6,19.15-1.64,25.76,0,3.3,0,6,.11,7.83.06.86.1,1.56.13,2.12A3.58,3.58,0,0,0,61.85,410.34Z" style="fill:#e0e0e0"></path><path d="M61.9,392.73a14.48,14.48,0,0,0,0-2.22c-.08-1.42-.28-3.49-.68-6a79,79,0,0,0-2-8.77,72.55,72.55,0,0,0-3.67-10.37A55.3,55.3,0,0,0,44.82,348.6a26.25,26.25,0,0,0-2.54-2.35c-.38-.33-.73-.66-1.08-.92l-1-.68a13.82,13.82,0,0,0-1.87-1.19,63.91,63.91,0,0,1,6.13,5.46A58.31,58.31,0,0,1,55,365.62a80.41,80.41,0,0,1,3.69,10.28,88.46,88.46,0,0,1,2.08,8.68c.46,2.5.73,4.54.89,5.95A13.27,13.27,0,0,0,61.9,392.73Z" style="fill:#e0e0e0"></path><polygon points="43.7 421.11 45.47 445.28 76.52 445.28 78.28 421.11 43.7 421.11" style="fill:#ebebeb"></polygon><rect x="40.71" y="411.59" width="41.28" height="9.53" style="fill:#ebebeb"></rect><polygon points="40.71 444.36 40.71 447 42.65 447 81.3 447 81.3 444.36 40.71 444.36" style="fill:#e0e0e0"></polygon><polygon points="44.55 451.35 42.65 446.7 79.82 446.7 77.61 451.35 44.55 451.35" style="fill:#e0e0e0"></polygon><path d="M77.7,429s0,.08-.14.22l-.47.62-1.8,2.31-.08.11-.08-.11L70.68,426h.26c-1.32,1.88-2.79,4-4.38,6.2l-.13.19-.16-.18-5.18-6.17h.3l-.29.36-4.64,5.82-.15.19-.15-.2L51.79,426H52l-5.39,6.14-.1.11-.07-.12-1.69-2.84c-.17-.31-.32-.56-.43-.75a1.84,1.84,0,0,1-.13-.27s.07.07.18.24.28.43.48.72l1.77,2.79-.17,0c1.38-1.62,3.18-3.73,5.31-6.21l.13-.16.12.17L56.47,432h-.3l4.63-5.83.28-.36.15-.18.15.18L66.56,432l-.29,0,4.41-6.17.13-.18.13.18L75.29,432h-.16L77,429.78l.51-.58A1.83,1.83,0,0,1,77.7,429Z" style="fill:#f5f5f5"></path><path d="M78.71,421c0,.11-7.78.2-17.37.2S44,421.06,44,421s7.78-.19,17.38-.19S78.71,420.85,78.71,421Z" style="fill:#f5f5f5"></path><rect x="171.63" y="369.18" width="149.88" height="25.26" style="fill:#e0e0e0"></rect><path d="M286.39,341a8.38,8.38,0,0,1,10.27-4.26A7.88,7.88,0,0,1,311,335.64a5.47,5.47,0,0,1,5.51,8.29" style="fill:#e0e0e0"></path><path d="M302.81,364.93h0a19.45,19.45,0,0,1-19.45-19.45v-4.55h38.9v4.55A19.45,19.45,0,0,1,302.81,364.93Z" style="fill:#f5f5f5"></path><polygon points="268.78 364.93 257.84 364.93 253.8 344.18 273 344.18 268.78 364.93" style="fill:#e0e0e0"></polygon><rect x="159.76" y="364.93" width="172.22" height="8.51" style="fill:#e0e0e0"></rect><polygon points="189.63 364.93 178.69 364.93 174.65 344.18 193.85 344.18 189.63 364.93" style="fill:#ebebeb"></polygon><polygon points="259.88 364.93 248.94 364.93 244.9 344.18 264.1 344.18 259.88 364.93" style="fill:#f5f5f5"></polygon><path d="M206.33,364.93V321.85a6,6,0,0,1,6-6h.24v-10h7.71v9.74h0a6,6,0,0,1,6,6v43.32Z" style="fill:#e0e0e0"></path><rect x="167.08" y="373.43" width="4.56" height="77.96" transform="translate(338.71 824.83) rotate(180)" style="fill:#e0e0e0"></rect><rect x="321.52" y="373.43" width="4.4" height="77.96" style="fill:#e0e0e0"></rect><path d="M251,383.57a4.09,4.09,0,1,1-4.09-4.08A4.09,4.09,0,0,1,251,383.57Z" style="fill:#f5f5f5"></path><path d="M330.43,373.43c0,.29-38.07.52-85,.52s-85-.23-85-.52,38.06-.52,85-.52S330.43,373.15,330.43,373.43Z" style="fill:#ebebeb"></path><rect x="26.05" y="88.69" width="95.75" height="138.78" style="fill:#e0e0e0"></rect><rect x="21.26" y="88.69" width="95.75" height="138.78" style="fill:#f5f5f5"></rect><rect x="33.15" y="103.72" width="71.95" height="108.72" style="fill:#fff"></rect><path d="M105.11,212.43s0-.18,0-.53,0-.87,0-1.54c0-1.36,0-3.37,0-6,0-5.21,0-12.81-.06-22.4,0-19.17-.07-46.32-.12-78.28l.24.23-71.95,0h0l.26-.26c0,41,0,78.82,0,108.71l-.21-.2,52.38.11,14.47,0,3.79,0,1.32,0-1.27,0-3.76,0-14.42.05-52.51.11H33v-.21c0-29.89,0-67.7-.06-108.71v-.26h.27l72,0h.24v.24c0,32-.09,59.23-.12,78.44,0,9.57,0,17.14-.06,22.34,0,2.58,0,4.56,0,5.92,0,.66,0,1.16,0,1.51S105.11,212.43,105.11,212.43Z" style="fill:#e0e0e0"></path><path d="M117.27,88.69a16,16,0,0,1-1.7,2.47c-1.12,1.49-2.69,3.52-4.5,5.7s-3.48,4.13-4.73,5.5a15.67,15.67,0,0,1-2.11,2.15,20.25,20.25,0,0,1,1.83-2.39c1.29-1.55,2.86-3.47,4.61-5.59s3.32-4,4.61-5.59A16.75,16.75,0,0,1,117.27,88.69Z" style="fill:#e0e0e0"></path><path d="M105.47,212.08A89.64,89.64,0,0,0,118,227.78" style="fill:#fff"></path><path d="M118,227.78a14.2,14.2,0,0,1-2.18-2,70.08,70.08,0,0,1-4.74-5.34c-1.74-2.18-3.18-4.25-4.17-5.79a14.72,14.72,0,0,1-1.46-2.58,19.82,19.82,0,0,1,1.77,2.37c1.06,1.48,2.54,3.51,4.27,5.68s3.38,4.05,4.6,5.41A18.52,18.52,0,0,1,118,227.78Z" style="fill:#e0e0e0"></path><path d="M21.24,227.36l12.54-15.48" style="fill:#fff"></path><path d="M33.78,211.88a92.46,92.46,0,0,1-6.06,7.9,93.78,93.78,0,0,1-6.48,7.58,97.71,97.71,0,0,1,6.07-7.91A95.83,95.83,0,0,1,33.78,211.88Z" style="fill:#e0e0e0"></path><path d="M21.26,88.69A84.64,84.64,0,0,1,27.34,96a85,85,0,0,1,5.68,7.6,88.12,88.12,0,0,1-6.08-7.28A85.78,85.78,0,0,1,21.26,88.69Z" style="fill:#e0e0e0"></path><rect x="58.6" y="125.79" width="24.09" height="68.41" style="fill:#e0e0e0"></rect><polygon points="485.48 79.83 485.48 153.22 380.08 153.22 376.28 80.35 485.48 79.83" style="fill:#e0e0e0"></polygon><rect x="392.62" y="64" width="72.72" height="105.41" transform="translate(312.28 545.69) rotate(-90)" style="fill:#fff"></rect><path d="M482.19,153.57H375.78V79.85H482.19Zm-105.41-1H481.19V80.85H376.78Z" style="fill:#e0e0e0"></path><rect x="387.7" y="89.38" width="82.57" height="54.65" style="fill:#fff"></rect><path d="M470.27,89.38l-.41,0h-1.17l-4.54,0-17,.06-59.44.12.23-.24c0,16.7,0,35.2,0,54.65h0l-.26-.26,82.57.06-.21.2c.05-16.63.09-30.27.11-39.76,0-4.75,0-8.46.05-11,0-1.25,0-2.22,0-2.89s0-1,0-1,0,.32,0,1,0,1.61,0,2.85c0,2.53,0,6.22.05,11,0,9.53.06,23.21.11,39.89v.21h-.21l-82.57,0h-.27V144h0c0-19.45,0-37.95,0-54.65v-.24h.24l59.59.12,17,.06,4.48,0,1.15,0Z" style="fill:#e0e0e0"></path><path d="M376.28,80.14a9.68,9.68,0,0,1,1.81,1.18c1.08.77,2.55,1.88,4.13,3.15s3,2.47,4,3.37a9.22,9.22,0,0,1,1.51,1.54,72.67,72.67,0,0,1-5.8-4.51A56.83,56.83,0,0,1,376.28,80.14Z" style="fill:#e0e0e0"></path><path d="M481.69,80.35a52.76,52.76,0,0,1-5.55,4.72,51.6,51.6,0,0,1-5.87,4.31,50.22,50.22,0,0,1,5.55-4.72A48.78,48.78,0,0,1,481.69,80.35Z" style="fill:#e0e0e0"></path><path d="M481.6,153.08l-11.75-9.53" style="fill:#fff"></path><path d="M469.85,143.55a56,56,0,0,1,6,4.56,54.3,54.3,0,0,1,5.71,5,56,56,0,0,1-6-4.56A55.59,55.59,0,0,1,469.85,143.55Z" style="fill:#e0e0e0"></path><path d="M376.28,153.07a48.4,48.4,0,0,1,5.49-4.67,50.59,50.59,0,0,1,5.81-4.27,49.63,49.63,0,0,1-5.49,4.67A50.59,50.59,0,0,1,376.28,153.07Z" style="fill:#e0e0e0"></path><rect x="421.29" y="89.58" width="18.3" height="51.96" transform="translate(314.88 546) rotate(-90)" style="fill:#e0e0e0"></rect><rect x="231.77" y="61.36" width="130.02" height="215.22" style="fill:#e0e0e0"></rect><rect x="227.44" y="61.36" width="130.02" height="215.22" style="fill:#ebebeb"></rect><rect x="232.98" y="65.72" width="118.95" height="206.49" style="fill:#fafafa"></rect><rect x="255.62" y="92.55" width="73.14" height="152.83" style="fill:#ebebeb"></rect><path d="M255.62,191.3c-.17-14.28,12.67-28.21,26.81-30.21,6.74-1,13.9.39,20.2-2.19,11.24-4.61,14.82-19.55,26.14-24l.51,110.46H255.62Z" style="fill:#e0e0e0"></path></g><g id="freepik--Floor--inject-2"><path d="M471.55,451.35c0,.15-99.2.26-221.54.26s-221.56-.11-221.56-.26,99.18-.26,221.56-.26S471.55,451.21,471.55,451.35Z" style="fill:#263238"></path></g><g id="freepik--Couple--inject-2"><path d="M269.42,179.28s-35.6-35.47-38.28-38a5.71,5.71,0,0,0-1.06-.76l-5.41-5.59s-7.52-.48-7.92-.48c-2.39,0-1,2.26-1,2.26-1.79.64.13,2.68.13,2.68-2.17,1.15-.13,2.93-.13,2.93a1.51,1.51,0,0,0-.44,2.64l5.7,2.93V148l1.17.46,4.95,2.55.24-.29c2,2.94,22.55,32.38,27.38,38.27,5.09,6.21,28.47,20,28.47,20l9.68-18.54Z" style="fill:#ffbe9d"></path><path d="M214.63,126.25l-1.88-18.56,9.9-1,1.88,18.57a5,5,0,0,1-3.7,5.29l2.59,25.56c2.29-.19,4.06-.18,4.08.06s-2.09.7-4.74,1-4.81.27-4.83,0,1.7-.61,4-.87l-2.59-25.56A5,5,0,0,1,214.63,126.25Z" style="fill:#e0e0e0"></path><path d="M227.41,137.78l-8.62-3.91a3,3,0,0,0,.23,2.68,6.9,6.9,0,0,0,1.92,2,19.31,19.31,0,0,1,2.17,1.75,6.09,6.09,0,0,0,.52,5.24,4,4,0,0,0,4.77,1.5" style="fill:#ffbe9d"></path><path d="M223,116.35a2.66,2.66,0,0,0-2.53,1.14,5.56,5.56,0,0,1-2.18,1.92,5.65,5.65,0,0,1-2,.07c-.66,0-.8-.3-1.31.13,0,0,.43,6.53,1.3,8.29,1.29,2.61,6.83,2.29,7.39-1.39C224,124.37,223,116.35,223,116.35Z" style="fill:currentColor"></path><path d="M226.64,137.43a14.28,14.28,0,0,1-2.28-.86c-1.42-.59-3.4-1.45-5.7-2.47l.34-.17a4.05,4.05,0,0,0,1.06,3.35,9,9,0,0,0,1.85,1.59c.33.23.67.42,1,.68a1.53,1.53,0,0,1,.45.52,1.25,1.25,0,0,1,.12.68c-.05.43-.16.75-.2,1.08a5.54,5.54,0,0,0,0,1,5.78,5.78,0,0,0,.36,1.74,6.11,6.11,0,0,0,1.41,2.18c.45.44.75.63.72.67a2.41,2.41,0,0,1-.85-.52,5.63,5.63,0,0,1-1.62-2.19,6,6,0,0,1-.45-1.85,6.4,6.4,0,0,1,0-1.07c0-.37.15-.75.18-1.07.11-.63-.71-1-1.38-1.42a9.3,9.3,0,0,1-2-1.67,4.49,4.49,0,0,1-1.14-3.8l.07-.3.27.13c2.29,1,4.24,2,5.62,2.64A14.78,14.78,0,0,1,226.64,137.43Z" style="fill:#eb996e"></path><path d="M217.68,146s.16.14.45.11a.68.68,0,0,0,.6-.86c-.08-.55-.64-1-1.32-1.41l-2.19-1.53.29-.41a11.43,11.43,0,0,0,1.68,1,9.57,9.57,0,0,0,1.59.57c.48.11.88-.23.71-.53s-.65-.65-1-1l-1-.86a12.62,12.62,0,0,1-1.84-1.78,11.52,11.52,0,0,1,2.07,1.5l1,.81.56.45a2,2,0,0,1,.58.64.83.83,0,0,1-.24,1,1.24,1.24,0,0,1-1,.22,8.64,8.64,0,0,1-1.73-.6,10.5,10.5,0,0,1-1.76-1l.3-.41,2.16,1.58a7.41,7.41,0,0,1,.91.75,1.73,1.73,0,0,1,.47,1,1,1,0,0,1-.28.86.88.88,0,0,1-.64.23C217.75,146.28,217.66,146,217.68,146Z" style="fill:#eb996e"></path><path d="M218.6,141.89c0-.08.43-.2.83-.62a.67.67,0,0,0,0-.85,5.68,5.68,0,0,0-1-.95,13.32,13.32,0,0,1-2.67-2.65,13.88,13.88,0,0,1,3,2.25,4.81,4.81,0,0,1,1.06,1.1,1.18,1.18,0,0,1,.17.75.94.94,0,0,1-.31.6,1.57,1.57,0,0,1-.78.37C218.72,141.93,218.61,141.93,218.6,141.89Z" style="fill:#eb996e"></path><path d="M218.89,139.75c0-.07.43,0,.78-.38a1.49,1.49,0,0,0-.3-1.88,12.19,12.19,0,0,1-2.12-2.64,10.49,10.49,0,0,1,2.53,2.31,2.45,2.45,0,0,1,.58,1.32,1.35,1.35,0,0,1-.43,1.15,1,1,0,0,1-.77.26C219,139.86,218.88,139.78,218.89,139.75Z" style="fill:#eb996e"></path><path d="M282.57,233.72c.68-2.09,9.51.31,10.28.46s1-1,.3-1c-1-.14-11.47-1.87-10.82-3.92.36-1.14,2.34-.31,2.34-.31s9.68,2.33,10,1.35-5.36-2.56-7.3-2.95-2.51-1.45-1.63-2.16c.59-.48,6.56.58,8.46,1s7.12,1.71,5.07-1-2.61-5.58-2.22-6.46a1.07,1.07,0,0,1,2,.38,12.45,12.45,0,0,0,1.51,3.08,8.08,8.08,0,0,0,1.86,2.09c.83.66,1.56,1.19,2.2,1.62,4.73.45,22.2.42,34-3.38,2.83-.91,15.44-23.63,15.44-23.63h0L369.32,209l-10.6,18.57c-9.52,20.71-57.53,11.76-57.53,11.76v0a104.16,104.16,0,0,1-11-.08c-5-.53-5-1-4.84-1.56.43-1.55,8.91.59,7.35-.93S282.05,235.32,282.57,233.72Z" style="fill:#eb996e"></path><path d="M340.22,225.06l14.91-11.16-4.22-8.45a44.38,44.38,0,0,0-6.75-10.65,23.92,23.92,0,0,0-13.83-7.06c-3.76-.59-7.39-.81-7.39-.81l-16.18,1.26c-3.71.22-8.74.12-8.74.12l-18.1-6.83-9.26,22.18,18,8.64a30,30,0,0,0-.82,6.95,16.56,16.56,0,0,0,4.66,10l.07.06,5,26.74,39-3.4-2.48-11,2.66-21.29Z" style="fill:#263238"></path><path d="M294.42,232.16a4.08,4.08,0,0,1-.78-.69,18.13,18.13,0,0,1-1.81-2.2,20.46,20.46,0,0,1-2.75-18,17.85,17.85,0,0,1,1.07-2.63,3.38,3.38,0,0,1,.54-.89c.09,0-.62,1.35-1.26,3.63a21.47,21.47,0,0,0,2.71,17.66C293.43,231.05,294.49,232.09,294.42,232.16Z" style="fill:#455a64"></path><path d="M325.45,231.52a2.14,2.14,0,0,1-.73.23,11.59,11.59,0,0,1-2.09.24,14.36,14.36,0,0,1-12-5.88,11.57,11.57,0,0,1-1.09-1.8,1.85,1.85,0,0,1-.27-.72c.08,0,.56.94,1.65,2.29a15.28,15.28,0,0,0,11.75,5.74C324.37,231.65,325.44,231.43,325.45,231.52Z" style="fill:#455a64"></path><path d="M335.19,189.16c0,.06-.5.09-1.34.35a8.23,8.23,0,0,0-3.13,1.92,10.33,10.33,0,0,0-2.76,4.66,13.93,13.93,0,0,0,0,6.67,27.88,27.88,0,0,0,2.43,6.33c.95,1.87,1.91,3.51,2.68,4.91s1.34,2.56,1.71,3.37a6.24,6.24,0,0,1,.49,1.3,7.54,7.54,0,0,1-.68-1.21c-.41-.78-1-1.91-1.84-3.28s-1.8-3-2.79-4.87a26.35,26.35,0,0,1-2.5-6.44,13.91,13.91,0,0,1,0-6.91,10.52,10.52,0,0,1,3-4.8,7.83,7.83,0,0,1,3.34-1.84,5.24,5.24,0,0,1,1-.16A1.2,1.2,0,0,1,335.19,189.16Z" style="fill:#455a64"></path><path d="M309.3,249.41c0,.06-.43-.06-1-.42a5.77,5.77,0,0,1-1.76-1.86,6,6,0,0,1-.82-2.44c-.06-.66,0-1.06.1-1.06a13.47,13.47,0,0,0,1.17,3.23A14.51,14.51,0,0,0,309.3,249.41Z" style="fill:#455a64"></path><path d="M308.56,249.32a3.78,3.78,0,0,1-2.41-.81c-1.21-.78-1.84-1.79-1.73-1.87s.88.71,2,1.43S308.6,249.17,308.56,249.32Z" style="fill:#455a64"></path><path d="M308.64,393.24c-.41,1.6-9.85,54.52-9.85,54.52l-14.45-1.08,2.64-54Z" style="fill:#ffbe9d"></path><path d="M283.9,447.51,284,446l15.4.32-.09,5-.95,0c-4.24.14-21.56.43-24.37-.42C270.81,450,283.9,447.51,283.9,447.51Z" style="fill:currentColor"></path><g style="opacity:0.30000000000000004"><path d="M293.7,451.11a4.92,4.92,0,0,1,2-3,5.47,5.47,0,0,1,3.61-1l-.09,4Z"></path></g><g style="opacity:0.30000000000000004"><path d="M277.5,448.94s-4.6,1.14-4,1.85,18.06,1,25.8.54l0-.32-20.43-.21S278.39,449,277.5,448.94Z"></path></g><path d="M299.48,451l-.26,0-.76,0-2.79.06c-2.36,0-5.61,0-9.21,0s-6.85-.19-9.2-.31l-2.79-.15-.75-.06-.26,0h.26l.76,0,2.79.1c2.35.09,5.6.19,9.2.26s6.84.07,9.2.07h3.55Z" style="fill:#263238"></path><path d="M278.81,451a6.14,6.14,0,0,0-.49-1.24,5.88,5.88,0,0,0-.93-1,2,2,0,0,1,1.09.92A1.78,1.78,0,0,1,278.81,451Z" style="fill:#263238"></path><path d="M283.38,449s-.25-.22-.47-.56-.35-.62-.31-.64.26.22.47.55S283.43,448.94,283.38,449Z" style="fill:#263238"></path><path d="M284.64,448.38s-.27-.12-.53-.34-.44-.44-.41-.47.27.11.53.34S284.68,448.35,284.64,448.38Z" style="fill:#263238"></path><path d="M285.46,447.05a2,2,0,0,1-.81.08,1.86,1.86,0,0,1-.8-.09,1.77,1.77,0,0,1,.8-.08A1.89,1.89,0,0,1,285.46,447.05Z" style="fill:#263238"></path><path d="M285.75,446.11a1.67,1.67,0,0,1-.9.22,1.7,1.7,0,0,1-.92-.13,5.6,5.6,0,0,1,.91,0A4.35,4.35,0,0,1,285.75,446.11Z" style="fill:#263238"></path><path d="M282.89,447.72a3.12,3.12,0,0,1-1,0,6.29,6.29,0,0,1-1.05-.23,3.18,3.18,0,0,1-.59-.24.5.5,0,0,1-.24-.29.35.35,0,0,1,.16-.37,1.89,1.89,0,0,1,2.22.54,1.58,1.58,0,0,1,.31.61.6.6,0,0,1,0,.25,2.72,2.72,0,0,0-.43-.79,1.85,1.85,0,0,0-.83-.52,1.66,1.66,0,0,0-1.18.05c-.16.11-.1.28.07.38a3.35,3.35,0,0,0,.55.23,7.55,7.55,0,0,0,1,.26C282.52,447.68,282.89,447.69,282.89,447.72Z" style="fill:#263238"></path><path d="M282.64,447.76a.8.8,0,0,1-.1-.64,1.4,1.4,0,0,1,.28-.67,1,1,0,0,1,.83-.45.37.37,0,0,1,.3.43,1.19,1.19,0,0,1-.18.41,2.59,2.59,0,0,1-.48.54,1.3,1.3,0,0,1-.57.36s.2-.16.48-.44a3.07,3.07,0,0,0,.42-.54c.14-.2.24-.54,0-.59a.91.91,0,0,0-.66.38,1.41,1.41,0,0,0-.29.59A2.45,2.45,0,0,0,282.64,447.76Z" style="fill:#263238"></path><path d="M299.26,447.1s-.44-.05-1.14,0a5.09,5.09,0,0,0-2.49.9,4.67,4.67,0,0,0-1.59,2,10.26,10.26,0,0,0-.31,1s0-.1,0-.29a3.6,3.6,0,0,1,.19-.76,4.4,4.4,0,0,1,1.6-2.1A4.8,4.8,0,0,1,299,447,1.06,1.06,0,0,1,299.26,447.1Z" style="fill:#263238"></path><path d="M291.8,449.69a9.38,9.38,0,0,1-2.24.16,9.84,9.84,0,0,1-2.24-.2s1,0,2.24,0S291.8,449.64,291.8,449.69Z" style="fill:#263238"></path><path d="M294.81,449.74s-.09.19-.2.41-.16.4-.22.39-.07-.22.06-.47S294.78,449.7,294.81,449.74Z" style="fill:#263238"></path><path d="M296,448.45s-.07.18-.23.33-.32.23-.36.2.07-.18.23-.33S295.93,448.41,296,448.45Z" style="fill:#263238"></path><path d="M297.6,447.88c0,.05-.21,0-.44.11s-.39.18-.43.15.09-.23.38-.31S297.62,447.84,297.6,447.88Z" style="fill:#263238"></path><path d="M298.76,447.62s-.07.11-.2.16-.24,0-.26,0,.07-.12.2-.17S298.75,447.57,298.76,447.62Z" style="fill:#263238"></path><path d="M345.42,388.82c.07,1.65,6.08,53.19,6.08,53.19l-14.15,3.13-13.05-52.5Z" style="fill:#ffbe9d"></path><path d="M336.71,446.51l-.32-1.48,15-3.36,1.11,4.89-.92.27c-4.08,1.14-20.83,5.55-23.77,5.39C324.58,452,336.71,446.51,336.71,446.51Z" style="fill:currentColor"></path><g style="opacity:0.30000000000000004"><path d="M347.09,447.68a4.9,4.9,0,0,1,1.25-3.4,5.45,5.45,0,0,1,3.27-1.8l.87,3.89Z"></path></g><g style="opacity:0.30000000000000004"><path d="M330.83,449.42s-4.19,2.2-3.49,2.76,17.8-3.3,25.2-5.62l-.1-.3-19.89,4.66S331.71,449.26,330.83,449.42Z"></path></g><path d="M352.68,446.2l-.26.08-.73.21-2.69.72c-2.28.6-5.44,1.38-8.95,2.18s-6.7,1.45-9,1.89l-2.74.51-.75.13-.26,0,.25-.07.75-.16,2.73-.56c2.3-.47,5.49-1.15,9-1.94s6.67-1.56,9-2.13l2.71-.67.74-.17A.91.91,0,0,1,352.68,446.2Z" style="fill:#263238"></path><path d="M332.6,451.13a5.61,5.61,0,0,0-.77-1.09,5.84,5.84,0,0,0-1.15-.76,2,2,0,0,1,1.28.64A1.78,1.78,0,0,1,332.6,451.13Z" style="fill:#263238"></path><path d="M336.55,448.05s-.3-.16-.59-.43-.49-.52-.45-.55.3.16.59.43S336.59,448,336.55,448.05Z" style="fill:#263238"></path><path d="M337.63,447.18s-.29-.05-.6-.21-.53-.32-.5-.36.29.05.6.21S337.66,447.14,337.63,447.18Z" style="fill:#263238"></path><path d="M338.11,445.69a1.84,1.84,0,0,1-.76.27,1.9,1.9,0,0,1-.8.1,2.24,2.24,0,0,1,.76-.27A2.11,2.11,0,0,1,338.11,445.69Z" style="fill:#263238"></path><path d="M338.17,444.71a1.68,1.68,0,0,1-.82.43,1.64,1.64,0,0,1-.93.09,5.89,5.89,0,0,1,.88-.26A4.16,4.16,0,0,1,338.17,444.71Z" style="fill:#263238"></path><path d="M335.78,447a3.24,3.24,0,0,1-1,.21,5.32,5.32,0,0,1-1.07,0,3,3,0,0,1-.64-.09.54.54,0,0,1-.3-.22.33.33,0,0,1,.07-.39,1.89,1.89,0,0,1,2.28,0,1.63,1.63,0,0,1,.45.53.5.5,0,0,1,.08.24,2.66,2.66,0,0,0-.61-.67,1.8,1.8,0,0,0-.93-.31,1.64,1.64,0,0,0-1.13.33c-.13.14,0,.3.16.35a2.8,2.8,0,0,0,.59.09,6.16,6.16,0,0,0,1,0C335.41,447,335.77,446.92,335.78,447Z" style="fill:#263238"></path><path d="M335.54,447.05a.88.88,0,0,1-.25-.59,1.49,1.49,0,0,1,.12-.72,1,1,0,0,1,.7-.64.37.37,0,0,1,.39.35,1,1,0,0,1-.08.44,2.73,2.73,0,0,1-.33.64,1.62,1.62,0,0,1-.47.49s.15-.21.35-.55a2.64,2.64,0,0,0,.29-.63c.09-.22.1-.58-.15-.56s-.44.31-.55.52a1.65,1.65,0,0,0-.14.64A3,3,0,0,0,335.54,447.05Z" style="fill:#263238"></path><path d="M351.53,442.46a11,11,0,0,0-1.1.28,5.2,5.2,0,0,0-2.21,1.47,4.72,4.72,0,0,0-1.07,2.32c-.09.65,0,1.06-.06,1.06a.7.7,0,0,1-.06-.28,3.29,3.29,0,0,1,0-.79,4.42,4.42,0,0,1,1.06-2.42,5,5,0,0,1,2.31-1.47,3.92,3.92,0,0,1,.83-.17C351.42,442.44,351.53,442.44,351.53,442.46Z" style="fill:#263238"></path><path d="M344.9,446.74a8.41,8.41,0,0,1-2.14.7,8.8,8.8,0,0,1-2.22.33s1-.21,2.18-.5S344.88,446.7,344.9,446.74Z" style="fill:#263238"></path><path d="M347.83,446.08c.05,0,0,.21-.09.44s-.06.43-.12.44-.12-.21-.06-.47S347.79,446.05,347.83,446.08Z" style="fill:#263238"></path><path d="M348.65,444.55c.05,0,0,.19-.14.37s-.26.31-.3.28,0-.19.15-.37S348.61,444.52,348.65,444.55Z" style="fill:#263238"></path><path d="M350.11,443.61c0,.05-.2.09-.41.21s-.33.27-.38.24,0-.24.29-.39S350.11,443.56,350.11,443.61Z" style="fill:#263238"></path><path d="M351.17,443.08s0,.13-.16.2-.22.1-.25.06,0-.13.15-.2S351.14,443,351.17,443.08Z" style="fill:#263238"></path><path d="M302.59,141.58a6.17,6.17,0,0,0-7.46-.7,9.09,9.09,0,0,0-4,6.73c-.21,1.68-.09,3.47-.92,4.94s-2.7,2.42-3.93,3.76a6.63,6.63,0,0,0-.86,7.57c.73,1.29,1.95,2.56,1.61,4s-2.22,2.07-3.32,3.2c-1.62,1.66-1.36,4.35-.88,6.63a3.64,3.64,0,0,0,1.46,2.66,4.11,4.11,0,0,0,2.46.13c5.22-.73,10.5.37,15.76.66s11-.44,14.82-4" style="fill:#263238"></path><path d="M322,151.67l.51,42.36a9.59,9.59,0,0,1-9.59,9.4h0a9.59,9.59,0,0,1-9.59-9.59V180.31s-6.75-1.12-8.15-9.93c-.7-4.38-.48-11.57-.11-17.44.34-5.28,3.8-11.93,10.84-11.91C305.92,141,321.29,139.92,322,151.67Z" style="fill:#ffbe9d"></path><path d="M296.63,159a1.11,1.11,0,0,0,1.08,1.14,1.08,1.08,0,0,0,1.17-1,1.13,1.13,0,0,0-1.09-1.14A1.08,1.08,0,0,0,296.63,159Z" style="fill:#263238"></path><path d="M296.39,157.78c.14.15,1-.49,2.21-.48s2.1.61,2.24.46-.08-.33-.47-.61a3.07,3.07,0,0,0-3.55,0C296.44,157.45,296.32,157.72,296.39,157.78Z" style="fill:#263238"></path><path d="M308.38,159a1.13,1.13,0,0,0,1.09,1.14,1.08,1.08,0,0,0,1.17-1,1.13,1.13,0,0,0-1.09-1.14A1.09,1.09,0,0,0,308.38,159Z" style="fill:#263238"></path><path d="M308.34,157.78c.14.15,1-.48,2.21-.48s2.11.61,2.24.46-.07-.33-.46-.61a3.09,3.09,0,0,0-3.56,0C308.4,157.45,308.27,157.72,308.34,157.78Z" style="fill:#263238"></path><path d="M304.18,166.92a7.76,7.76,0,0,0-2-.35c-.31,0-.61-.1-.66-.31a1.62,1.62,0,0,1,.21-.92c.29-.75.59-1.54.92-2.36a42.1,42.1,0,0,0,2.06-6.19,40.58,40.58,0,0,0-2.56,6c-.3.83-.6,1.62-.88,2.37a1.85,1.85,0,0,0-.16,1.22.8.8,0,0,0,.51.46,2.21,2.21,0,0,0,.53.07A7.54,7.54,0,0,0,304.18,166.92Z" style="fill:#263238"></path><path d="M303.34,180.31a22.42,22.42,0,0,0,11.8-3.15s-2.86,6.06-11.68,5.23Z" style="fill:#eb996e"></path><path d="M304.7,171.1a1.13,1.13,0,0,1,.05-1.21,1.08,1.08,0,0,1,1.14-.39.94.94,0,0,1,.64-1,1.36,1.36,0,0,1,1.2.21,1.85,1.85,0,0,1,.61,2.17,2,2,0,0,1-1.94,1.21,2.25,2.25,0,0,1-1.9-1.33" style="fill:#eb996e"></path><path d="M307.76,167.76c-.2,0-.2,1.31-1.33,2.25s-2.53.79-2.54,1,.31.25.9.27a3.32,3.32,0,0,0,2.13-.74,2.89,2.89,0,0,0,1-1.88C308,168.08,307.85,167.76,307.76,167.76Z" style="fill:#263238"></path><path d="M307.28,155.65c.12.33,1.33.18,2.76.35s2.58.56,2.77.26c.08-.14-.12-.45-.58-.77A4.87,4.87,0,0,0,308,155C307.49,155.24,307.23,155.5,307.28,155.65Z" style="fill:#263238"></path><path d="M296.55,155.43c.22.28,1.06,0,2.06,0s1.86.19,2.06-.1-.05-.42-.42-.68a2.79,2.79,0,0,0-1.67-.45,2.86,2.86,0,0,0-1.65.55C296.58,155,296.46,155.3,296.55,155.43Z" style="fill:#263238"></path><path d="M315.72,167.76c-.07,1.17-.07,2.19-.05,3.07a7.88,7.88,0,0,0,4.08,6.73,14.23,14.23,0,0,0,6.09,1.93,18.51,18.51,0,0,0,6.82-1.36q5.65-2,11.12-4.43a2,2,0,0,0,1.28-2.95l-1.53-5a11.47,11.47,0,0,0-1.12-2.79,3.87,3.87,0,0,0-2.3-1.83c-.8-.2-1.66-.05-2.44-.33a3.27,3.27,0,0,1-1.77-1.89,15.15,15.15,0,0,1-.66-2.57c-.42-1.87-1.3-3.83-3-4.62-1-.45-2.13-.46-3.13-.87a6.71,6.71,0,0,1-3.13-3.22,43.51,43.51,0,0,0-2.11-4.07,5.88,5.88,0,0,0-2.59-2.2,9.17,9.17,0,0,0-1.8-1.32c-2.92-1.61-6.44-1.5-9.76-1.35s-10.6-2.92-11.07,5.61a2.7,2.7,0,0,1,4.31.37,4.92,4.92,0,0,1,.72,3.67c-.25,1.43-.43,3,.35,4.25a4.25,4.25,0,0,0,3.91,1.69,12.7,12.7,0,0,0,4.24-1.37s1.08,5.58,4.79,6.93Z" style="fill:#263238"></path><path d="M340.25,175.08a2.16,2.16,0,0,0,.55-.33,1.81,1.81,0,0,0,.43-1.67,2.69,2.69,0,0,0-2-1.8c-1.07-.38-2.46-.33-3.84-1a3.82,3.82,0,0,1-1.64-1.73,7.94,7.94,0,0,1-.68-2.45c-.22-1.72-.07-3.63-1-5.27a4.08,4.08,0,0,0-2.13-1.75,11.63,11.63,0,0,0-2.95-.63,16,16,0,0,1-3.21-.56,5,5,0,0,1-2.83-1.92,6.2,6.2,0,0,1-.74-3.32,6.93,6.93,0,0,0-.49-3,2.72,2.72,0,0,0-2.34-1.38,9.22,9.22,0,0,0-2.86.33,11.21,11.21,0,0,1-2.85.41,3.51,3.51,0,0,1-2.58-1,4.11,4.11,0,0,1-1-2.38c-.1-.81-.12-1.57-.21-2.29a4.76,4.76,0,0,0-.58-1.91,3.36,3.36,0,0,0-1.28-1.21,3.65,3.65,0,0,0-2.79-.29,4.41,4.41,0,0,0-1.62.89c-.32.27-.46.45-.48.44s0-.06.09-.15a2.7,2.7,0,0,1,.32-.37,4.27,4.27,0,0,1,1.63-1,3.84,3.84,0,0,1,3,.22,3.7,3.7,0,0,1,1.42,1.29,5,5,0,0,1,.66,2c.11.74.14,1.51.25,2.29a3.69,3.69,0,0,0,.91,2.13,3.1,3.1,0,0,0,2.27.84,11.53,11.53,0,0,0,2.73-.42,9.67,9.67,0,0,1,3-.37,3.23,3.23,0,0,1,2.75,1.64c1,2.09.06,4.5,1.22,6.31a4.48,4.48,0,0,0,2.54,1.71,17.15,17.15,0,0,0,3.11.54,12,12,0,0,1,3.09.68,4.53,4.53,0,0,1,2.35,2c1,1.83.79,3.8,1,5.45s.79,3.26,2.06,3.92,2.64.65,3.76,1.06a2.91,2.91,0,0,1,2.15,2,1.9,1.9,0,0,1-.56,1.79A.88.88,0,0,1,340.25,175.08Z" style="fill:#455a64"></path><path d="M287.68,180.91s-.16-.07-.44-.25a4.69,4.69,0,0,1-1-1.08,5.26,5.26,0,0,1-.82-2.18,5.18,5.18,0,0,1,.47-3.06,10.17,10.17,0,0,1,2.54-2.72,4,4,0,0,0,1.11-1.57,2.77,2.77,0,0,0-.09-2c-.52-1.34-1.66-2.46-2.47-3.93a6,6,0,0,1-.35-5.06,4.47,4.47,0,0,1,1.77-2,7.61,7.61,0,0,0,1.88-1.48,7.82,7.82,0,0,0,1.48-4.2c.16-1.46.12-2.89.3-4.22a9.11,9.11,0,0,1,1.08-3.58,4.69,4.69,0,0,1,2.33-2,3.1,3.1,0,0,1,2.33,0,2.45,2.45,0,0,1,1.12,1,1.08,1.08,0,0,1,.2.47,2.81,2.81,0,0,0-1.4-1.24,2.92,2.92,0,0,0-2.15.06,4.41,4.41,0,0,0-2.11,2,8.56,8.56,0,0,0-1,3.44c-.15,1.3-.1,2.71-.25,4.22a8.3,8.3,0,0,1-1.57,4.48,8.06,8.06,0,0,1-2,1.58,3.87,3.87,0,0,0-1.58,1.75,5.53,5.53,0,0,0,.32,4.62c.74,1.37,1.91,2.52,2.47,4a3.32,3.32,0,0,1,.08,2.28,4.42,4.42,0,0,1-1.26,1.74,10.1,10.1,0,0,0-2.51,2.57,5,5,0,0,0-.5,2.85,5.24,5.24,0,0,0,.7,2.11A6.08,6.08,0,0,0,287.68,180.91Z" style="fill:#455a64"></path><path d="M327,145.94a.56.56,0,0,1,.08-.4,1.13,1.13,0,0,1,1.06-.51,2.39,2.39,0,0,1,1.54,1,4.85,4.85,0,0,1,1,2.24,2.79,2.79,0,0,1-1.1,2.76,4.12,4.12,0,0,1-3.4.47,5.76,5.76,0,0,1-3.1-2.15,6.9,6.9,0,0,1-1.21-3.87,30.24,30.24,0,0,0-.23-3.91,4.69,4.69,0,0,0-1.65-3,3.92,3.92,0,0,0-3-.5,19,19,0,0,0-2.73.87,9.32,9.32,0,0,1-2.37.59,4.65,4.65,0,0,1-1.84-.22,2.84,2.84,0,0,1-1-.59.91.91,0,0,1-.26-.31,4.47,4.47,0,0,0,1.33.71,4.52,4.52,0,0,0,1.74.12,9.6,9.6,0,0,0,2.27-.64,16.69,16.69,0,0,1,2.78-.95,4.3,4.3,0,0,1,3.38.53,5.09,5.09,0,0,1,1.88,3.29,29.24,29.24,0,0,1,.25,4,6.52,6.52,0,0,0,1.1,3.6,5.38,5.38,0,0,0,2.82,2,3.7,3.7,0,0,0,3-.37,2.39,2.39,0,0,0,1-2.35,4.66,4.66,0,0,0-.89-2.11,2.26,2.26,0,0,0-1.33-1,1.06,1.06,0,0,0-.95.36C327,145.79,327.05,145.94,327,145.94Z" style="fill:#263238"></path><path d="M311.89,162.19a.26.26,0,0,1-.34.15.25.25,0,0,1-.13-.34.25.25,0,0,1,.33-.14A.25.25,0,0,1,311.89,162.19Z" style="fill:#263238"></path><path d="M422.19,213.37c-1.45-1.65-8.63,4-9.27,4.47s-1.3-.48-.7-.84c.87-.52,9.82-6.22,8.41-7.86-.77-.91-2.27.64-2.27.64s-8,5.95-8.62,5.16,3.92-4.46,5.55-5.58,1.73-2.33.65-2.63c-.74-.21-5.8,3.11-7.39,4.26s-5.87,4.37-5.05,1.06.2-6.16-.5-6.81-1.87.08-1.67,1.12a12.55,12.55,0,0,1-.18,3.44,8,8,0,0,1-.89,2.64c-.5.94-1,1.71-1.38,2.36-4.18,2.28-17.28,8.85-29.67,10a9.84,9.84,0,0,1-8.39-2.81l-6.57-7.83-12.68,9.65s3.95,4.91,9.45,11.54c14.39,17.35,56.27-9.48,56.27-9.48l0,0a101.21,101.21,0,0,0,10.06-4.4c4.44-2.47,4.22-2.87,3.84-3.33-1-1.26-8,4.05-7.13,2S423.29,214.64,422.19,213.37Z" style="fill:#ffbe9d"></path><path d="M193.25,88.13a20.3,20.3,0,0,1,2.39,2.42c1.42,1.54,3.34,3.71,5.4,6.16s3.87,4.73,5.15,6.39a20.56,20.56,0,0,1,2,2.78,24.44,24.44,0,0,1-2.25-2.55l-5.25-6.28-5.29-6.26A22.36,22.36,0,0,1,193.25,88.13Z" style="fill:#263238"></path><path d="M210.28,91.3a30.68,30.68,0,0,1,.28,5.7,32.11,32.11,0,0,1-.24,5.71A30.68,30.68,0,0,1,210,97,32.11,32.11,0,0,1,210.28,91.3Z" style="fill:#263238"></path><path d="M223,98.67c.09.11-.76,1-1.91,2a8.1,8.1,0,0,1-2.26,1.64c-.1-.11.76-1,1.91-2A7.8,7.8,0,0,1,223,98.67Z" style="fill:#263238"></path><path d="M363.18,390.89s-6.18,12-22.33,6.19c0,0-15.8,8.58-30.91-1,0,0-19.57,2.63-30.22-2.52l13.69-118.1,4-24.39,37.93-4.43s9.84,7.44,16.2,42.85c5.06,28.14,8.25,69.84,8.25,69.84Z" style="fill:currentColor"></path><path d="M320.81,248.29c.15,0,2.5,34,5.26,75.87s4.88,75.9,4.73,75.91-2.49-34-5.25-75.88S320.67,248.3,320.81,248.29Z" style="fill:#fff"></path><path d="M330.33,247.18a1.69,1.69,0,0,1,.05.39c0,.28.06.66.11,1.14.08,1,.19,2.51.34,4.4l1.26,16.2c1.1,13.67,2.77,32.55,4.88,53.37s4.24,39.66,5.79,53.29c.77,6.8,1.4,12.3,1.84,16.14.2,1.89.36,3.36.47,4.4l.12,1.14c0,.26,0,.39,0,.39a1.74,1.74,0,0,1-.08-.38c0-.29-.09-.66-.16-1.14-.14-1-.33-2.5-.58-4.38-.47-3.84-1.15-9.34-2-16.12-1.64-13.62-3.82-32.45-5.94-53.29s-3.74-39.71-4.74-53.39c-.5-6.84-.87-12.38-1.09-16.21-.11-1.89-.19-3.38-.25-4.41,0-.49,0-.87-.05-1.15A1.91,1.91,0,0,1,330.33,247.18Z" style="fill:#fff"></path><path d="M358.41,394.89a2.91,2.91,0,0,1-.05-.38c0-.28-.06-.66-.1-1.14-.07-1-.18-2.49-.32-4.37-.29-3.83-.7-9.31-1.21-16.08-1.08-13.56-2.77-32.3-5.64-52.87-1.47-10.28-3.13-20-4.82-28.87-.9-4.41-1.71-8.61-2.62-12.5-.44-1.95-.86-3.84-1.27-5.65s-.87-3.53-1.28-5.19c-.78-3.31-1.62-6.27-2.31-8.88-.36-1.3-.68-2.52-1-3.64s-.62-2.12-.89-3l-1.22-4.2c-.13-.47-.23-.83-.3-1.1a1.88,1.88,0,0,1-.09-.38,2.3,2.3,0,0,1,.14.36c.08.27.2.63.35,1.08l1.32,4.19.93,3,1,3.63c.72,2.61,1.59,5.57,2.39,8.87.42,1.66.85,3.39,1.31,5.19s.85,3.69,1.3,5.64c.93,3.9,1.75,8.1,2.67,12.51,1.72,8.84,3.39,18.6,4.86,28.89,2.88,20.58,4.51,39.34,5.5,52.92.49,6.79.83,12.28,1,16.09.1,1.88.17,3.35.23,4.38,0,.48,0,.85,0,1.14A1.83,1.83,0,0,1,358.41,394.89Z" style="fill:#fff"></path><path d="M312.54,249.26a1.76,1.76,0,0,1,0,.38c0,.27,0,.64.05,1.11,0,1,.07,2.45.13,4.29.1,3.73.19,9.13.24,15.79.1,13.34-.1,31.76-.67,52.1S311,361.68,310.49,375c-.27,6.64-.49,12-.64,15.78-.08,1.84-.15,3.28-.19,4.29,0,.47-.05.84-.07,1.11a1.42,1.42,0,0,1,0,.38,2.21,2.21,0,0,1,0-.38c0-.28,0-.65,0-1.12,0-1,.06-2.45.1-4.29.11-3.75.27-9.14.47-15.78.43-13.33,1.1-31.74,1.68-52.08s.83-38.74.82-52.08c0-6.66,0-12.06-.08-15.78,0-1.84,0-3.29,0-4.3,0-.47,0-.84,0-1.11A1.52,1.52,0,0,1,312.54,249.26Z" style="fill:#fff"></path><path d="M297.4,396.11a1.59,1.59,0,0,1,0-.38c0-.28,0-.64,0-1.11,0-1,0-2.44,0-4.26,0-3.71,0-9.07.07-15.69.16-13.24.67-31.54,1.81-51.71s2.68-38.42,4-51.6c.66-6.59,1.24-11.92,1.69-15.59l.51-4.24.15-1.1a2.41,2.41,0,0,1,.07-.37,2.17,2.17,0,0,1,0,.38c0,.28-.05.64-.09,1.11l-.42,4.24c-.38,3.68-.91,9-1.52,15.6-1.24,13.19-2.72,31.42-3.86,51.6s-1.71,38.45-2,51.69c-.13,6.62-.21,12-.24,15.68,0,1.82,0,3.26-.06,4.26,0,.47,0,.83,0,1.11A1.88,1.88,0,0,1,297.4,396.11Z" style="fill:#fff"></path><path d="M298.9,250.85a2.58,2.58,0,0,1,0,.37c0,.27-.05.64-.08,1.09-.09,1-.22,2.4-.38,4.2-.33,3.64-.84,8.91-1.5,15.41-1.33,13-3.3,31-5.66,50.78s-4.65,37.74-6.33,50.7c-.86,6.46-1.55,11.7-2,15.35-.25,1.79-.44,3.19-.58,4.17-.07.46-.12.82-.17,1.09a2.08,2.08,0,0,1-.07.36,2.07,2.07,0,0,1,0-.37l.12-1.09c.11-1,.27-2.39.48-4.18.44-3.66,1.08-8.9,1.87-15.37,1.58-13,3.77-30.93,6.18-50.72s4.39-37.76,5.81-50.76c.7-6.47,1.27-11.73,1.67-15.39.2-1.8.36-3.2.47-4.19.06-.45.1-.81.14-1.08A1.74,1.74,0,0,1,298.9,250.85Z" style="fill:#fff"></path><g style="opacity:0.30000000000000004"><path d="M312.65,295.88c-3.5,30.58-7.37,60.66-7.71,91.43,0,2.34,0,4.84,1.31,6.78s4.41,2.82,6,1.1c1.14-1.23,1-3.13.84-4.8-3.4-31.42-2.73-63-.44-94.51"></path></g><g style="opacity:0.30000000000000004"><path d="M340.85,397.08c-1-21.17-1.44-45.07-3.35-66.77,5,18.14,6.47,36.28,11.49,54.42.6,2.16,1.94,5.67,1.76,7.91s-.88,4.43-2.9,5.4-7.31,0-7-1"></path></g><path d="M404.9,147.67l-3.19,37-18.53-1.48c-.12-2.16,0-10.53,0-10.53s-7.71-1.2-7.81-8.91c-.05-3.74.49-11.48,1-18.45a14.27,14.27,0,0,1,15.05-13.09h0A14.27,14.27,0,0,1,404.9,147.67Z" style="fill:#eb996e"></path><path d="M383.2,172.54a17.88,17.88,0,0,0,10.05-2.86s-2.37,5.55-10.07,4.86Z" style="fill:#d1734a"></path><path d="M378.35,150.42a1.11,1.11,0,0,0,1.06,1.11,1.06,1.06,0,0,0,1.14-1,1.11,1.11,0,0,0-1.06-1.11A1.06,1.06,0,0,0,378.35,150.42Z" style="fill:#263238"></path><path d="M378.11,149.18c.14.14,1-.47,2.16-.47s2.05.59,2.18.45-.08-.32-.46-.6a3.07,3.07,0,0,0-1.74-.54,3,3,0,0,0-1.72.56C378.17,148.85,378,149.11,378.11,149.18Z" style="fill:#263238"></path><path d="M390,150.69a1.11,1.11,0,0,0,1.06,1.12,1.06,1.06,0,0,0,1.14-1,1.1,1.1,0,0,0-1.06-1.11A1.06,1.06,0,0,0,390,150.69Z" style="fill:#263238"></path><path d="M389.75,149.4c.14.14,1-.47,2.16-.47s2,.59,2.18.45-.07-.32-.45-.6a3.1,3.1,0,0,0-1.75-.54,3,3,0,0,0-1.72.56C389.81,149.08,389.69,149.34,389.75,149.4Z" style="fill:#263238"></path><path d="M385.46,158.18a8.65,8.65,0,0,0-1.93-.34c-.3,0-.59-.09-.64-.3a1.61,1.61,0,0,1,.2-.9c.29-.73.59-1.5.9-2.3a40.75,40.75,0,0,0,2-6,38,38,0,0,0-2.49,5.84c-.3.81-.59,1.58-.86,2.32a1.8,1.8,0,0,0-.16,1.19.8.8,0,0,0,.5.44,2.13,2.13,0,0,0,.52.07A7.2,7.2,0,0,0,385.46,158.18Z" style="fill:#263238"></path><path d="M388.94,159c-.19,0-.19,1.28-1.29,2.19s-2.47.77-2.48.95.3.25.88.27a3.23,3.23,0,0,0,2.07-.72,2.78,2.78,0,0,0,1-1.84C389.17,159.31,389,159,388.94,159Z" style="fill:#263238"></path><path d="M389.45,144.76c.12.32,1.3.17,2.69.33s2.51.55,2.7.26c.08-.14-.12-.44-.57-.75a4.51,4.51,0,0,0-2-.7,4.44,4.44,0,0,0-2.1.25C389.65,144.35,389.4,144.6,389.45,144.76Z" style="fill:#263238"></path><path d="M378.55,145.75c.2.27,1,0,2,0s1.81.18,2-.1,0-.41-.41-.66a2.7,2.7,0,0,0-1.63-.45,2.84,2.84,0,0,0-1.6.55C378.57,145.34,378.45,145.62,378.55,145.75Z" style="fill:#263238"></path><path d="M396.53,139.21l0,0a6,6,0,0,1,2.25,3.92l0,.15c.31,2.2.25,4.44.57,6.63a10.06,10.06,0,0,0,2.71,6c.67.62,1.85,1,2.4.32a1.84,1.84,0,0,0,.27-1c.41-5.31,1.86-10,1.85-15.57a4.67,4.67,0,0,0-1.94-3.81c-1.16-.83-2.58-1.69-3.87-1.08s-3,3-4,4.05" style="fill:#263238"></path><path d="M402.3,151.15c.13-.05,5.29-1.56,5.12,3.71s-5.42,4-5.42,3.86S402.3,151.15,402.3,151.15Z" style="fill:#eb996e"></path><path d="M403.67,156.78s.09.06.24.14a.92.92,0,0,0,.69,0,2.25,2.25,0,0,0,1.11-2,3,3,0,0,0-.21-1.32,1.07,1.07,0,0,0-.67-.73.47.47,0,0,0-.55.24c-.07.14,0,.25-.07.25s-.11-.09-.06-.3a.55.55,0,0,1,.22-.33.7.7,0,0,1,.51-.12,1.31,1.31,0,0,1,.93.85,3.11,3.11,0,0,1,.27,1.48,2.37,2.37,0,0,1-1.41,2.23,1,1,0,0,1-.84-.16C403.68,156.9,403.65,156.78,403.67,156.78Z" style="fill:#d1734a"></path><path d="M401,135.56c1.29-2.11-2.26-5.13-4.86-5.95s-6,.44-8.62,1.3c-1.63.55-3.26,1.3-5,1.23a11.9,11.9,0,0,1-3.34-.85,8.38,8.38,0,0,0-3.37-.67,3.18,3.18,0,0,0-2.77,1.76,2,2,0,0,0,1.33,2.69,2.59,2.59,0,1,0-.69,4.8c-.75-.57-1.9.37-1.81,1.3a2.76,2.76,0,0,0,1.81,2,9.86,9.86,0,0,0,7-.11c2.28-.77,4.39-2,6.64-2.78s6-2.3,8.3-1.5c1.66.59,2.82,2.6,3.76,2.15a3.22,3.22,0,0,0,1.67-2.37,9.46,9.46,0,0,0-.1-3" style="fill:#263238"></path><path d="M373.76,134.93c.05,0,.28.39.84,1a7.26,7.26,0,0,0,2.93,1.89,6.7,6.7,0,0,0,5-.21c1.79-.8,3.32-2.41,5.09-3.92a10.93,10.93,0,0,1,5.92-2.85,6.12,6.12,0,0,1,5,1.75A4.6,4.6,0,0,1,399.8,136a3.24,3.24,0,0,1-.22,1,1.46,1.46,0,0,1-.17.31,8.76,8.76,0,0,0,.19-1.28,4.54,4.54,0,0,0-1.26-3.15,5.74,5.74,0,0,0-4.72-1.52,10.67,10.67,0,0,0-5.64,2.77c-1.73,1.48-3.31,3.13-5.23,4a7.15,7.15,0,0,1-8.3-2,4.29,4.29,0,0,1-.55-.82A1.35,1.35,0,0,1,373.76,134.93Z" style="fill:#455a64"></path><path d="M403.64,150.92s-.23-.14-.43-.56a3.51,3.51,0,0,1-.26-1.91c.12-1.7,1.62-3.52,1.81-5.86a5.34,5.34,0,0,0-.58-3.15,4.05,4.05,0,0,0-2-1.69,3.4,3.4,0,0,0-1.76-.24c-.43.07-.65.16-.67.12s.18-.18.63-.32a3.3,3.3,0,0,1,1.92.09,4.31,4.31,0,0,1,2.25,1.8,5.61,5.61,0,0,1,.69,3.43,9.78,9.78,0,0,1-1,3.33,10.09,10.09,0,0,0-.95,2.54A4.09,4.09,0,0,0,403.64,150.92Z" style="fill:#455a64"></path><path d="M373.92,446.73l-.05-10,18.65-.22.24,14.57-1.15.09c-5.15.35-26.19,1.4-29.64.4C358.11,450.5,373.92,446.73,373.92,446.73Z" style="fill:currentColor"></path><g style="opacity:0.4"><path d="M386,451.05a6.45,6.45,0,0,1,2.34-4,6.23,6.23,0,0,1,4.34-1.41l.07,5.21Z"></path></g><g style="opacity:0.4"><path d="M366.2,448.85s-5.54,1.67-4.83,2.57,22,.64,31.39-.3l0-.42-24.85.53S367.29,448.88,366.2,448.85Z"></path></g><path d="M393,450.69l-.32,0-.92.06-3.39.19c-2.86.14-6.82.28-11.19.34s-8.34,0-11.21,0l-3.39-.09-.92-.05-.32,0h1.24l3.4,0c2.86,0,6.82,0,11.19,0s8.33-.18,11.19-.28l3.39-.12.93,0Z" style="fill:#263238"></path><path d="M367.89,451.52a7.67,7.67,0,0,0-.65-1.61,6.82,6.82,0,0,0-1.18-1.28,2.45,2.45,0,0,1,1.37,1.16A2.51,2.51,0,0,1,367.89,451.52Z" style="fill:#263238"></path><path d="M373.36,448.66c-.05,0-.32-.29-.6-.71s-.46-.8-.41-.83.32.28.6.7S373.41,448.62,373.36,448.66Z" style="fill:#263238"></path><path d="M374.86,447.84c0,.05-.34-.15-.66-.43s-.56-.56-.52-.6.34.14.67.43S374.9,447.79,374.86,447.84Z" style="fill:#263238"></path><path d="M375.79,446.07a2.23,2.23,0,0,1-1,.13,2.33,2.33,0,0,1-1-.09,2.3,2.3,0,0,1,1-.13C375.35,446,375.79,446,375.79,446.07Z" style="fill:#263238"></path><path d="M376.1,444.82a1.87,1.87,0,0,1-1.08.33,1.83,1.83,0,0,1-1.12-.14,7.1,7.1,0,0,1,1.1-.09A6,6,0,0,1,376.1,444.82Z" style="fill:#263238"></path><path d="M372.71,447a3.8,3.8,0,0,1-1.2,0,6.46,6.46,0,0,1-1.28-.26,3.64,3.64,0,0,1-.74-.29.66.66,0,0,1-.3-.37.46.46,0,0,1,.18-.48,2.19,2.19,0,0,1,2.72.61,2.08,2.08,0,0,1,.4.79.61.61,0,0,1,0,.32,3.41,3.41,0,0,0-.56-1,2.06,2.06,0,0,0-1-.65,1.87,1.87,0,0,0-1.43.11c-.19.15-.11.37.1.49a3.36,3.36,0,0,0,.68.28,8.61,8.61,0,0,0,1.24.31C372.25,447,372.71,447,372.71,447Z" style="fill:#263238"></path><path d="M372.4,447.1a1.16,1.16,0,0,1-.15-.83,2,2,0,0,1,.31-.88,1.19,1.19,0,0,1,1-.62.47.47,0,0,1,.38.54,1.24,1.24,0,0,1-.2.54,3.84,3.84,0,0,1-.55.74,1.85,1.85,0,0,1-.68.49s.23-.22.56-.6a4,4,0,0,0,.49-.73c.16-.25.27-.71,0-.76s-.61.28-.79.52a2,2,0,0,0-.32.78A4,4,0,0,0,372.4,447.1Z" style="fill:#263238"></path><path d="M392.57,445.59s-.53,0-1.37.06a5.83,5.83,0,0,0-4.84,4c-.28.8-.3,1.34-.34,1.34a1.25,1.25,0,0,1,0-.38,4.62,4.62,0,0,1,.19-1,5.69,5.69,0,0,1,5-4.07,4.73,4.73,0,0,1,1,0C392.45,445.54,392.58,445.57,392.57,445.59Z" style="fill:#263238"></path><path d="M390.54,437.85a66.8,66.8,0,0,1,.2,7.66,29.92,29.92,0,0,1-.21-3.83A32.34,32.34,0,0,1,390.54,437.85Z" style="fill:#263238"></path><path d="M383.62,449.26a10.23,10.23,0,0,1-2.72.31,11,11,0,0,1-2.73-.18c0-.07,1.23,0,2.73-.05S383.62,449.2,383.62,449.26Z" style="fill:#263238"></path><path d="M387.28,449.21c.05.05-.1.26-.22.54s-.18.53-.25.53-.1-.29.05-.62S387.24,449.17,387.28,449.21Z" style="fill:#263238"></path><path d="M388.64,447.48s-.08.24-.27.44-.38.32-.42.28.07-.24.26-.44S388.59,447.44,388.64,447.48Z" style="fill:#263238"></path><path d="M390.6,446.67c0,.06-.26.07-.53.17s-.47.24-.52.2.1-.3.44-.42S390.62,446.61,390.6,446.67Z" style="fill:#263238"></path><path d="M392,446.28c0,.06-.09.16-.24.22s-.3.07-.32,0,.08-.16.24-.22S392,446.23,392,446.28Z" style="fill:#263238"></path><path d="M429.1,448l-1.49-9.84L446,435.24l2.35,14.38-1.13.26c-5,1.09-25.71,5.18-29.27,4.69C414,454,429.1,448,429.1,448Z" style="fill:currentColor"></path><g style="opacity:0.4"><path d="M441.68,450.54a6.42,6.42,0,0,1,1.72-4.3,6.25,6.25,0,0,1,4.1-2l.82,5.14Z"></path></g><g style="opacity:0.4"><path d="M421.77,451.22s-5.24,2.46-4.41,3.25,21.87-2.55,31-4.85l-.09-.4-24.51,4.12S422.85,451.1,421.77,451.22Z"></path></g><path d="M448.57,449.16a1.31,1.31,0,0,1-.31.09l-.9.2-3.33.67c-2.81.56-6.7,1.27-11,2s-8.25,1.23-11.09,1.58l-3.37.4-.92.09-.32,0,.31-.06.92-.14,3.36-.46c2.84-.4,6.75-1,11.07-1.65s8.22-1.38,11-1.89l3.34-.61.91-.16A1.92,1.92,0,0,1,448.57,449.16Z" style="fill:#263238"></path><path d="M423.83,453.62a8.2,8.2,0,0,0-.88-1.5,8.45,8.45,0,0,0-1.35-1.1,2.58,2.58,0,0,1,1.52,1A2.48,2.48,0,0,1,423.83,453.62Z" style="fill:#263238"></path><path d="M428.83,450c-.05,0-.36-.24-.7-.62s-.57-.72-.52-.76.36.23.69.61S428.87,450,428.83,450Z" style="fill:#263238"></path><path d="M430.19,449s-.35-.09-.72-.33-.63-.47-.59-.52.35.1.72.33S430.23,448.92,430.19,449Z" style="fill:#263238"></path><path d="M430.86,447.08a2.18,2.18,0,0,1-.94.28,2.21,2.21,0,0,1-1,.05,2.25,2.25,0,0,1,.94-.27A2.14,2.14,0,0,1,430.86,447.08Z" style="fill:#263238"></path><path d="M431,445.8a2.74,2.74,0,0,1-2.16.52,5.55,5.55,0,0,1,1.08-.25A7.19,7.19,0,0,1,431,445.8Z" style="fill:#263238"></path><path d="M427.94,448.49a3.32,3.32,0,0,1-1.18.18,6.19,6.19,0,0,1-1.31-.08,2.87,2.87,0,0,1-.76-.18.69.69,0,0,1-.36-.32.48.48,0,0,1,.11-.5,2.18,2.18,0,0,1,2.78.21,2.08,2.08,0,0,1,.52.73.67.67,0,0,1,.08.31,3.54,3.54,0,0,0-.7-.92,2.19,2.19,0,0,0-1.12-.5,1.81,1.81,0,0,0-1.39.32c-.17.17-.06.38.17.47a3.36,3.36,0,0,0,.71.18,7.71,7.71,0,0,0,1.27.12C427.49,448.53,427.94,448.45,427.94,448.49Z" style="fill:#263238"></path><path d="M427.65,448.6a1.17,1.17,0,0,1-.27-.8,1.92,1.92,0,0,1,.19-.92,1.19,1.19,0,0,1,.88-.76.48.48,0,0,1,.46.49,1.4,1.4,0,0,1-.12.56,4,4,0,0,1-.44.81c-.32.42-.59.6-.61.58s.21-.25.47-.67a4.47,4.47,0,0,0,.39-.79c.12-.28.16-.75-.15-.75s-.55.36-.7.62a2.12,2.12,0,0,0-.21.83A5.14,5.14,0,0,0,427.65,448.6Z" style="fill:#263238"></path><path d="M447.39,444.18s-.53,0-1.35.26a6.18,6.18,0,0,0-2.78,1.7,6,6,0,0,0-1.43,2.92c-.16.83-.1,1.37-.14,1.37s-.05-.13-.06-.37a4.8,4.8,0,0,1,0-1,5.68,5.68,0,0,1,4.33-4.74,5.37,5.37,0,0,1,1-.14C447.26,444.15,447.39,444.16,447.39,444.18Z" style="fill:#263238"></path><path d="M444.26,436.81a34,34,0,0,1,.76,3.76,30.6,30.6,0,0,1,.54,3.8,33.34,33.34,0,0,1-.76-3.76A32.45,32.45,0,0,1,444.26,436.81Z" style="fill:#263238"></path><path d="M439.07,449.11a10.19,10.19,0,0,1-2.65.7,10.67,10.67,0,0,1-2.73.21c0-.06,1.21-.18,2.69-.43S439.05,449.05,439.07,449.11Z" style="fill:#263238"></path><path d="M442.68,448.53c.05,0-.06.27-.14.57s-.1.55-.17.56-.14-.28,0-.62S442.63,448.49,442.68,448.53Z" style="fill:#263238"></path><path d="M443.77,446.62c.05,0,0,.25-.2.47s-.33.38-.38.34,0-.24.2-.47S443.72,446.59,443.77,446.62Z" style="fill:#263238"></path><path d="M445.6,445.54c0,.06-.25.1-.51.24s-.43.31-.48.28.06-.32.38-.48S445.61,445.48,445.6,445.54Z" style="fill:#263238"></path><path d="M446.92,445c0,.06-.06.17-.2.25s-.28.11-.32.05.06-.16.21-.24S446.89,444.9,446.92,445Z" style="fill:#263238"></path><polygon points="419.54 267.13 449.82 443.34 425.94 446.24 392.03 289.06 394.86 443.98 371.99 443.98 362.95 265.58 419.54 267.13" style="fill:#263238"></polygon><path d="M453.68,215.1l-7.91-46.32h-.11l3-10.29a1.51,1.51,0,0,0-2.22-1.5s.12-2.71-2.21-1.93c0,0-.16-2.79-1.85-1.94,0,0-.69-2.57-2.33-.83-.27.29-5.12,6.07-5.12,6.07l.63,10.85h0l.08,1.34v.05h0l2.09,35.91-7.34-7.84-13.19,13.4,16.44,13.84a12.23,12.23,0,0,0,20-10.82Z" style="fill:#eb996e"></path><path d="M440.15,144.24l-8-16.87-9,4.24,8,16.88a5,5,0,0,0,5.9,2.63l11,23.23c-2.06,1-3.56,1.94-3.46,2.16s2.15-.49,4.56-1.62,4.26-2.25,4.14-2.49-1.77.36-3.86,1.3l-11-23.23A5,5,0,0,0,440.15,144.24Z" style="fill:#e0e0e0"></path><path d="M427.92,140.05a2.68,2.68,0,0,1,2.76-.33,3.65,3.65,0,0,0,4.58-.43c.55-.37.53-.68,1.19-.56,0,0,3,5.81,3.16,7.76.24,2.9-4.67,5.49-7.06,2.62C431.17,147.45,427.92,140.05,427.92,140.05Z" style="fill:currentColor"></path><path d="M443.29,167.77a5.83,5.83,0,0,0-2.59-6.06,2,2,0,0,1-.8-.66,1.76,1.76,0,0,1,0-1.2,10,10,0,0,0,.22-3.52,4.19,4.19,0,0,0-1.79-2.94l-2.86,8.16" style="fill:#eb996e"></path><path d="M435.44,161.55a15.28,15.28,0,0,1,.66-2.35c.47-1.46,1.15-3.52,2-5.89l.09-.29.27.16a4.46,4.46,0,0,1,2,3.44,9.53,9.53,0,0,1-.14,2.58c-.13.81-.46,1.62.07,2,.25.19.6.38.9.6a5.47,5.47,0,0,1,.78.73,6,6,0,0,1,1,1.61,5.61,5.61,0,0,1,.47,2.68,2.29,2.29,0,0,1-.21,1,7.38,7.38,0,0,1,0-1,6,6,0,0,0-.6-2.52,5.81,5.81,0,0,0-1-1.46,6,6,0,0,0-.75-.66c-.27-.2-.58-.34-.92-.6a1.24,1.24,0,0,1-.41-.56,1.6,1.6,0,0,1-.07-.68c0-.43.15-.8.21-1.2a9,9,0,0,0,.12-2.44,4,4,0,0,0-1.7-3.07l.36-.13c-.84,2.37-1.59,4.4-2.14,5.83A12.79,12.79,0,0,1,435.44,161.55Z" style="fill:#d1734a"></path><path d="M447.85,161s.13.2-.08.5a1,1,0,0,1-.55.32,1,1,0,0,1-.81-.29,1.51,1.51,0,0,1-.44-.92,3.9,3.9,0,0,1,0-1.13c0-.38.07-.78.11-1.19,0-.21,0-.42.05-.64l0-.33V157s.07-.19.16-.21a.25.25,0,0,1,.21,0,.28.28,0,0,1,.14.23v0h0a15.08,15.08,0,0,1-.46,2,3.75,3.75,0,0,1-.83,1.59,1,1,0,0,1-1,.25.9.9,0,0,1-.59-.79c0-1.08.13-1.91.19-2.68a11.65,11.65,0,0,1,.33-2.42,11.61,11.61,0,0,1,0,2.44c0,.73-.1,1.69-.08,2.6,0,.21.12.34.25.37a.52.52,0,0,0,.5-.15,3.61,3.61,0,0,0,.68-1.36c.19-.65.34-1.3.46-1.9l.09,0s.08.07.12.06,0-.09,0-.05.1,0,.22,0,0,0,0,0v.14l0,.17,0,.33-.06.65c0,.42-.1.82-.16,1.2a2,2,0,0,0,.22,1.76.65.65,0,0,0,1,.08C447.84,161.18,447.81,161,447.85,161Z" style="fill:#d1734a"></path><path d="M440.05,153c0,.88.09,1.77.13,2.65a4.77,4.77,0,0,0,.21,1.36,1.53,1.53,0,0,0,.9,1,1,1,0,0,0,1.18-.44" style="fill:#eb996e"></path><path d="M442.47,157.53s0,.13-.08.29a1,1,0,0,1-.73.38,1.33,1.33,0,0,1-1.12-.48,2.39,2.39,0,0,1-.56-1.33,10.21,10.21,0,0,1,.07-3.42,12,12,0,0,1,.45,3.35c.07.88.63,1.52,1.15,1.52S442.41,157.49,442.47,157.53Z" style="fill:#d1734a"></path><path d="M444.22,158.8s-.06.11-.22.23a1.74,1.74,0,0,1-.81.31,1,1,0,0,1-.65-.19,1.17,1.17,0,0,1-.42-.64,4.91,4.91,0,0,1-.07-1.53,13.17,13.17,0,0,1,.45-3.73,14.17,14.17,0,0,1,.07,3.76,5.71,5.71,0,0,0,0,1.38.67.67,0,0,0,.61.58C443.77,159,444.19,158.71,444.22,158.8Z" style="fill:#d1734a"></path><path d="M440.56,213.54a18.17,18.17,0,0,1-2.85-7.27,35.79,35.79,0,0,1,1.43,3.64A34.54,34.54,0,0,1,440.56,213.54Z" style="fill:#d1734a"></path><path d="M384.07,131.65s14.09-19.57,16.17-17.61c1.92,1.8,3.06,21.93,3.06,21.93S389.11,137.39,384.07,131.65Z" style="fill:#455a64"></path><path d="M390.49,124.09c-.28.08-.5.78-.28,1.22a.91.91,0,0,0,1.26.33.94.94,0,0,0,.23-1.3,1,1,0,0,0-1.32-.18" style="fill:#fafafa"></path><path d="M394.41,131.93c-.27.07-.5.77-.28,1.21a.91.91,0,0,0,1.27.33.94.94,0,0,0,.22-1.3,1,1,0,0,0-1.32-.17" style="fill:#fafafa"></path><path d="M399,126.48c-.28.08-.5.78-.28,1.22a.91.91,0,0,0,1.26.33.94.94,0,0,0,.23-1.3,1,1,0,0,0-1.32-.18" style="fill:#fafafa"></path><path d="M398.94,115.57c-.27.07-.5.77-.28,1.21a.92.92,0,0,0,1.27.33,1,1,0,0,0,.22-1.3,1,1,0,0,0-1.32-.18" style="fill:#fafafa"></path><path d="M394.68,119.63c-.27.07-.5.77-.28,1.21a.92.92,0,0,0,1.27.33,1,1,0,0,0,.22-1.3,1,1,0,0,0-1.32-.18" style="fill:#fafafa"></path><path d="M389.29,131.62c-.17,0-.31.49-.17.77a.58.58,0,0,0,.8.21.59.59,0,0,0,.14-.82.62.62,0,0,0-.83-.12" style="fill:#fafafa"></path><path d="M399.39,133c-.18,0-.32.49-.18.76a.58.58,0,0,0,.8.21.59.59,0,0,0,.14-.82.61.61,0,0,0-.83-.11" style="fill:#fafafa"></path><path d="M387.9,128.41c-.17.05-.32.49-.18.77a.58.58,0,0,0,.8.21.59.59,0,0,0,.14-.82.62.62,0,0,0-.83-.12" style="fill:#fafafa"></path><path d="M394.39,126.9c-.17,0-.31.49-.17.77a.58.58,0,0,0,.8.21.6.6,0,0,0,.14-.82.62.62,0,0,0-.84-.12" style="fill:#fafafa"></path><path d="M399.4,121.64c-.17,0-.32.49-.18.77a.57.57,0,0,0,.8.2.59.59,0,0,0,.14-.82.61.61,0,0,0-.83-.11" style="fill:#fafafa"></path><path d="M416.32,211.67l1.18,27.21,4.36,31h-59s-.13-7.32.41-17.5c.75-14.3,1.47-34.73,1.47-34.73l-2.7,5.2a100.45,100.45,0,0,1-7.7-8.82l.83-.71-6.19-12,11.26-12.73a32.81,32.81,0,0,1,10.51-8c2.07-1,2.86-1,7.52-1.66,2.19-.3,3.85-.44,4.93-.58a31.78,31.78,0,0,0,19.13,0h0c.75,0,1.47.09,2.18.17l4.68,1.09A27.67,27.67,0,0,1,423.28,188l12.49,13.66L422.33,216.6Z" style="fill:currentColor"></path><path d="M364.61,226.74a29.05,29.05,0,0,0,.38-4.11c.16-2.53.4-6,.71-9.89s.67-7.36.92-9.89a27.4,27.4,0,0,0,.29-4.1,24.26,24.26,0,0,0-.76,4.05c-.37,2.52-.78,6-1.1,9.88s-.48,7.39-.53,9.93A23,23,0,0,0,364.61,226.74Z" style="fill:#263238"></path><path d="M426.55,191.57a2.07,2.07,0,0,1-.49.54,3.46,3.46,0,0,1-2,.75,9.28,9.28,0,0,1-3.29-.5,39.21,39.21,0,0,1-4-1.71,13.25,13.25,0,0,0-5-1.23,18.44,18.44,0,0,0-5.91.9,42.85,42.85,0,0,0-6.15,2.57c-2.07,1-4.2,2.2-6.47,3.19a25.54,25.54,0,0,1-7,2.06,13.72,13.72,0,0,1-6.72-.77,24.94,24.94,0,0,1-5.32-3c-1.55-1.11-3-2.19-4.29-3.09a20.16,20.16,0,0,0-3.79-2.08,10.14,10.14,0,0,0-3.16-.72,13.25,13.25,0,0,0-2.78.22,7,7,0,0,1,2.79-.42,9.87,9.87,0,0,1,3.25.65,19.61,19.61,0,0,1,3.89,2.05c1.37.88,2.79,1.95,4.34,3a24.26,24.26,0,0,0,5.24,2.93,13.25,13.25,0,0,0,6.49.71,25.22,25.22,0,0,0,6.81-2c2.25-1,4.37-2.13,6.46-3.17a40.23,40.23,0,0,1,6.23-2.57,18.17,18.17,0,0,1,6.06-.88,13.35,13.35,0,0,1,5.17,1.32,37.35,37.35,0,0,0,4,1.77,9.05,9.05,0,0,0,3.19.57A3.33,3.33,0,0,0,426,192C426.37,191.75,426.53,191.55,426.55,191.57Z" style="fill:#fff"></path><path d="M431.16,196.72a2.18,2.18,0,0,1-.28.83,4.22,4.22,0,0,1-1.82,1.72,8.27,8.27,0,0,1-3.88.83,17.34,17.34,0,0,0-5.14.44,16.84,16.84,0,0,0-5.42,3.07A62.6,62.6,0,0,1,408.8,208a16.8,16.8,0,0,1-16.2.58c-2.66-1.38-4.9-3.28-7.52-4.15a20.71,20.71,0,0,0-7.84-.8c-2.55.13-5,.39-7.28.39a24.19,24.19,0,0,1-14.55-4.9,21.12,21.12,0,0,1-1.89-1.63,5.91,5.91,0,0,1-.6-.64,6.46,6.46,0,0,1,.67.56c.43.37,1.07.91,1.94,1.55a24.87,24.87,0,0,0,8.21,3.86,23.66,23.66,0,0,0,6.22.76c2.27,0,4.68-.28,7.25-.43a20.91,20.91,0,0,1,8,.8c2.73.91,5,2.83,7.59,4.16a16.33,16.33,0,0,0,15.71-.52,65.94,65.94,0,0,0,5.81-4.32,16.75,16.75,0,0,1,5.58-3.08,17.75,17.75,0,0,1,5.24-.39,8.22,8.22,0,0,0,3.79-.71,4.26,4.26,0,0,0,1.82-1.59C431.07,197,431.13,196.71,431.16,196.72Z" style="fill:#fff"></path><path d="M416.19,219.71a2.64,2.64,0,0,1-.45.33c-.31.2-.76.49-1.35.85a47.91,47.91,0,0,1-5.19,2.78,20.88,20.88,0,0,1-8.47,2.1,15.48,15.48,0,0,1-5.12-.87,35.56,35.56,0,0,1-5.05-2.31c-1.67-.89-3.28-1.79-4.9-2.5a27,27,0,0,0-4.83-1.6,27.89,27.89,0,0,0-8.58-.62,28.85,28.85,0,0,0-5.72,1.07c-1.33.38-2,.67-2.06.62a2.61,2.61,0,0,1,.5-.24c.34-.15.83-.36,1.49-.58a26.48,26.48,0,0,1,5.76-1.24,27.38,27.38,0,0,1,13.64,2.12c1.65.72,3.28,1.62,4.94,2.51a35.94,35.94,0,0,0,5,2.28,14.88,14.88,0,0,0,5,.88,21.28,21.28,0,0,0,8.32-1.95c2.27-1,4-2,5.23-2.63l1.4-.76A2.05,2.05,0,0,1,416.19,219.71Z" style="fill:#fff"></path><path d="M417.79,241s-.75-.21-2.11-.47a14.82,14.82,0,0,0-5.84.16c-2.45.47-5.3,1.31-8.5,2.2a50.71,50.71,0,0,1-5.08,1.14,37.7,37.7,0,0,1-5.6.44c-7.73,0-14.46-2.18-19.18-3.78-2.37-.82-4.26-1.55-5.56-2.06l-1.5-.61a2.25,2.25,0,0,1-.5-.24,2.06,2.06,0,0,1,.54.14l1.53.52c1.32.45,3.23,1.13,5.61,1.9,4.74,1.51,11.44,3.61,19.06,3.61a37.86,37.86,0,0,0,5.51-.42,51.46,51.46,0,0,0,5.05-1.11c3.19-.86,6.08-1.66,8.55-2.09a14.27,14.27,0,0,1,6,0,15.84,15.84,0,0,1,1.55.45A2.06,2.06,0,0,1,417.79,241Z" style="fill:#fff"></path><path d="M418.92,256.22a2.1,2.1,0,0,1-.6,0l-.72,0c-.29,0-.62-.08-1-.13a19.06,19.06,0,0,1-5.92-2.17c-2.37-1.19-5-3-8.39-3.89a13.53,13.53,0,0,0-5.37-.3,22.72,22.72,0,0,0-5.58,1.69c-3.69,1.6-7.06,3.58-10.46,4.62a27.93,27.93,0,0,1-9.25,1.44,24,24,0,0,1-6.24-1c-.71-.21-1.26-.39-1.62-.54a3.78,3.78,0,0,1-.55-.24c0-.06.79.23,2.22.59a26.21,26.21,0,0,0,6.19.78,28.66,28.66,0,0,0,9.1-1.54c3.33-1,6.67-3,10.41-4.64a22.78,22.78,0,0,1,5.71-1.71,13.9,13.9,0,0,1,5.56.33c3.46,1,6.11,2.82,8.44,4a20.15,20.15,0,0,0,5.79,2.29l1,.18.72.07A4,4,0,0,1,418.92,256.22Z" style="fill:#fff"></path><path d="M420.39,266.27a2.3,2.3,0,0,1-.34.52,6.24,6.24,0,0,1-1.27,1.23,10.05,10.05,0,0,1-6.22,1.67c-2.8,0-5.92-1.24-9.24-2.6a28.66,28.66,0,0,0-5.33-1.72l-1.45-.26c-.48-.09-1-.09-1.48-.14a25.75,25.75,0,0,0-3.05-.06,79.47,79.47,0,0,0-11.55,1.74,61,61,0,0,1-9.52,1.34,23.83,23.83,0,0,1-6.45-.62c-.73-.21-1.29-.42-1.67-.55a2.19,2.19,0,0,1-.57-.23,2.74,2.74,0,0,1,.6.13l1.69.46a26.21,26.21,0,0,0,6.39.44,64.19,64.19,0,0,0,9.43-1.44A75,75,0,0,1,392,264.39a26.77,26.77,0,0,1,3.11.06c.51.05,1,.06,1.51.15l1.48.27a28.83,28.83,0,0,1,5.41,1.77c3.32,1.39,6.35,2.63,9.07,2.69a10.05,10.05,0,0,0,6.09-1.48A9.8,9.8,0,0,0,420.39,266.27Z" style="fill:#fff"></path><g style="opacity:0.30000000000000004"><path d="M415.24,203s-4.35,19.9-3,26.49,7,25.17,7,25.17l-2-14.44-.86-20.71Z"></path></g><path d="M415.35,202.11a11.6,11.6,0,0,0-.15,2.83c0,1.74.11,4.15.31,6.81s.46,5.05.71,6.78a11.06,11.06,0,0,0,.56,2.77,12.45,12.45,0,0,0-.1-2.82c-.15-2-.32-4.27-.51-6.78s-.35-4.79-.5-6.78A12.64,12.64,0,0,0,415.35,202.11Z" style="fill:#263238"></path><g style="opacity:0.30000000000000004"><path d="M366,203.22a30.82,30.82,0,0,1-3.64,7.08c-1.39,2.26-2.65,4.17-3.15,6.78s.92,3.95,2.82,5.81c1.52-2.93,2.81-5.32,2.81-5.32.14-3.7.67-10.34,1.29-14.35"></path></g></g><g id="freepik--Character--inject-2"><path d="M133.37,446.73l0-10-18.66-.22-.23,14.57,1.15.09c5.15.35,26.19,1.4,29.64.4C149.18,450.5,133.37,446.73,133.37,446.73Z" style="fill:currentColor"></path><g style="opacity:0.4"><path d="M121.29,451.05a6.4,6.4,0,0,0-2.33-4,6.26,6.26,0,0,0-4.34-1.41l-.07,5.21Z"></path></g><g style="opacity:0.4"><path d="M141.09,448.85s5.53,1.67,4.83,2.57-22,.64-31.39-.3l0-.42,24.85.53S140,448.88,141.09,448.85Z"></path></g><path d="M114.27,450.69l.32,0,.92.06,3.39.19c2.86.14,6.82.28,11.19.34s8.34,0,11.2,0l3.4-.09.92-.05.32,0h-1.24l-3.4,0c-2.86,0-6.82,0-11.19,0s-8.33-.18-11.19-.28l-3.4-.12-.92,0Z" style="fill:#263238"></path><path d="M139.4,451.52a7.67,7.67,0,0,1,.65-1.61,6.82,6.82,0,0,1,1.18-1.28,3,3,0,0,0-1.83,2.89Z" style="fill:#263238"></path><path d="M133.93,448.66s.32-.29.6-.71.46-.8.4-.83-.32.28-.59.7S133.88,448.62,133.93,448.66Z" style="fill:#263238"></path><path d="M132.43,447.84c0,.05.34-.15.66-.43s.56-.56.52-.6-.34.14-.67.43S132.39,447.79,132.43,447.84Z" style="fill:#263238"></path><path d="M131.5,446.07a2.19,2.19,0,0,0,1,.13,2.33,2.33,0,0,0,1-.09,2.3,2.3,0,0,0-1-.13C131.93,446,131.5,446,131.5,446.07Z" style="fill:#263238"></path><path d="M131.18,444.82a1.92,1.92,0,0,0,1.09.33,1.83,1.83,0,0,0,1.12-.14,7.1,7.1,0,0,0-1.1-.09A6.09,6.09,0,0,0,131.18,444.82Z" style="fill:#263238"></path><path d="M134.58,447a3.8,3.8,0,0,0,1.2,0,6.46,6.46,0,0,0,1.28-.26,3.64,3.64,0,0,0,.74-.29.66.66,0,0,0,.3-.37.46.46,0,0,0-.18-.48,2.18,2.18,0,0,0-2.72.61,2.3,2.3,0,0,0-.41.79.66.66,0,0,0,0,.32,3.41,3.41,0,0,1,.56-1,2.06,2.06,0,0,1,1-.65,1.87,1.87,0,0,1,1.43.11c.19.15.11.37-.1.49a3.36,3.36,0,0,1-.68.28,8.61,8.61,0,0,1-1.24.31C135,447,134.58,447,134.58,447Z" style="fill:#263238"></path><path d="M134.89,447.1a1.16,1.16,0,0,0,.15-.83,2,2,0,0,0-.31-.88,1.2,1.2,0,0,0-1-.62.47.47,0,0,0-.37.54,1.24,1.24,0,0,0,.2.54,3.84,3.84,0,0,0,.55.74,1.76,1.76,0,0,0,.68.49s-.23-.22-.56-.6a4,4,0,0,1-.49-.73c-.16-.25-.27-.71,0-.76s.6.28.78.52a1.85,1.85,0,0,1,.33.78A4,4,0,0,1,134.89,447.1Z" style="fill:#263238"></path><path d="M114.72,445.59s.53,0,1.37.06a5.87,5.87,0,0,1,4.84,4c.27.8.29,1.34.34,1.34a1.25,1.25,0,0,0,0-.38,4.62,4.62,0,0,0-.19-1,5.71,5.71,0,0,0-1.85-2.8,5.79,5.79,0,0,0-3.12-1.27,4.73,4.73,0,0,0-1,0C114.84,445.54,114.71,445.57,114.72,445.59Z" style="fill:#263238"></path><path d="M116.75,437.85a66.8,66.8,0,0,0-.2,7.66,29.92,29.92,0,0,0,.21-3.83A32.34,32.34,0,0,0,116.75,437.85Z" style="fill:#263238"></path><path d="M123.66,449.26a10.44,10.44,0,0,0,2.73.31,11,11,0,0,0,2.73-.18c0-.07-1.23,0-2.73-.05S123.67,449.2,123.66,449.26Z" style="fill:#263238"></path><path d="M120,449.21c-.05.05.09.26.22.54s.18.53.25.53.1-.29,0-.62S120.05,449.17,120,449.21Z" style="fill:#263238"></path><path d="M118.65,447.48s.07.24.27.44.38.32.42.28-.07-.24-.26-.44S118.7,447.44,118.65,447.48Z" style="fill:#263238"></path><path d="M116.69,446.67c0,.06.26.07.53.17s.47.24.52.2-.1-.3-.44-.42S116.67,446.61,116.69,446.67Z" style="fill:#263238"></path><path d="M115.29,446.28c0,.06.08.16.24.22s.3.07.32,0-.08-.16-.24-.22S115.31,446.23,115.29,446.28Z" style="fill:#263238"></path><path d="M77.16,447.84l1.5-9.85-18.43-2.92-2.35,14.38,1.13.25c5.05,1.09,25.71,5.19,29.27,4.7C92.26,453.85,77.16,447.84,77.16,447.84Z" style="fill:currentColor"></path><g style="opacity:0.4"><path d="M64.59,450.37a6.5,6.5,0,0,0-1.73-4.31,6.3,6.3,0,0,0-4.09-2l-.83,5.15Z"></path></g><g style="opacity:0.4"><path d="M84.49,451.05s5.24,2.45,4.41,3.25-21.87-2.56-31-4.85L58,449l24.51,4.12S83.41,450.92,84.49,451.05Z"></path></g><path d="M57.69,449l.31.09.9.2,3.33.68c2.81.55,6.71,1.26,11,1.95s8.24,1.24,11.08,1.59l3.38.39.91.09.33,0-.32-.06-.91-.13-3.36-.47c-2.84-.39-6.76-1-11.08-1.65s-8.21-1.38-11-1.89l-3.34-.61L58,449Z" style="fill:#263238"></path><path d="M82.44,453.45a7,7,0,0,1,.88-1.5,7.44,7.44,0,0,1,1.34-1.1,3,3,0,0,0-2.22,2.6Z" style="fill:#263238"></path><path d="M77.44,449.82s.36-.23.69-.61.57-.72.53-.77-.36.24-.7.62S77.39,449.78,77.44,449.82Z" style="fill:#263238"></path><path d="M76.07,448.8c0,.05.36-.1.72-.33s.63-.47.6-.52-.36.09-.72.33S76,448.74,76.07,448.8Z" style="fill:#263238"></path><path d="M75.41,446.91c0,.06.41.18.94.27a2.31,2.31,0,0,0,1,.06c0-.07-.41-.19-.94-.28A2.21,2.21,0,0,0,75.41,446.91Z" style="fill:#263238"></path><path d="M75.28,445.63a1.84,1.84,0,0,0,1,.48,1.87,1.87,0,0,0,1.13,0,6.25,6.25,0,0,0-1.08-.25A5.3,5.3,0,0,0,75.28,445.63Z" style="fill:#263238"></path><path d="M78.32,448.32a3.63,3.63,0,0,0,1.18.18,7.83,7.83,0,0,0,1.31-.08,4.22,4.22,0,0,0,.77-.18.64.64,0,0,0,.35-.32.5.5,0,0,0-.1-.51,2.21,2.21,0,0,0-2.79.21,2.11,2.11,0,0,0-.51.73c-.08.19-.1.31-.08.31a3.19,3.19,0,0,1,.7-.92,2.2,2.2,0,0,1,1.11-.5,1.89,1.89,0,0,1,1.4.33c.16.16.06.37-.17.47a3.5,3.5,0,0,1-.71.17,8,8,0,0,1-1.27.13C78.78,448.35,78.32,448.28,78.32,448.32Z" style="fill:#263238"></path><path d="M78.62,448.42a1.22,1.22,0,0,0,.26-.8,1.87,1.87,0,0,0-.18-.91,1.21,1.21,0,0,0-.89-.77.49.49,0,0,0-.45.49,1.36,1.36,0,0,0,.11.56,3.38,3.38,0,0,0,.45.81c.31.42.58.61.6.59s-.2-.25-.47-.68a4.17,4.17,0,0,1-.38-.79c-.12-.28-.16-.74.14-.75s.56.36.7.63a1.78,1.78,0,0,1,.21.82A4.21,4.21,0,0,1,78.62,448.42Z" style="fill:#263238"></path><path d="M58.87,444c0,.05.54,0,1.35.27A6,6,0,0,1,63,446a6,6,0,0,1,1.44,2.91c.15.84.1,1.37.14,1.38a1.31,1.31,0,0,0,.06-.38,4.84,4.84,0,0,0,0-1,5.64,5.64,0,0,0-4.33-4.75,4.46,4.46,0,0,0-1-.13A1.12,1.12,0,0,0,58.87,444Z" style="fill:#263238"></path><path d="M62,436.64a65.85,65.85,0,0,0-1.3,7.56,34,34,0,0,0,.76-3.76A33.07,33.07,0,0,0,62,436.64Z" style="fill:#263238"></path><path d="M67.19,448.94a11.31,11.31,0,0,0,2.65.69,10.47,10.47,0,0,0,2.73.22c0-.07-1.21-.19-2.69-.44S67.21,448.88,67.19,448.94Z" style="fill:#263238"></path><path d="M63.58,448.36s.06.27.14.56.11.55.17.56.14-.28,0-.62S63.63,448.32,63.58,448.36Z" style="fill:#263238"></path><path d="M62.49,446.45c-.05,0,0,.24.2.47s.33.37.38.34,0-.25-.2-.47S62.54,446.41,62.49,446.45Z" style="fill:#263238"></path><path d="M60.67,445.36c0,.07.25.1.5.24s.43.32.48.28,0-.31-.37-.48S60.66,445.3,60.67,445.36Z" style="fill:#263238"></path><path d="M59.34,444.78c0,.05.06.16.2.25s.29.1.32.05-.06-.17-.2-.25S59.37,444.72,59.34,444.78Z" style="fill:#263238"></path><polygon points="87.57 267.13 57.29 443.34 81.17 446.24 115.08 289.06 112.25 443.98 135.12 443.98 144.16 265.58 87.57 267.13" style="fill:#263238"></polygon><path d="M211.36,175.48s2-1.78-.13-2.93c0,0,1.91-2,.13-2.68,0,0,1.38-2.28-1-2.26-.4,0-7.92.47-7.92.47l-8.51,8.8-18.69,25.64-27.65-9.34L144,217.47l26.63,7a11.86,11.86,0,0,0,13.52-6l17.19-33.12,10.48-7.18A1.51,1.51,0,0,0,211.36,175.48Z" style="fill:#ffbe9d"></path><path d="M214.53,150.91l3.11-18.39-9.81-1.66-3.11,18.39a5,5,0,0,0,3.33,5.53l-4.28,25.33c-2.27-.34-4-.44-4.08-.21s2.05.83,4.67,1.28,4.78.59,4.82.33-1.66-.72-3.91-1.14L209.56,155A5,5,0,0,0,214.53,150.91Z" style="fill:#e0e0e0"></path><path d="M199.69,171l8.63-3.91a3,3,0,0,1-.23,2.68,7.07,7.07,0,0,1-1.93,2A18.25,18.25,0,0,0,204,173.5a6.07,6.07,0,0,1-.52,5.23,4,4,0,0,1-4.77,1.51" style="fill:#ffbe9d"></path><path d="M206.89,140.48a2.67,2.67,0,0,1,2.45,1.31,3.65,3.65,0,0,0,4,2.26c.66,0,.82-.25,1.29.22,0,0-.86,6.49-1.84,8.18-1.47,2.52-7,1.83-7.29-1.88C205.32,148.41,206.89,140.48,206.89,140.48Z" style="fill:currentColor"></path><path d="M200.47,170.63a13.62,13.62,0,0,1,2.15-1.14c1.38-.67,3.34-1.59,5.62-2.64l.28-.13.07.3a4.55,4.55,0,0,1-1.14,3.8,9.87,9.87,0,0,1-2,1.67c-.68.47-1.49.79-1.39,1.42,0,.31.14.7.18,1.07a5.48,5.48,0,0,1,0,1.07,6,6,0,0,1-.46,1.85,5.63,5.63,0,0,1-1.61,2.19,2.25,2.25,0,0,1-.86.51s.28-.23.72-.66a6,6,0,0,0,1.41-2.18,6.09,6.09,0,0,0,.37-1.74,5.54,5.54,0,0,0,0-1c0-.33-.15-.65-.2-1.09a1.19,1.19,0,0,1,.12-.68,1.69,1.69,0,0,1,.44-.51c.35-.27.68-.45,1-.68a9.06,9.06,0,0,0,1.86-1.59,4.06,4.06,0,0,0,1-3.35l.35.17c-2.3,1-4.29,1.88-5.7,2.47A14.28,14.28,0,0,1,200.47,170.63Z" style="fill:#eb996e"></path><path d="M209.42,179.23s-.06.25-.45.28a.81.81,0,0,1-.63-.22,1,1,0,0,1-.28-.86,1.64,1.64,0,0,1,.47-1,6.73,6.73,0,0,1,.91-.75l2.16-1.58.29.41a9.75,9.75,0,0,1-1.76,1.05,8.55,8.55,0,0,1-1.72.6,1.28,1.28,0,0,1-1-.22.84.84,0,0,1-.24-1,2,2,0,0,1,.57-.63l.56-.46,1-.8a11.76,11.76,0,0,1,2.08-1.5,13.33,13.33,0,0,1-1.84,1.78l-1,.85c-.32.31-.81.63-1,1s.24.64.72.52a7.75,7.75,0,0,0,1.58-.57,10.14,10.14,0,0,0,1.68-1l.3.41-2.2,1.53c-.68.44-1.24.86-1.31,1.4a.69.69,0,0,0,.59.87C209.27,179.37,209.4,179.2,209.42,179.23Z" style="fill:#eb996e"></path><path d="M208.51,175.09s-.13,0-.32,0a1.53,1.53,0,0,1-.78-.37,1,1,0,0,1-.32-.6,1.25,1.25,0,0,1,.18-.75,4.81,4.81,0,0,1,1.06-1.1,13.62,13.62,0,0,1,3-2.25,14,14,0,0,1-2.67,2.65,5.34,5.34,0,0,0-1,1,.67.67,0,0,0,0,.85C208.07,174.89,208.55,175,208.51,175.09Z" style="fill:#eb996e"></path><path d="M208.21,173s-.06.11-.26.14a1,1,0,0,1-.78-.26,1.34,1.34,0,0,1-.42-1.15,2.45,2.45,0,0,1,.58-1.32,10.5,10.5,0,0,1,2.52-2.31,11.94,11.94,0,0,1-2.11,2.64,1.49,1.49,0,0,0-.31,1.88C207.79,173,208.2,172.88,208.21,173Z" style="fill:#eb996e"></path><path d="M67.2,171.05,70.11,129l.08,0,.13-.7c.36-.67.82-1.45,1.38-2.33a8,8,0,0,1,1.84-2,15.47,15.47,0,0,1,2.57-1.68l.21-.07a1,1,0,0,0,.39-.2,1,1,0,0,0-.1-1.84c-.83-.27-3.23.42-5.53,2.31l-.11.09c-2.38,1.85-1.28-2.72-.91-4.39s1.29-6.89.86-7.39c-.62-.75-1.56-.2-1.89,1.47s-1.69,6.64-2.57,6.43,1.15-8.7,1.15-8.7.73-1.75-.27-2c-1.82-.52-3.29,8.64-3.41,9.52-.08.61-1,.4-.92-.24s2.21-8.42.37-9c-1.41-.42-1.28,7.52-2.61,8.91s.52-6-.85-6.38c-.5-.13-.9-.15-1.35,4.26a96.5,96.5,0,0,0,0,10.4h0l0,1h0s-12.65,49.26-9.58,56S87.1,204.12,87.1,204.12l7.47-21.57Z" style="fill:#ffbe9d"></path><path d="M202.15,117.45a37.54,37.54,0,0,1-6.16-1.1,37.11,37.11,0,0,1-6.05-1.62,38.28,38.28,0,0,1,6.16,1.11A38.14,38.14,0,0,1,202.15,117.45Z" style="fill:#263238"></path><path d="M202.53,125.12a12.38,12.38,0,0,1-3.14,1.69A12.18,12.18,0,0,1,196,128a12.38,12.38,0,0,1,3.14-1.69A12.59,12.59,0,0,1,202.53,125.12Z" style="fill:#263238"></path><path d="M116.56,130.41a30.69,30.69,0,0,0-8.28-.3,10.36,10.36,0,0,0-7.09,3.9c-1,1.4-1.52,3.13-2.75,4.32-1.53,1.48-4.14,2.19-4.5,4.29s2.06,3.9,2.13,6.08a7.14,7.14,0,0,1-1.3,3.47c-.6,1.1-1.09,2.44-.54,3.57.44.91,1.45,1.41,2,2.26.85,1.3.45,3,.82,4.55.55,2.21,2.79,3.68,5,4a17.53,17.53,0,0,0,6.77-.68" style="fill:#263238"></path><path d="M118.73,131.16a13.55,13.55,0,0,1,5.58-.83,4.9,4.9,0,0,1,4.21,3.29c.28,1.14,0,2.47.78,3.36s2.48.93,3.42,1.84c2,2-1.07,6,.94,8,.54.53,1.33.73,1.91,1.22a2.89,2.89,0,0,1,.47,3.54,4.41,4.41,0,0,1-3.18,2,9.77,9.77,0,0,1-3.83-.35A17.6,17.6,0,0,1,118.44,146c-2.33-3.69-2.65-8.77-.17-12.37" style="fill:#263238"></path><path d="M102.83,147.37l3.19,37,18.53-1.48c.11-2.16,0-10.53,0-10.53s7.71-1.2,7.8-8.91c.05-3.74-.49-11.48-1.05-18.45a14.26,14.26,0,0,0-15-13.09h0A14.26,14.26,0,0,0,102.83,147.37Z" style="fill:#ffbe9d"></path><path d="M124.52,172.24a18,18,0,0,1-10-2.86s2.38,5.55,10.07,4.85Z" style="fill:#eb996e"></path><path d="M129.37,150.12a1.1,1.1,0,0,1-1.06,1.11,1.06,1.06,0,0,1-1.13-1,1.1,1.1,0,0,1,1.06-1.11A1.05,1.05,0,0,1,129.37,150.12Z" style="fill:#263238"></path><path d="M129.61,148.88c-.13.14-1-.48-2.15-.47s-2.06.59-2.18.45.07-.33.45-.6a3,3,0,0,1,1.75-.54,2.91,2.91,0,0,1,1.71.55C129.56,148.55,129.68,148.81,129.61,148.88Z" style="fill:#263238"></path><path d="M117.77,150.39a1.1,1.1,0,0,1-1.06,1.11,1,1,0,0,1-1.13-1,1.1,1.1,0,0,1,1.06-1.11A1.05,1.05,0,0,1,117.77,150.39Z" style="fill:#263238"></path><path d="M118,149.1c-.14.14-1-.48-2.16-.47s-2,.59-2.18.45.08-.32.46-.6a3,3,0,0,1,3.46,0C117.91,148.77,118,149,118,149.1Z" style="fill:#263238"></path><path d="M122.27,157.88a8.31,8.31,0,0,1,1.92-.34c.3,0,.59-.1.64-.3a1.52,1.52,0,0,0-.2-.9l-.89-2.3a37.64,37.64,0,0,1-2-6,40.91,40.91,0,0,1,2.49,5.85c.29.81.58,1.58.85,2.32a1.74,1.74,0,0,1,.16,1.19.75.75,0,0,1-.5.44,2,2,0,0,1-.51.07A7.71,7.71,0,0,1,122.27,157.88Z" style="fill:#263238"></path><path d="M118.78,158.7c.19,0,.2,1.28,1.3,2.19s2.47.77,2.48,1-.31.25-.89.27a3.23,3.23,0,0,1-2.07-.73,2.74,2.74,0,0,1-1-1.83C118.55,159,118.69,158.7,118.78,158.7Z" style="fill:#263238"></path><path d="M118.27,144.46c-.11.32-1.29.17-2.68.33s-2.52.55-2.7.26.11-.44.56-.75a4.51,4.51,0,0,1,2-.7,4.63,4.63,0,0,1,2.11.24C118.07,144.05,118.33,144.3,118.27,144.46Z" style="fill:#263238"></path><path d="M129.18,145.45c-.21.27-1,0-2,0s-1.81.18-2-.1,0-.41.41-.67a2.91,2.91,0,0,1,3.23.1C129.16,145,129.27,145.32,129.18,145.45Z" style="fill:#263238"></path><path d="M105.43,153.15c-.14-.06-5.3-1.57-5.13,3.7s5.42,4,5.43,3.87S105.43,153.15,105.43,153.15Z" style="fill:#ffbe9d"></path><path d="M104.05,158.77s-.09.07-.24.14a.9.9,0,0,1-.68,0,2.24,2.24,0,0,1-1.12-2,3.18,3.18,0,0,1,.21-1.33,1.06,1.06,0,0,1,.67-.72.46.46,0,0,1,.55.23.93.93,0,0,1,.07.26s.12-.09.07-.31a.62.62,0,0,0-.22-.32.68.68,0,0,0-.51-.12,1.31,1.31,0,0,0-.94.85,3.21,3.21,0,0,0-.27,1.48,2.37,2.37,0,0,0,1.41,2.23,1,1,0,0,0,.84-.17C104.05,158.9,104.07,158.78,104.05,158.77Z" style="fill:#eb996e"></path><path d="M121.2,131.73a8.25,8.25,0,0,0-2.44-1.8,7.59,7.59,0,0,0-3.4-.65,19.1,19.1,0,0,0-6.91,1.35,9.15,9.15,0,0,0-5,4.79c-.68,1.72-.71,3.74-1.88,5.16-.95,1.16-2.57,1.82-3,3.24-.83,2.52,2.65,5,1.86,7.49a3.93,3.93,0,0,0-.39,1.35,1.47,1.47,0,0,0,1.06,1.18,4.25,4.25,0,0,0,1.67.07c2-.18,4.34-.68,5-2.55s-.63-3.82.34-5.36c.85-1.33,2.53-1,3.81-1.92a7.75,7.75,0,0,0,1.45-1.35,11,11,0,0,0,2.58-.74,4.5,4.5,0,0,0,2.38-2.26c.48-1.14.33-2.54,1.12-3.49s2.51-1.3,2.82-2.55A1.82,1.82,0,0,0,121.2,131.73Z" style="fill:#263238"></path><path d="M118.49,129.27S101.76,111.92,100,114.15c-1.65,2.06.09,22.14.09,22.14S114.32,135.68,118.49,129.27Z" style="fill:currentColor"></path><path d="M111.06,122.71c.28,0,.61.7.45,1.17a.91.91,0,0,1-1.2.5.94.94,0,0,1-.41-1.25,1,1,0,0,1,1.28-.36" style="fill:#fafafa"></path><path d="M108.29,131c.28,0,.61.69.45,1.16a.91.91,0,0,1-1.2.51,1,1,0,0,1-.41-1.26,1,1,0,0,1,1.28-.36" style="fill:#fafafa"></path><path d="M103,126.29c.28,0,.61.69.45,1.16a.92.92,0,0,1-1.21.51,1,1,0,0,1-.4-1.26,1,1,0,0,1,1.28-.36" style="fill:#fafafa"></path><path d="M101.48,115.48c.28,0,.61.69.45,1.16a.92.92,0,0,1-1.21.51,1,1,0,0,1-.4-1.26,1,1,0,0,1,1.28-.36" style="fill:#fafafa"></path><path d="M106.28,118.89c.28,0,.6.69.44,1.16a.91.91,0,0,1-1.2.51,1,1,0,0,1-.41-1.26,1,1,0,0,1,1.28-.36" style="fill:#fafafa"></path><path d="M113.32,130c.17,0,.38.44.28.74a.58.58,0,0,1-.76.32.59.59,0,0,1-.26-.79.61.61,0,0,1,.81-.23" style="fill:#fafafa"></path><path d="M103.52,132.75c.17,0,.38.44.28.74a.58.58,0,0,1-.76.32.59.59,0,0,1-.26-.79.61.61,0,0,1,.81-.23" style="fill:#fafafa"></path><path d="M114.24,126.62c.18,0,.38.44.28.73a.56.56,0,0,1-.76.32.59.59,0,0,1-.25-.79.62.62,0,0,1,.81-.23" style="fill:#fafafa"></path><path d="M107.6,126.05c.17,0,.38.44.28.73a.57.57,0,0,1-.76.32.59.59,0,0,1-.26-.79.62.62,0,0,1,.81-.23" style="fill:#fafafa"></path><path d="M101.89,121.55c.18,0,.38.44.29.74a.56.56,0,1,1-1-.47.6.6,0,0,1,.81-.23" style="fill:#fafafa"></path><path d="M124.85,172.39a1.7,1.7,0,0,1-.47.15,8.87,8.87,0,0,1-1.4.25,11.44,11.44,0,0,1-5.15-.66c-4.23-1.45-8.84-6.1-11.41-12.32a32,32,0,0,1-2.08-9.2,65.54,65.54,0,0,1-.22-7.69c.06-2.19.17-3.95.25-5.17,0-.56.08-1,.11-1.4a2,2,0,0,1,.08-.49,1.56,1.56,0,0,1,0,.49c0,.38,0,.85,0,1.41,0,1.22-.07,3-.08,5.16a71.81,71.81,0,0,0,.33,7.64,32.48,32.48,0,0,0,2.08,9.05c2.52,6.1,7,10.69,11.06,12.18a12.15,12.15,0,0,0,5,.8C124.18,172.52,124.84,172.33,124.85,172.39Z" style="fill:#455a64"></path><path d="M91.11,213l-5.54,58.25h59.78c.15-6.19-2.62-52.23-2.62-52.23l12,2.66,11.66-25-19.11-9.43c-8.72-4.25-10.59-5.9-18-6.92-2.19-.3-3.85-.44-4.93-.58a31.7,31.7,0,0,1-19.13,0h0c-.75,0-1.47.09-2.18.17l-16.35-3.72L73.5,172.73l-8.38,26.52Z" style="fill:currentColor"></path><path d="M92.2,202.14a11.32,11.32,0,0,1,0,2.84c-.08,1.74-.28,4.14-.59,6.79s-.67,5-1,6.75a11.22,11.22,0,0,1-.68,2.75,13.54,13.54,0,0,1,.22-2.82c.23-2,.5-4.26.8-6.76s.55-4.76.77-6.75A13.3,13.3,0,0,1,92.2,202.14Z" style="fill:#263238"></path><path d="M142.82,228a29,29,0,0,1-.38-4.1c-.16-2.54-.39-6-.71-9.9s-.66-7.35-.91-9.88a28.65,28.65,0,0,1-.3-4.11,23.31,23.31,0,0,1,.76,4c.37,2.52.79,6,1.11,9.88s.47,7.39.52,9.94A22.85,22.85,0,0,1,142.82,228Z" style="fill:#263238"></path><path d="M141.71,226.19a5,5,0,0,1-.85-.43l-2.34-1.35-8.35-4.93h.22l-14.51,8h0l-.14.08-.12-.08-12.6-8h.23l-9.6,5.06L90.92,226a7.19,7.19,0,0,1-1,.45,5.31,5.31,0,0,1,.91-.57l2.65-1.5L103,219.1l.11-.06.11.07c3.69,2.31,8,5,12.63,7.91h-.24l14.55-7.89.11-.06.1.06,8.26,5.09,2.28,1.44A5.3,5.3,0,0,1,141.71,226.19Z" style="fill:#fff"></path><path d="M141.06,232.39a5.78,5.78,0,0,1-.85-.44l-2.35-1.34-8.34-4.93h.21l-14.5,8h0l-.14.07-.13-.08-12.59-8h.23L93,230.76l-2.7,1.39a6.36,6.36,0,0,1-1,.45,7,7,0,0,1,.92-.58l2.65-1.49,9.51-5.23.12-.07.11.07,12.63,7.91H115l14.55-7.89.11-.06.1.07,8.25,5.08,2.29,1.45A5.3,5.3,0,0,1,141.06,232.39Z" style="fill:#fff"></path><path d="M146.43,219.82a2.33,2.33,0,0,1-.21-.68l-.45-1.93c-.37-1.77-.87-4.12-1.45-6.89l0-.18.17-.07,8-3.58,4.34-1.92-.15.26c-.45-3.92-.83-7.33-1.07-9.75q-.17-1.71-.27-2.85a5,5,0,0,1,0-1,4.34,4.34,0,0,1,.21,1c.11.75.25,1.7.42,2.83.33,2.41.77,5.81,1.25,9.73l0,.18-.18.08L152.61,207l-8,3.52.14-.26c.51,2.79.94,5.15,1.27,6.93.13.78.23,1.42.32,2A2.82,2.82,0,0,1,146.43,219.82Z" style="fill:#fff"></path><path d="M81,174.8a3,3,0,0,1,.22.67l.51,1.92c.43,1.77,1,4.1,1.66,6.84l0,.18-.17.08-7.44,3.62-4.68,2.24.14-.24c.26,3.71.47,6.9.6,9.18,0,1.06.09,2,.13,2.66a5.08,5.08,0,0,1,0,1,3.76,3.76,0,0,1-.16-.95c-.07-.71-.16-1.59-.27-2.65-.22-2.27-.49-5.46-.79-9.16V190l.15-.08,4.67-2.26,7.47-3.56-.13.25c-.59-2.76-1.09-5.1-1.48-6.88-.15-.77-.27-1.41-.38-1.95A2.17,2.17,0,0,1,81,174.8Z" style="fill:#fff"></path></g><g id="freepik--Plant--inject-2"><path d="M219.58,418.19c-1.56,2.22-4.58,1.48-6,.13s-2-3.32-2.53-5.2c-1.23-4.27-2.47-8.76-1.56-13.12a7.35,7.35,0,0,1,1.68-3.57,3.79,3.79,0,0,1,3.61-1.2c1.52.4,2.47,1.89,3.15,3.31a31.39,31.39,0,0,1,3.1,13.43,10.94,10.94,0,0,1-1.47,6.22" style="fill:currentColor"></path><g style="opacity:0.2"><path d="M219.58,418.19c-1.56,2.22-4.58,1.48-6,.13s-2-3.32-2.53-5.2c-1.23-4.27-2.47-8.76-1.56-13.12a7.35,7.35,0,0,1,1.68-3.57,3.79,3.79,0,0,1,3.61-1.2c1.52.4,2.47,1.89,3.15,3.31a31.39,31.39,0,0,1,3.1,13.43,10.94,10.94,0,0,1-1.47,6.22"></path></g><path d="M213.75,429.43a13.51,13.51,0,0,0-8.35-1,6.93,6.93,0,0,0-4.13,1.85c-1,1.14-1.23,3.14-.05,4.12a4,4,0,0,0,2.61.66c2.21,0,4.66-.42,6.42.91,1,.76,1.68,2,2.88,2.4a2.86,2.86,0,0,0,3.14-1.4,4.89,4.89,0,0,0,.35-3.59A5.49,5.49,0,0,0,213.75,429.43Z" style="fill:currentColor"></path><path d="M223.85,421.37a8.07,8.07,0,0,1-1.26-7.43,14.35,14.35,0,0,1,4.4-6.39,23.75,23.75,0,0,1,7.89-4.65,8.32,8.32,0,0,1,3.54-.6,3.71,3.71,0,0,1,3,1.8c.83,1.58,0,3.5-.83,5.07a60.12,60.12,0,0,1-5.18,7.88c-1.55,2-3.33,4-5.69,4.88s-4.83.8-6.08-.84" style="fill:currentColor"></path><path d="M219.68,451.68a5.41,5.41,0,0,1-.14-1c-.07-.74-.16-1.66-.27-2.75a35.56,35.56,0,0,0-.62-4,31.66,31.66,0,0,0-1.46-4.77,17.63,17.63,0,0,0-2.44-4.31,9,9,0,0,0-3.07-2.56,8.57,8.57,0,0,0-2.6-.81,4.48,4.48,0,0,1-1-.14,4.24,4.24,0,0,1,1,0,7.82,7.82,0,0,1,2.7.71,9,9,0,0,1,3.25,2.58,17.1,17.1,0,0,1,2.52,4.39A29.31,29.31,0,0,1,219,443.8a33,33,0,0,1,.54,4.09c.08,1.17.12,2.11.13,2.77A4.29,4.29,0,0,1,219.68,451.68Z" style="fill:#263238"></path><path d="M219.49,449.56a2.42,2.42,0,0,1,0-.47v-1.34c0-1.16,0-2.84-.06-4.92-.1-4.15-.46-9.88-1.15-16.18s-1.62-12-2.46-16c-.2-1-.4-1.93-.59-2.74s-.33-1.5-.48-2.06-.23-1-.31-1.3a2,2,0,0,1-.08-.46,2.36,2.36,0,0,1,.16.44c.1.34.23.76.39,1.28s.35,1.25.55,2.05.43,1.71.65,2.73a149.36,149.36,0,0,1,2.58,16.05c.69,6.32,1,12.07,1,16.23,0,2.08,0,3.76-.07,4.93,0,.54-.06,1-.08,1.33A1.47,1.47,0,0,1,219.49,449.56Z" style="fill:#263238"></path><path d="M219.47,438.47a8.38,8.38,0,0,1,0-1.4c.06-.89.18-2.19.43-3.78a53.74,53.74,0,0,1,3.54-12.06,34.94,34.94,0,0,1,6.78-10.55,16.1,16.1,0,0,1,1.6-1.48c.24-.21.46-.42.68-.58l.62-.43a11.28,11.28,0,0,1,1.17-.75,43.31,43.31,0,0,0-3.86,3.44,36.64,36.64,0,0,0-6.6,10.52,53.28,53.28,0,0,0-2.33,6.47c-.6,2-1,3.89-1.31,5.47s-.45,2.86-.55,3.75A12.6,12.6,0,0,1,219.47,438.47Z" style="fill:#263238"></path></g><g id="freepik--Confetti--inject-2"><path d="M152.89,121.71l2.77,1.71s1.5-3.67,4.1-3.9,3.06,3,6.92-2.77l-1.12-2.53s-1.45,3.7-3.59,3.45S157,114.88,152.89,121.71Z" style="fill:currentColor"></path><path d="M39.88,266.93l1.76-2.75S38,262.62,37.81,260s3-3-2.65-7l-2.54,1.07s3.67,1.52,3.38,3.65S33.12,262.74,39.88,266.93Z" style="fill:currentColor"></path><path d="M386.17,53.57l3.26,0s-.61-3.92,1.49-5.46,4.16,1,4.5-5.94l-2.25-1.59s.66,3.92-1.3,4.81S386.13,45.62,386.17,53.57Z" style="fill:currentColor"></path><path d="M180.39,285.55l-3.16-.79s-.32,3.95-2.72,5-3.83-1.9-5.76,4.74l1.83,2.06s.26-4,2.38-4.37S178.58,293.29,180.39,285.55Z" style="fill:currentColor"></path><path d="M207,231.37l-.66-2.19s-2.49,1.23-4,.15-.23-3-4.93-1.76l-.58,1.85s2.48-1.27,3.49-.15S201.66,233.07,207,231.37Z" style="fill:#455a64"></path><path d="M69.17,66.49l-1.39-.77s-.65,1.82-1.9,2-1.57-1.37-3.31,1.51l.6,1.21s.62-1.84,1.67-1.77S67.35,69.92,69.17,66.49Z" style="fill:#455a64"></path><path d="M242.79,42.46l.72-2.6s-3.27-.35-4-2.36,1.65-3.13-3.81-4.86L233.9,34.1s3.28.3,3.57,2.07S236.41,40.8,242.79,42.46Z" style="fill:#455a64"></path><path d="M457.1,310.81l.72-2.6s-3.26-.35-4.05-2.36,1.65-3.13-3.81-4.86l-1.75,1.47s3.28.29,3.58,2.06S450.72,309.16,457.1,310.81Z" style="fill:#455a64"></path><path d="M472,191.08l-1.57.25s.61,1.83-.27,2.75-2.08-.13-1.68,3.22l1.21.58s-.64-1.83.24-2.42S472.67,194.9,472,191.08Z" style="fill:#455a64"></path><path d="M305.61,100.51l0-1.59s-1.91.27-2.65-.77.51-2-2.86-2.25L299.35,97s1.92-.29,2.33.67S301.73,100.46,305.61,100.51Z" style="fill:#e0e0e0"></path><path d="M136.74,43.67l-1.28,2.63s3.41,1,3.86,3.31-2.37,3,3.1,5.92l2.16-1.22s-3.44-1-3.41-2.89S143.21,46.68,136.74,43.67Z" style="fill:currentColor"></path><path d="M461.07,247.06l-2-2.58s-2.69,2.91-5.21,2.21-1.82-3.85-7.45.16l.16,2.76s2.66-3,4.57-2S454.86,252,461.07,247.06Z" style="fill:currentColor"></path></g></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/heart.svg b/ruoyi-plus-soybean/src/assets/svg-icon/heart.svg
new file mode 100755
index 0000000..56e59b4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/heart.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/login-background.svg b/ruoyi-plus-soybean/src/assets/svg-icon/login-background.svg
new file mode 100755
index 0000000..89fda3e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/login-background.svg
@@ -0,0 +1 @@
+<svg height="8887" node-id="1" sillyvg="true" template-height="8887" template-width="11927" version="1.1" viewBox="0 0 11927 8887" width="11927" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs node-id="176"></defs><path d="M 11926.20 7809.20 L 0.00 7809.20 L 0.00 7815.20 L 11926.20 7815.20 L 11926.20 7809.20 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="178" stroke="none" target-height="6" target-width="11926.2" target-x="0" target-y="7809.2"></path><path d="M 2977.20 8170.10 L 2187.30 8170.10 L 2187.30 8176.10 L 2977.20 8176.10 L 2977.20 8170.10 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="180" stroke="none" target-height="6" target-width="789.8999" target-x="2187.3" target-y="8170.1"></path><path d="M 1937.30 8170.10 L 1479.60 8170.10 L 1479.60 8176.10 L 1937.30 8176.10 L 1937.30 8170.10 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="182" stroke="none" target-height="6" target-width="457.70007" target-x="1479.6" target-y="8170.1"></path><path d="M 5022.10 8257.90 L 4564.40 8257.90 L 4564.40 8263.90 L 5022.10 8263.90 L 5022.10 8257.90 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="184" stroke="none" target-height="6" target-width="457.7002" target-x="4564.4" target-y="8257.9"></path><path d="M 7408.80 8391.00 L 6378.60 8391.00 L 6378.60 8397.00 L 7408.80 8397.00 L 7408.80 8391.00 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="186" stroke="none" target-height="6" target-width="1030.1997" target-x="6378.6" target-y="8391"></path><path d="M 6166.30 8391.00 L 6015.30 8391.00 L 6015.30 8397.00 L 6166.30 8397.00 L 6166.30 8391.00 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="188" stroke="none" target-height="6" target-width="151" target-x="6015.3" target-y="8391"></path><path d="M 10813.40 8257.90 L 8579.00 8257.90 L 8579.00 8263.90 L 10813.40 8263.90 L 10813.40 8257.90 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="190" stroke="none" target-height="6" target-width="2234.4004" target-x="8579" target-y="8257.9"></path><path d="M 5653.00 6745.40 L 1047.40 6745.40 L 1038.30 6745.10 L 1029.40 6744.20 L 1020.70 6742.80 L 1012.10 6740.70 L 1003.60 6738.10 L 995.30 6735.00 L 987.30 6731.30 L 979.40 6727.20 L 971.90 6722.50 L 964.70 6717.30 L 957.80 6711.60 L 951.20 6705.50 L 945.00 6698.90 L 939.40 6691.90 L 934.20 6684.70 L 929.60 6677.20 L 925.40 6669.30 L 921.70 6661.30 L 918.60 6653.00 L 916.10 6644.50 L 914.00 6635.90 L 912.60 6627.20 L 911.70 6618.30 L 911.40 6609.20 L 911.40 135.00 L 911.80 126.00 L 912.70 117.20 L 914.20 108.50 L 916.30 99.90 L 918.90 91.50 L 922.10 83.30 L 925.70 75.30 L 929.90 67.60 L 934.60 60.10 L 939.80 52.90 L 945.50 46.10 L 951.60 39.60 L 958.20 33.40 L 965.10 27.80 L 972.30 22.70 L 979.80 18.10 L 987.60 13.90 L 995.60 10.30 L 1003.80 7.20 L 1012.20 4.70 L 1020.80 2.60 L 1029.50 1.20 L 1038.40 0.30 L 1047.40 0.00 L 5653.00 0.00 L 5662.00 0.30 L 5670.90 1.20 L 5679.70 2.60 L 5688.30 4.70 L 5696.80 7.30 L 5705.10 10.40 L 5713.20 14.00 L 5721.00 18.20 L 5728.60 22.90 L 5735.80 28.00 L 5742.70 33.70 L 5749.30 39.90 L 5755.50 46.50 L 5761.10 53.40 L 5766.30 60.60 L 5771.00 68.20 L 5775.20 76.00 L 5778.80 84.10 L 5781.90 92.30 L 5784.50 100.90 L 5786.60 109.50 L 5788.00 118.30 L 5788.90 127.20 L 5789.20 136.20 L 5789.20 6609.20 L 5788.90 6618.30 L 5786.60 6635.90 L 5784.50 6644.60 L 5778.80 6661.40 L 5775.20 6669.40 L 5771.00 6677.20 L 5766.30 6684.80 L 5761.10 6692.00 L 5755.50 6698.90 L 5749.30 6705.50 L 5742.70 6711.70 L 5735.80 6717.40 L 5728.60 6722.60 L 5721.00 6727.20 L 5713.20 6731.40 L 5705.10 6735.10 L 5688.30 6740.80 L 5679.70 6742.80 L 5670.90 6744.20 L 5662.00 6745.10 L 5653.00 6745.40 Z M 1047.40 4.80 L 1038.70 5.10 L 1030.20 5.90 L 1021.90 7.30 L 1013.60 9.30 L 1005.50 11.80 L 997.60 14.80 L 989.90 18.20 L 982.40 22.30 L 975.20 26.70 L 968.30 31.70 L 961.70 37.10 L 955.40 43.00 L 949.50 49.30 L 944.10 55.90 L 939.20 62.80 L 934.70 70.00 L 930.70 77.50 L 927.30 85.20 L 924.30 93.10 L 921.80 101.20 L 919.90 109.50 L 918.50 117.90 L 917.70 126.40 L 917.40 135.00 L 917.40 6609.20 L 917.70 6617.90 L 918.50 6626.40 L 919.90 6634.70 L 921.80 6643.00 L 924.30 6651.10 L 927.30 6659.00 L 930.70 6666.70 L 934.70 6674.20 L 939.20 6681.40 L 944.10 6688.30 L 949.50 6694.90 L 955.40 6701.20 L 961.70 6707.20 L 968.30 6712.60 L 975.20 6717.50 L 982.40 6722.00 L 989.90 6726.00 L 997.60 6729.50 L 1005.50 6732.50 L 1013.60 6735.00 L 1021.90 6736.90 L 1030.20 6738.30 L 1038.70 6739.20 L 1047.40 6739.50 L 5653.00 6739.50 L 5661.60 6739.20 L 5670.10 6738.30 L 5678.50 6736.90 L 5686.80 6735.00 L 5694.90 6732.50 L 5702.80 6729.50 L 5710.50 6726.00 L 5718.00 6722.00 L 5725.20 6717.50 L 5738.70 6707.20 L 5745.00 6701.30 L 5750.90 6694.90 L 5756.30 6688.30 L 5761.30 6681.40 L 5765.80 6674.20 L 5769.80 6666.70 L 5773.30 6659.00 L 5776.20 6651.10 L 5778.70 6643.00 L 5780.70 6634.70 L 5782.10 6626.40 L 5782.90 6617.90 L 5783.20 6609.20 L 5783.20 135.00 L 5782.90 126.40 L 5782.10 117.90 L 5780.70 109.50 L 5778.70 101.20 L 5776.20 93.10 L 5773.30 85.20 L 5769.80 77.50 L 5765.80 70.00 L 5761.30 62.80 L 5756.30 55.90 L 5750.90 49.30 L 5745.00 43.00 L 5738.70 37.10 L 5732.10 31.70 L 5725.20 26.70 L 5718.00 22.20 L 5710.50 18.20 L 5702.80 14.70 L 5694.90 11.80 L 5686.80 9.30 L 5678.50 7.30 L 5670.10 5.90 L 5661.60 5.10 L 5653.00 4.80 L 1047.40 4.80 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="192" stroke="none" target-height="6745.4" target-width="4877.8003" target-x="911.4" target-y="0"></path><path d="M 10812.50 6745.40 L 6206.60 6745.40 L 6197.60 6745.10 L 6188.70 6744.20 L 6179.90 6742.80 L 6171.30 6740.70 L 6162.80 6738.10 L 6154.50 6735.00 L 6146.50 6731.40 L 6138.60 6727.20 L 6123.90 6717.30 L 6117.00 6711.60 L 6110.40 6705.50 L 6104.20 6698.90 L 6098.50 6692.00 L 6093.40 6684.70 L 6088.70 6677.20 L 6084.50 6669.40 L 6080.80 6661.30 L 6077.70 6653.10 L 6075.10 6644.60 L 6073.10 6635.90 L 6071.60 6627.20 L 6070.70 6618.30 L 6070.40 6609.20 L 6070.40 135.00 L 6070.80 126.00 L 6071.80 117.20 L 6073.30 108.50 L 6075.40 99.90 L 6078.00 91.50 L 6081.20 83.30 L 6084.90 75.30 L 6089.10 67.50 L 6093.80 60.10 L 6099.00 52.90 L 6104.60 46.10 L 6110.80 39.50 L 6117.40 33.40 L 6124.30 27.80 L 6131.50 22.70 L 6139.00 18.10 L 6146.80 13.90 L 6154.80 10.30 L 6163.00 7.20 L 6171.50 4.70 L 6180.10 2.60 L 6188.80 1.20 L 6197.60 0.30 L 6206.60 0.00 L 10812.50 0.00 L 10821.50 0.30 L 10830.30 1.20 L 10839.00 2.70 L 10847.60 4.70 L 10856.00 7.30 L 10864.20 10.40 L 10872.20 14.00 L 10880.00 18.20 L 10887.50 22.80 L 10894.70 27.90 L 10901.50 33.50 L 10908.10 39.60 L 10914.20 46.20 L 10919.90 53.00 L 10925.00 60.20 L 10929.70 67.60 L 10933.90 75.40 L 10937.60 83.40 L 10940.70 91.50 L 10943.30 100.00 L 10945.40 108.50 L 10946.90 117.20 L 10947.80 126.00 L 10948.20 135.00 L 10948.20 6609.20 L 10947.90 6618.30 L 10945.60 6635.90 L 10943.60 6644.50 L 10937.90 6661.20 L 10930.10 6677.10 L 10925.40 6684.60 L 10914.60 6698.80 L 10908.50 6705.40 L 10901.90 6711.50 L 10895.00 6717.20 L 10887.80 6722.40 L 10880.30 6727.10 L 10872.50 6731.30 L 10864.40 6734.90 L 10856.20 6738.10 L 10847.70 6740.70 L 10839.10 6742.70 L 10830.40 6744.20 L 10821.50 6745.10 L 10812.50 6745.40 Z M 6206.60 4.80 L 6198.00 5.10 L 6189.50 5.90 L 6181.10 7.30 L 6172.80 9.30 L 6164.70 11.80 L 6156.80 14.70 L 6149.10 18.20 L 6141.60 22.20 L 6134.40 26.70 L 6127.50 31.70 L 6120.90 37.10 L 6114.60 43.00 L 6108.70 49.30 L 6103.30 55.90 L 6098.30 62.80 L 6093.80 70.00 L 6089.80 77.50 L 6086.40 85.20 L 6083.40 93.10 L 6080.90 101.20 L 6078.90 109.50 L 6077.50 117.90 L 6076.70 126.40 L 6076.40 135.00 L 6076.40 6609.20 L 6076.70 6617.90 L 6077.50 6626.40 L 6078.90 6634.70 L 6080.90 6643.00 L 6083.40 6651.10 L 6086.40 6659.00 L 6089.80 6666.70 L 6093.80 6674.20 L 6098.30 6681.40 L 6103.30 6688.30 L 6108.70 6694.90 L 6114.60 6701.30 L 6120.90 6707.20 L 6127.50 6712.60 L 6134.40 6717.50 L 6141.60 6722.00 L 6149.10 6726.00 L 6156.80 6729.50 L 6164.70 6732.50 L 6172.80 6735.00 L 6181.10 6736.90 L 6189.50 6738.30 L 6198.00 6739.20 L 6206.60 6739.50 L 10812.50 6739.50 L 10821.10 6739.20 L 10838.00 6736.90 L 10846.30 6735.00 L 10854.40 6732.50 L 10862.30 6729.50 L 10870.00 6726.00 L 10877.50 6722.00 L 10884.70 6717.50 L 10891.60 6712.60 L 10898.20 6707.20 L 10904.50 6701.30 L 10910.40 6694.90 L 10915.80 6688.30 L 10920.80 6681.40 L 10925.20 6674.20 L 10929.30 6666.70 L 10932.80 6659.00 L 10938.20 6643.00 L 10940.20 6634.70 L 10942.40 6617.90 L 10942.70 6609.20 L 10942.70 135.00 L 10942.40 126.40 L 10941.60 117.90 L 10940.20 109.50 L 10938.20 101.20 L 10935.70 93.10 L 10932.70 85.20 L 10929.30 77.50 L 10925.20 70.00 L 10920.80 62.80 L 10915.80 55.90 L 10910.40 49.30 L 10904.50 43.00 L 10898.20 37.10 L 10891.60 31.70 L 10884.70 26.70 L 10877.50 22.20 L 10870.00 18.20 L 10862.30 14.70 L 10854.40 11.80 L 10846.30 9.30 L 10838.00 7.30 L 10829.60 5.90 L 10821.10 5.10 L 10812.50 4.80 L 6206.60 4.80 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="194" stroke="none" target-height="6745.4" target-width="4877.8003" target-x="6070.4" target-y="0"></path><path d="M 6378.10 5771.80 L 5853.60 5771.80 L 5853.60 7852.40 L 6378.10 7852.40 L 6378.10 5771.80 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="196" stroke="none" target-height="2080.6" target-width="524.5" target-x="5853.6" target-y="5771.8"></path><path d="M 6278.40 7852.40 L 6378.10 7852.40 L 6378.10 7569.00 L 6173.90 7569.00 L 6278.40 7852.40 Z" fill="#fafafa" fill-rule="nonzero" node-id="198" stroke="none" target-height="283.3999" target-width="204.2002" target-x="6173.9" target-y="7569"></path><path d="M 4079.70 5771.80 L 3555.20 5771.80 L 3555.20 7852.40 L 4079.70 7852.40 L 4079.70 5771.80 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="200" stroke="none" target-height="2080.6" target-width="524.5" target-x="3555.2" target-y="5771.8"></path><path d="M 6378.10 5771.80 L 3992.90 5771.80 L 3992.90 7678.50 L 6378.10 7678.50 L 6378.10 5771.80 Z" fill="#fafafa" fill-rule="nonzero" node-id="202" stroke="none" target-height="1906.7002" target-width="2385.2002" target-x="3992.9" target-y="5771.8"></path><path d="M 6291.50 5865.50 L 6291.50 6370.00 L 4078.70 6370.00 L 4078.70 5865.50 L 6290.80 5865.50 M 6300.10 5856.20 L 4070.20 5856.20 L 4070.20 6379.10 L 6300.80 6379.10 L 6300.80 5856.20 L 6300.10 5856.20 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="204" stroke="none" target-height="522.8999" target-width="2230.5999" target-x="4070.2" target-y="5856.2"></path><path d="M 4092.30 7852.40 L 3992.90 7852.40 L 3992.90 7569.00 L 4196.80 7569.00 L 4092.30 7852.40 Z" fill="#fafafa" fill-rule="nonzero" node-id="206" stroke="none" target-height="283.3999" target-width="203.8999" target-x="3992.9" target-y="7569"></path><path d="M 5132.80 6117.60 L 5133.80 6127.90 L 5135.00 6132.90 L 5136.80 6137.80 L 5139.00 6142.50 L 5141.70 6146.90 L 5148.20 6154.90 L 5152.10 6158.40 L 5156.20 6161.50 L 5160.60 6164.10 L 5165.30 6166.30 L 5175.20 6169.30 L 5180.30 6170.10 L 5185.50 6170.40 L 5190.60 6170.10 L 5195.80 6169.30 L 5200.80 6168.10 L 5205.70 6166.30 L 5210.30 6164.10 L 5214.80 6161.50 L 5218.90 6158.40 L 5222.80 6154.90 L 5229.30 6146.90 L 5232.00 6142.50 L 5234.20 6137.80 L 5235.90 6133.00 L 5237.20 6127.90 L 5238.00 6122.80 L 5238.20 6117.60 L 5238.00 6112.50 L 5237.20 6107.40 L 5235.90 6102.40 L 5234.20 6097.50 L 5232.00 6092.80 L 5229.30 6088.30 L 5222.80 6080.40 L 5219.00 6076.90 L 5214.80 6073.80 L 5210.30 6071.10 L 5205.70 6068.90 L 5200.80 6067.20 L 5195.80 6065.90 L 5190.70 6065.20 L 5185.50 6064.90 L 5175.10 6066.00 L 5170.20 6067.20 L 5165.30 6068.90 L 5156.30 6073.80 L 5152.10 6076.80 L 5148.20 6080.40 L 5144.70 6084.30 L 5141.60 6088.40 L 5136.80 6097.50 L 5133.80 6107.30 L 5132.80 6117.60 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="208" stroke="none" target-height="105.5" target-width="105.40039" target-x="5132.8" target-y="6064.9"></path><path d="M 6291.50 6472.80 L 6291.50 6977.30 L 4078.70 6977.30 L 4078.70 6472.80 L 6290.80 6472.80 M 6300.10 6463.70 L 4070.20 6463.70 L 4070.20 6986.60 L 6300.80 6986.60 L 6300.80 6464.00 L 6300.10 6463.70 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="210" stroke="none" target-height="522.8999" target-width="2230.5999" target-x="4070.2" target-y="6463.7"></path><path d="M 5132.80 6726.40 L 5133.00 6731.60 L 5133.80 6736.70 L 5135.00 6741.70 L 5136.80 6746.50 L 5141.70 6755.60 L 5144.80 6759.80 L 5148.20 6763.60 L 5152.10 6767.10 L 5156.20 6770.20 L 5165.30 6775.10 L 5170.20 6776.80 L 5175.20 6778.10 L 5180.30 6778.80 L 5185.50 6779.10 L 5190.60 6778.80 L 5195.80 6778.10 L 5200.80 6776.80 L 5205.70 6775.00 L 5210.30 6772.80 L 5214.80 6770.20 L 5222.80 6763.60 L 5226.30 6759.80 L 5229.30 6755.60 L 5234.20 6746.50 L 5235.90 6741.70 L 5237.20 6736.60 L 5238.00 6731.50 L 5238.20 6726.30 L 5238.00 6721.20 L 5237.20 6716.10 L 5234.20 6706.20 L 5232.00 6701.50 L 5229.30 6697.10 L 5226.20 6692.90 L 5222.80 6689.10 L 5219.00 6685.60 L 5214.80 6682.50 L 5210.30 6679.80 L 5205.70 6677.60 L 5195.80 6674.70 L 5190.70 6673.90 L 5185.50 6673.60 L 5175.10 6674.70 L 5165.30 6677.70 L 5156.30 6682.50 L 5152.10 6685.60 L 5148.20 6689.10 L 5144.70 6693.00 L 5141.60 6697.10 L 5139.00 6701.50 L 5136.80 6706.20 L 5135.10 6711.00 L 5133.80 6716.00 L 5133.00 6721.10 L 5132.80 6726.40 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="212" stroke="none" target-height="105.5" target-width="105.40039" target-x="5132.8" target-y="6673.6"></path><path d="M 6291.50 7080.30 L 6291.50 7585.00 L 4078.70 7585.00 L 4078.70 7080.30 L 6290.80 7080.30 M 6300.10 7071.00 L 4070.20 7071.00 L 4070.20 7594.10 L 6300.80 7594.10 L 6300.80 7071.00 L 6300.10 7071.00 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="214" stroke="none" target-height="523.1001" target-width="2230.5999" target-x="4070.2" target-y="7071"></path><path d="M 5132.80 7332.70 L 5133.10 7337.90 L 5133.80 7343.00 L 5135.10 7348.00 L 5136.90 7352.80 L 5139.10 7357.40 L 5141.80 7361.90 L 5144.90 7366.10 L 5148.40 7369.90 L 5152.20 7373.30 L 5156.40 7376.40 L 5160.80 7379.00 L 5165.50 7381.20 L 5175.40 7384.20 L 5185.60 7385.20 L 5190.80 7384.90 L 5195.90 7384.10 L 5201.00 7382.80 L 5205.80 7381.10 L 5210.50 7378.90 L 5214.90 7376.20 L 5222.90 7369.60 L 5226.30 7365.80 L 5229.40 7361.60 L 5234.20 7352.50 L 5235.90 7347.70 L 5237.20 7342.60 L 5238.20 7332.30 L 5237.20 7322.10 L 5235.90 7317.10 L 5234.20 7312.20 L 5229.30 7303.10 L 5222.70 7295.10 L 5218.90 7291.70 L 5214.80 7288.60 L 5205.60 7283.70 L 5200.80 7282.00 L 5195.80 7280.70 L 5190.70 7280.00 L 5185.50 7279.70 L 5175.20 7280.70 L 5170.20 7282.00 L 5165.30 7283.80 L 5160.50 7286.00 L 5156.10 7288.70 L 5148.10 7295.30 L 5141.60 7303.30 L 5138.90 7307.70 L 5136.70 7312.40 L 5135.00 7317.40 L 5133.80 7322.40 L 5132.80 7332.70 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="216" stroke="none" target-height="105.5" target-width="105.40039" target-x="5132.8" target-y="7279.7"></path><path d="M 3759.40 4563.40 L 3234.90 4563.40 L 3234.90 7852.70 L 3759.40 7852.70 L 3759.40 4563.40 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="218" stroke="none" target-height="3289.3003" target-width="524.5" target-x="3234.9" target-y="4563.4"></path><path d="M 3659.70 7852.40 L 3759.40 7852.40 L 3759.40 7569.00 L 3555.40 7569.00 L 3659.70 7852.40 Z" fill="#fafafa" fill-rule="nonzero" node-id="220" stroke="none" target-height="283.3999" target-width="204" target-x="3555.4" target-y="7569"></path><path d="M 1799.20 4563.40 L 1274.70 4563.40 L 1274.70 7852.70 L 1799.20 7852.70 L 1799.20 4563.40 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="222" stroke="none" target-height="3289.3003" target-width="524.5" target-x="1274.7" target-y="4563.4"></path><path d="M 3759.40 4563.40 L 1712.10 4563.40 L 1712.10 7678.80 L 3759.40 7678.80 L 3759.40 4563.40 Z" fill="#fafafa" fill-rule="nonzero" node-id="224" stroke="none" target-height="3115.4" target-width="2047.2999" target-x="1712.1" target-y="4563.4"></path><path d="M 3671.10 4651.20 L 3671.10 5356.50 L 1800.40 5356.50 L 1800.40 4651.20 L 3671.10 4651.20 Z M 3682.10 4640.50 L 1788.90 4640.50 L 1788.90 5366.80 L 3681.60 5366.80 L 3681.60 4640.70 L 3682.10 4640.50 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="226" stroke="none" target-height="726.2998" target-width="1893.2001" target-x="1788.9" target-y="4640.5"></path><path d="M 3671.10 5442.60 L 3671.10 7313.40 L 2780.00 7313.40 L 2780.00 5442.60 L 3671.10 5442.60 Z M 3682.10 5431.70 L 2769.30 5431.70 L 2769.30 7324.30 L 3682.10 7324.30 L 3682.10 5431.70 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="228" stroke="none" target-height="1892.5996" target-width="912.80005" target-x="2769.3" target-y="5431.7"></path><path d="M 2691.50 5442.60 L 2691.50 7313.40 L 1800.40 7313.40 L 1800.40 5442.60 L 2691.50 5442.60 Z M 2702.20 5431.70 L 1788.90 5431.70 L 1788.90 7324.30 L 2701.80 7324.30 L 2701.80 5431.70 L 2702.20 5431.70 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="230" stroke="none" target-height="1892.5996" target-width="913.2999" target-x="1788.9" target-y="5431.7"></path><path d="M 1811.80 7852.40 L 1712.10 7852.40 L 1712.10 7569.00 L 1916.30 7569.00 L 1811.80 7852.40 Z" fill="#fafafa" fill-rule="nonzero" node-id="232" stroke="none" target-height="283.3999" target-width="204.20007" target-x="1712.1" target-y="7569"></path><path d="M 2682.90 5004.20 L 2683.10 5009.40 L 2683.90 5014.60 L 2685.10 5019.60 L 2686.80 5024.50 L 2689.00 5029.20 L 2691.70 5033.70 L 2694.80 5037.90 L 2698.30 5041.80 L 2702.10 5045.30 L 2706.30 5048.40 L 2710.70 5051.00 L 2715.40 5053.30 L 2720.40 5055.10 L 2725.40 5056.40 L 2730.50 5057.10 L 2735.70 5057.40 L 2740.90 5057.20 L 2746.00 5056.40 L 2751.10 5055.20 L 2756.00 5053.40 L 2760.70 5051.20 L 2765.20 5048.60 L 2769.30 5045.50 L 2773.20 5042.00 L 2776.70 5038.10 L 2779.80 5033.90 L 2782.50 5029.50 L 2784.70 5024.80 L 2786.50 5020.00 L 2787.80 5014.90 L 2788.60 5009.70 L 2788.80 5004.50 L 2788.60 4999.30 L 2787.80 4994.20 L 2786.60 4989.20 L 2784.80 4984.20 L 2782.60 4979.50 L 2776.80 4970.90 L 2773.30 4967.00 L 2769.50 4963.60 L 2765.30 4960.40 L 2760.80 4957.70 L 2756.10 4955.50 L 2751.20 4953.80 L 2741.10 4951.80 L 2730.60 4951.80 L 2725.50 4952.50 L 2720.50 4953.80 L 2711.00 4957.70 L 2706.60 4960.30 L 2702.40 4963.40 L 2698.50 4966.90 L 2695.00 4970.80 L 2691.90 4975.00 L 2689.20 4979.40 L 2687.00 4984.00 L 2685.20 4988.90 L 2684.00 4993.80 L 2683.20 4998.90 L 2682.90 5004.20 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="234" stroke="none" target-height="105.6001" target-width="105.90015" target-x="2682.9" target-y="4951.8"></path><path d="M 2871.80 6333.50 L 2872.10 6338.70 L 2872.80 6343.90 L 2874.10 6348.90 L 2875.90 6353.80 L 2878.10 6358.50 L 2880.80 6362.90 L 2887.40 6371.00 L 2891.20 6374.50 L 2895.40 6377.60 L 2899.80 6380.20 L 2904.60 6382.50 L 2909.50 6384.20 L 2914.50 6385.50 L 2919.70 6386.20 L 2924.80 6386.50 L 2930.00 6386.20 L 2935.20 6385.40 L 2940.30 6384.10 L 2945.10 6382.40 L 2949.80 6380.20 L 2954.30 6377.50 L 2958.40 6374.40 L 2962.30 6370.90 L 2965.80 6367.00 L 2968.90 6362.80 L 2971.50 6358.30 L 2973.70 6353.70 L 2975.50 6348.80 L 2976.70 6343.70 L 2977.50 6338.50 L 2977.70 6333.30 L 2977.50 6328.20 L 2976.70 6323.00 L 2973.60 6313.10 L 2971.40 6308.30 L 2968.70 6303.90 L 2965.60 6299.80 L 2962.10 6295.90 L 2954.00 6289.40 L 2949.50 6286.70 L 2944.80 6284.50 L 2939.90 6282.80 L 2934.90 6281.50 L 2924.50 6280.50 L 2919.30 6280.80 L 2914.20 6281.60 L 2904.30 6284.60 L 2899.60 6286.80 L 2895.20 6289.50 L 2891.00 6292.60 L 2887.20 6296.10 L 2883.70 6300.00 L 2880.60 6304.10 L 2878.00 6308.50 L 2875.80 6313.30 L 2874.00 6318.20 L 2872.80 6323.20 L 2871.80 6333.50 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="236" stroke="none" target-height="106" target-width="105.8999" target-x="2871.8" target-y="6280.5"></path><path d="M 2494.20 6333.50 L 2495.30 6343.80 L 2498.30 6353.60 L 2500.50 6358.30 L 2503.20 6362.70 L 2509.80 6370.70 L 2513.70 6374.10 L 2517.80 6377.20 L 2522.20 6379.80 L 2526.90 6382.00 L 2531.90 6383.80 L 2536.80 6385.00 L 2547.10 6386.00 L 2552.20 6385.70 L 2557.40 6384.90 L 2562.40 6383.70 L 2567.30 6381.90 L 2571.90 6379.70 L 2576.30 6377.00 L 2580.50 6374.00 L 2584.30 6370.50 L 2587.80 6366.60 L 2590.80 6362.40 L 2593.50 6358.00 L 2595.70 6353.40 L 2597.40 6348.50 L 2598.70 6343.50 L 2599.40 6338.30 L 2599.70 6333.20 L 2599.40 6328.00 L 2598.60 6322.90 L 2597.40 6317.90 L 2595.60 6313.00 L 2593.40 6308.30 L 2590.70 6303.90 L 2587.70 6299.80 L 2584.20 6296.00 L 2580.40 6292.50 L 2576.20 6289.40 L 2571.70 6286.70 L 2567.10 6284.60 L 2562.20 6282.80 L 2557.20 6281.60 L 2552.10 6280.80 L 2547.00 6280.50 L 2541.80 6280.80 L 2536.60 6281.60 L 2531.60 6282.80 L 2526.70 6284.60 L 2522.00 6286.80 L 2517.60 6289.50 L 2513.50 6292.60 L 2509.60 6296.10 L 2506.10 6300.00 L 2503.00 6304.10 L 2498.20 6313.30 L 2495.20 6323.20 L 2494.50 6328.30 L 2494.20 6333.50 Z" fill="#e6e6e6" fill-rule="nonzero" node-id="238" stroke="none" target-height="105.5" target-width="105.5" target-x="2494.2" target-y="6280.5"></path><path d="M 2434.60 3646.30 L 2213.50 3646.30 L 2219.40 3678.50 L 2224.80 3710.80 L 2229.80 3743.20 L 2234.30 3775.60 L 2238.40 3808.10 L 2242.00 3840.60 L 2245.20 3873.20 L 2247.90 3905.80 L 2250.10 3938.50 L 2251.90 3971.20 L 2251.60 3979.70 L 2250.90 3989.90 L 2249.50 4001.80 L 2246.90 4020.10 L 2239.10 4063.80 L 2224.10 4140.70 L 2213.70 4196.60 L 2208.90 4225.30 L 2204.50 4254.30 L 2199.60 4293.00 L 2196.20 4331.10 L 2195.10 4349.70 L 2194.60 4367.90 L 2194.50 4381.60 L 2195.40 4407.70 L 2196.30 4420.10 L 2197.70 4432.60 L 2199.50 4444.60 L 2201.70 4456.00 L 2204.30 4466.90 L 2206.80 4475.60 L 2209.60 4483.90 L 2212.70 4491.80 L 2216.10 4499.30 L 2219.80 4506.40 L 2223.90 4513.40 L 2228.40 4519.90 L 2233.20 4526.00 L 2238.40 4531.60 L 2243.90 4536.90 L 2249.80 4541.70 L 2256.20 4546.00 L 2263.00 4550.00 L 2270.30 4553.40 L 2278.10 4556.50 L 2285.90 4558.90 L 2294.20 4560.80 L 2303.30 4562.20 L 2313.00 4563.10 L 2323.50 4563.40 L 2333.90 4563.10 L 2343.60 4562.20 L 2352.70 4560.80 L 2361.00 4558.90 L 2368.80 4556.50 L 2376.70 4553.50 L 2383.90 4550.00 L 2390.70 4546.10 L 2397.10 4541.70 L 2403.00 4536.90 L 2408.50 4531.70 L 2413.70 4526.00 L 2418.50 4520.00 L 2423.00 4513.50 L 2427.10 4506.50 L 2430.80 4499.40 L 2434.20 4492.00 L 2437.30 4484.10 L 2440.10 4475.80 L 2442.60 4467.10 L 2445.20 4456.20 L 2447.40 4444.80 L 2449.20 4432.90 L 2450.60 4420.40 L 2451.50 4408.00 L 2452.40 4381.90 L 2452.40 4368.20 L 2451.80 4350.10 L 2450.80 4331.50 L 2447.30 4293.40 L 2442.40 4254.80 L 2438.00 4225.90 L 2433.20 4197.20 L 2422.80 4141.30 L 2407.80 4064.20 L 2400.00 4020.50 L 2397.40 4002.10 L 2396.00 3990.10 L 2395.30 3979.80 L 2395.00 3971.20 L 2396.90 3938.50 L 2399.30 3905.80 L 2402.10 3873.20 L 2405.40 3840.60 L 2409.10 3808.00 L 2413.30 3775.60 L 2417.90 3743.10 L 2423.00 3710.80 L 2428.60 3678.50 L 2434.60 3646.30 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="240" stroke="none" target-height="917.09985" target-width="257.8999" target-x="2194.5" target-y="3646.3"></path><path d="M 1593.30 4130.00 L 1593.30 4273.10 L 1593.60 4284.70 L 1594.30 4296.10 L 1595.50 4307.40 L 1597.10 4318.70 L 1599.10 4329.80 L 1601.60 4340.90 L 1604.50 4351.90 L 1607.80 4362.60 L 1611.50 4373.30 L 1615.60 4383.80 L 1620.20 4394.20 L 1625.10 4404.30 L 1630.40 4414.20 L 1636.20 4424.00 L 1642.30 4433.60 L 1648.80 4442.90 L 1655.60 4451.90 L 1662.80 4460.70 L 1670.40 4469.30 L 1678.40 4477.60 L 1686.70 4485.60 L 1695.30 4493.20 L 1704.10 4500.40 L 1713.20 4507.30 L 1722.50 4513.80 L 1732.00 4519.90 L 1741.80 4525.60 L 1761.90 4535.90 L 1772.20 4540.40 L 1782.80 4544.60 L 1793.40 4548.30 L 1804.20 4551.60 L 1815.10 4554.50 L 1826.20 4556.90 L 1837.40 4559.00 L 1848.60 4560.60 L 1859.90 4561.70 L 1871.40 4562.40 L 1882.90 4562.70 L 2026.00 4562.70 L 2037.50 4562.40 L 2049.00 4561.70 L 2060.30 4560.60 L 2071.50 4559.00 L 2082.70 4556.90 L 2093.80 4554.50 L 2104.70 4551.60 L 2115.50 4548.30 L 2126.20 4544.60 L 2136.70 4540.40 L 2147.00 4535.90 L 2157.20 4530.90 L 2167.10 4525.60 L 2176.90 4519.90 L 2186.40 4513.80 L 2195.80 4507.30 L 2204.80 4500.40 L 2213.60 4493.20 L 2222.20 4485.60 L 2230.50 4477.60 L 2238.50 4469.30 L 2246.10 4460.70 L 2253.30 4451.90 L 2260.20 4442.90 L 2266.60 4433.60 L 2272.80 4424.00 L 2278.50 4414.20 L 2283.80 4404.30 L 2288.70 4394.20 L 2293.30 4383.80 L 2297.40 4373.30 L 2301.10 4362.60 L 2304.40 4351.90 L 2307.30 4340.90 L 2309.80 4329.80 L 2311.80 4318.70 L 2313.40 4307.40 L 2314.60 4296.10 L 2315.30 4284.70 L 2315.60 4273.10 L 2315.60 4130.00 L 1593.30 4130.00 Z" fill="#f5f5f5" fill-rule="nonzero" node-id="242" stroke="none" target-height="432.7002" target-width="722.30005" target-x="1593.3" target-y="4130"></path><path d="M 4667.20 5468.40 L 4667.40 5478.80 L 4668.20 5489.20 L 4669.30 5499.40 L 4671.00 5509.50 L 4673.10 5519.50 L 4675.70 5529.40 L 4679.30 5541.10 L 4683.50 5552.60 L 4688.40 5564.00 L 4693.80 5575.20 L 4699.90 5586.20 L 4706.50 5596.90 L 4713.60 5607.40 L 4721.30 5617.70 L 4729.50 5627.80 L 4738.30 5637.80 L 4747.40 5647.20 L 4757.10 5656.40 L 4767.20 5665.40 L 4777.90 5674.20 L 4789.10 5682.70 L 4800.50 5690.70 L 4812.30 5698.40 L 4824.60 5705.90 L 4837.30 5713.00 L 4850.60 5719.80 L 4863.90 5726.10 L 4877.60 5732.10 L 4891.70 5737.70 L 4906.20 5743.00 L 4921.10 5747.90 L 4936.00 5752.20 L 4951.20 5756.20 L 4966.70 5759.80 L 4982.60 5762.90 L 4998.80 5765.60 L 5015.00 5767.80 L 5031.40 5769.50 L 5048.10 5770.80 L 5065.00 5771.50 L 5082.20 5771.80 L 5288.30 5771.80 L 5305.50 5771.50 L 5322.40 5770.80 L 5339.10 5769.50 L 5355.50 5767.80 L 5371.70 5765.60 L 5387.90 5762.90 L 5403.80 5759.80 L 5434.50 5752.20 L 5449.40 5747.90 L 5464.30 5743.00 L 5478.80 5737.70 L 5492.90 5732.10 L 5519.90 5719.80 L 5533.20 5713.00 L 5545.90 5705.90 L 5558.20 5698.40 L 5570.00 5690.70 L 5581.40 5682.70 L 5592.60 5674.20 L 5603.30 5665.40 L 5613.40 5656.40 L 5623.10 5647.20 L 5632.20 5637.80 L 5641.00 5627.80 L 5649.20 5617.70 L 5656.90 5607.40 L 5664.00 5596.90 L 5670.60 5586.20 L 5676.70 5575.20 L 5682.10 5564.00 L 5687.00 5552.60 L 5691.20 5541.10 L 5694.80 5529.40 L 5697.40 5519.50 L 5699.50 5509.50 L 5701.20 5499.40 L 5702.30 5489.20 L 5703.10 5478.80 L 5703.30 5468.40 L 4667.20 5468.40 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="244" stroke="none" target-height="303.3999" target-width="1036.0996" target-x="4667.2" target-y="5468.4"></path><path d="M 10127.90 5223.70 L 10096.40 5250.30 L 10061.70 5281.70 L 10026.80 5315.30 L 10008.40 5333.90 L 9989.30 5353.60 L 9970.90 5373.30 L 9951.90 5394.10 L 9932.40 5416.10 L 9893.10 5462.30 L 9873.40 5486.50 L 9853.30 5511.70 L 9832.90 5538.20 L 9813.00 5564.60 L 9793.00 5592.00 L 9772.70 5620.60 L 9752.20 5650.30 L 9732.40 5680.00 L 9712.40 5710.70 L 9692.40 5742.50 L 9672.40 5775.40 L 9659.40 5797.30 L 9646.80 5819.40 L 9634.70 5841.50 L 9623.10 5863.80 L 9611.90 5886.20 L 9590.70 5931.80 L 9580.70 5954.90 L 9571.20 5978.20 L 9562.00 6001.80 L 9553.30 6025.90 L 9544.90 6050.30 L 9537.00 6074.70 L 9529.50 6099.70 L 9522.30 6125.20 L 9515.60 6151.20 L 9509.30 6177.20 L 9503.30 6203.90 L 9497.80 6231.30 L 9492.50 6259.30 L 9487.80 6287.20 L 9483.40 6315.90 L 9479.30 6345.50 L 9475.60 6376.00 L 9475.80 6209.20 L 9478.20 5529.00 L 9536.60 5395.50 L 9586.30 5279.30 L 9623.10 5190.30 L 9655.90 5108.70 L 9695.00 5012.60 L 9747.30 4888.10 L 9779.10 4814.50 L 9809.20 4746.20 L 9925.80 4485.50 L 9957.50 4413.10 L 9991.50 4333.70 L 10028.70 4245.20 L 10052.70 4187.20 L 10076.70 4128.30 L 10124.10 4010.20 L 10170.20 3892.70 L 10214.20 3778.10 L 10235.30 3722.00 L 10274.70 3615.00 L 10310.10 3515.80 L 10340.70 3426.30 L 10353.70 3386.60 L 10365.80 3348.50 L 10375.70 3315.60 L 10381.90 3294.10 L 10391.00 3259.30 L 10394.70 3243.00 L 10398.80 3219.70 L 10399.60 3211.50 L 10399.70 3205.60 L 10398.50 3198.90 L 10397.60 3197.40 L 10396.50 3196.50 L 10395.30 3196.20 L 10393.70 3196.30 L 10391.70 3197.10 L 10389.10 3198.80 L 10383.90 3203.20 L 10377.90 3208.70 L 10371.20 3215.50 L 10363.60 3223.80 L 10354.20 3234.90 L 10343.60 3248.10 L 10331.80 3263.50 L 10307.90 3296.50 L 10274.50 3345.60 L 10252.80 3379.00 L 10230.80 3413.60 L 10184.10 3489.50 L 10135.30 3571.50 L 10110.30 3614.30 L 10060.20 3701.70 L 10010.40 3790.30 L 9985.80 3834.70 L 9938.10 3921.80 L 9872.10 4044.80 L 9814.90 4154.50 L 9770.60 4242.20 L 9749.40 4286.40 L 9741.20 4304.30 L 9736.00 4316.60 L 9718.70 4359.30 L 9702.00 4402.30 L 9685.90 4445.40 L 9670.30 4488.70 L 9655.30 4532.20 L 9640.80 4575.90 L 9626.90 4619.80 L 9613.60 4663.80 L 9600.80 4708.00 L 9588.60 4752.40 L 9577.00 4796.80 L 9566.00 4841.50 L 9555.50 4886.30 L 9545.60 4931.20 L 9536.30 4976.20 L 9527.60 5021.30 L 9519.40 5066.60 L 9511.80 5112.00 L 9504.80 5157.50 L 9498.40 5203.10 L 9492.60 5248.70 L 9487.40 5294.50 L 9482.70 5340.40 L 9478.70 5386.30 L 9479.40 5084.80 L 9482.20 4601.60 L 9484.60 4355.40 L 9486.70 4222.80 L 9489.00 4113.60 L 9491.40 4025.70 L 9493.80 3956.80 L 9496.20 3904.70 L 9498.30 3867.10 L 9500.10 3841.70 L 9530.00 3794.80 L 9543.90 3773.60 L 9569.30 3736.10 L 9582.30 3716.20 L 9594.50 3696.60 L 9606.40 3676.70 L 9628.70 3637.40 L 9639.20 3617.60 L 9659.40 3577.50 L 9674.00 3547.00 L 9688.30 3515.40 L 9702.50 3483.30 L 9716.90 3449.60 L 9779.30 3300.60 L 9826.70 3190.00 L 9847.90 3137.50 L 9868.40 3082.80 L 9878.70 3054.20 L 9898.40 2996.50 L 9922.30 2922.20 L 9936.00 2877.20 L 9949.30 2832.10 L 9962.00 2787.40 L 9974.20 2742.80 L 9985.80 2699.30 L 10006.90 2614.90 L 10016.60 2574.30 L 10033.40 2499.10 L 10040.40 2465.30 L 10052.00 2403.90 L 10056.50 2376.70 L 10059.80 2354.30 L 10062.80 2327.80 L 10063.70 2308.50 L 10063.40 2301.90 L 10062.70 2297.70 L 10061.80 2295.40 L 10060.90 2294.20 L 10059.90 2293.80 L 10057.10 2294.80 L 10044.00 2305.90 L 10030.90 2317.80 L 10017.80 2330.50 L 10004.80 2344.00 L 9991.70 2358.40 L 9979.30 2372.80 L 9966.80 2388.00 L 9954.30 2404.00 L 9929.10 2438.60 L 9914.00 2460.70 L 9898.80 2484.00 L 9883.50 2508.50 L 9868.00 2534.30 L 9853.10 2560.00 L 9838.00 2586.90 L 9807.20 2644.20 L 9787.10 2683.20 L 9766.50 2724.20 L 9745.50 2767.10 L 9714.10 2832.40 L 9681.40 2901.80 L 9613.90 3046.90 L 9601.90 3073.40 L 9590.70 3099.00 L 9580.50 3123.80 L 9571.10 3147.60 L 9562.00 3172.10 L 9553.70 3195.70 L 9546.00 3218.70 L 9539.10 3241.00 L 9532.50 3263.60 L 9526.50 3285.70 L 9521.10 3307.30 L 9516.20 3328.30 L 9511.70 3349.60 L 9503.90 3391.00 L 9500.70 3411.10 L 9496.80 3438.10 L 9493.60 3464.70 L 9490.80 3490.90 L 9486.50 3543.30 L 9482.80 3608.20 L 9477.70 3727.10 L 9475.70 3767.60 L 9471.10 3837.70 L 9464.00 3912.90 L 9462.30 3939.00 L 9460.90 3968.30 L 9459.30 4006.50 L 9456.60 4096.80 L 9454.10 4207.80 L 9451.80 4337.20 L 9449.80 4483.00 L 9445.10 4447.90 L 9434.90 4381.20 L 9429.50 4349.60 L 9423.50 4317.70 L 9417.30 4286.80 L 9410.80 4257.00 L 9404.10 4228.10 L 9396.80 4199.10 L 9389.20 4171.00 L 9381.30 4143.60 L 9373.10 4117.20 L 9364.40 4090.60 L 9355.30 4064.80 L 9345.90 4039.60 L 9336.20 4015.00 L 9325.90 3990.60 L 9315.20 3966.60 L 9304.20 3943.10 L 9292.70 3920.10 L 9280.80 3897.30 L 9268.40 3874.80 L 9255.60 3852.70 L 9242.30 3830.80 L 9228.60 3809.30 L 9214.30 3787.90 L 9199.60 3766.60 L 9184.30 3745.50 L 9162.60 3716.50 L 9141.20 3688.40 L 9099.40 3635.50 L 9078.90 3610.50 L 9052.40 3579.00 L 9026.50 3549.20 L 9001.30 3520.90 L 8976.70 3494.20 L 8951.30 3467.40 L 8926.60 3442.30 L 8902.80 3418.70 L 8879.70 3396.70 L 8855.80 3374.60 L 8832.90 3354.10 L 8811.00 3335.20 L 8789.90 3317.80 L 8768.00 3300.40 L 8747.30 3284.60 L 8727.70 3270.30 L 8709.20 3257.50 L 8703.60 3254.00 L 8695.40 3250.20 L 8690.10 3249.20 L 8688.00 3249.50 L 8686.30 3250.20 L 8685.00 3251.20 L 8682.90 3254.60 L 8682.00 3260.90 L 8683.10 3271.90 L 8684.80 3279.60 L 8687.40 3289.30 L 8691.20 3300.90 L 8696.30 3314.60 L 8708.10 3343.50 L 8727.70 3386.40 L 8741.90 3415.40 L 8756.90 3445.30 L 8773.60 3477.60 L 8809.80 3545.30 L 8849.30 3616.60 L 8891.00 3689.70 L 8933.60 3763.00 L 8997.00 3869.30 L 9037.10 3935.00 L 9074.10 3994.50 L 9197.80 4188.70 L 9231.50 4242.50 L 9275.10 4313.50 L 9316.10 4383.10 L 9335.60 4417.50 L 9354.20 4451.40 L 9367.90 4477.00 L 9381.00 4502.50 L 9393.60 4527.70 L 9405.60 4552.70 L 9417.30 4578.00 L 9428.30 4603.10 L 9438.80 4628.10 L 9448.60 4652.90 L 9446.00 4983.10 L 9444.10 5340.90 L 9443.50 5525.80 L 9442.70 6084.00 L 9436.20 6046.90 L 9429.20 6009.40 L 9421.80 5971.50 L 9405.80 5895.90 L 9397.10 5858.10 L 9388.00 5820.30 L 9378.50 5782.90 L 9368.60 5745.80 L 9358.30 5708.70 L 9347.50 5672.30 L 9336.50 5636.50 L 9324.80 5600.80 L 9312.90 5566.10 L 9300.70 5532.30 L 9291.10 5507.00 L 9281.30 5482.40 L 9271.40 5458.50 L 9230.20 5365.00 L 9196.40 5289.70 L 9160.50 5211.40 L 9141.80 5171.80 L 9103.50 5092.80 L 9084.00 5054.10 L 9070.80 5028.50 L 9044.50 4979.20 L 9017.90 4931.80 L 8991.50 4887.60 L 8965.60 4847.40 L 8945.80 4819.20 L 8927.10 4794.60 L 8917.30 4782.70 L 8907.90 4771.90 L 8890.00 4753.30 L 8888.90 4752.60 L 8888.20 4752.50 L 8887.60 4752.90 L 8887.00 4754.10 L 8886.60 4756.60 L 8886.50 4760.70 L 8886.80 4767.10 L 8887.70 4776.30 L 8889.70 4790.90 L 8892.90 4810.40 L 8901.60 4856.60 L 8907.10 4883.10 L 8920.60 4943.80 L 8936.60 5011.40 L 8954.80 5083.90 L 8964.60 5121.40 L 8985.10 5197.10 L 8995.90 5235.00 L 9017.90 5309.30 L 9029.00 5345.00 L 9040.40 5380.10 L 9051.50 5413.10 L 9062.90 5445.20 L 9073.80 5474.50 L 9081.40 5493.60 L 9088.60 5511.10 L 9095.50 5527.00 L 9102.80 5542.80 L 9109.70 5556.60 L 9148.50 5627.30 L 9186.30 5698.90 L 9207.60 5740.20 L 9266.90 5857.40 L 9284.90 5892.60 L 9302.80 5926.70 L 9319.50 5957.70 L 9330.70 5977.80 L 9341.50 5996.30 L 9359.90 6027.10 L 9371.30 6047.40 L 9399.80 6099.40 L 9442.70 6178.90 L 9443.10 6564.10 L 9443.70 6743.20 L 9445.50 7062.80 L 9448.00 7315.90 L 9449.60 7411.80 L 9451.20 7484.10 L 9477.00 7484.10 L 9476.00 7403.50 L 9475.40 7241.10 L 9475.60 6730.90 L 9482.50 6707.60 L 9490.00 6684.00 L 9498.10 6659.90 L 9506.80 6635.50 L 9525.40 6587.00 L 9535.50 6562.30 L 9546.10 6537.20 L 9560.50 6504.20 L 9591.40 6437.10 L 9624.10 6369.70 L 9666.90 6285.20 L 9797.30 6035.30 L 9825.40 5980.80 L 9857.60 5917.20 L 9892.80 5846.60 L 9948.70 5732.30 L 9986.40 5653.30 L 10023.10 5574.80 L 10040.60 5536.60 L 10073.40 5462.80 L 10102.40 5394.60 L 10115.20 5362.70 L 10126.20 5334.30 L 10133.00 5315.60 L 10138.80 5298.80 L 10143.70 5283.90 L 10148.20 5268.80 L 10151.50 5256.10 L 10153.80 5245.50 L 10155.20 5237.10 L 10155.80 5230.40 L 10155.30 5221.20 L 10154.60 5218.50 L 10153.50 5216.40 L 10149.10 5213.40 L 10147.10 5213.30 L 10144.70 5213.60 L 10138.10 5216.50 L 10133.50 5219.40 L 10127.90 5223.70 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="246" stroke="none" target-height="5190.3" target-width="1717.7002" target-x="8682" target-y="2293.8"></path><path d="M 9837.60 7852.40 L 9090.10 7852.40 L 9033.60 7119.70 L 9894.20 7119.70 L 9837.60 7852.40 Z" fill="#f0f0f0" fill-rule="nonzero" node-id="248" stroke="none" target-height="732.6997" target-width="860.6006" target-x="9033.6" target-y="7119.7"></path><path d="M 9949.80 7205.10 L 8977.80 7205.10 L 8950.10 6957.70 L 9977.70 6957.70 L 9949.80 7205.10 Z" fill="#f5f5f5" fill-rule="nonzero" node-id="250" stroke="none" target-height="247.3999" target-width="1027.6006" target-x="8950.1" target-y="6957.7"></path><path d="M 2963.90 489.00 L 1627.00 489.00 L 1627.00 1957.10 L 2963.90 1957.10 L 2963.90 489.00 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="252" stroke="none" target-height="1468.1" target-width="1336.8999" target-x="1627" target-y="489"></path><path d="M 3108.70 489.00 L 1727.60 489.00 L 1727.60 1957.10 L 3108.70 1957.10 L 3108.70 489.00 Z" fill="#f5f5f5" fill-rule="nonzero" node-id="254" stroke="none" target-height="1468.1" target-width="1381.1" target-x="1727.6" target-y="489"></path><path d="M 2993.00 1841.40 L 2993.00 604.70 L 1843.30 604.70 L 1843.30 1841.40 L 2993.00 1841.40 Z" fill="#ffffff" fill-rule="nonzero" node-id="256" stroke="none" target-height="1236.7" target-width="1149.7" target-x="1843.3" target-y="604.7"></path><path d="M 2714.20 1541.30 L 2714.20 904.20 L 2121.90 904.20 L 2121.90 1541.30 L 2714.20 1541.30 Z" fill="#f5f5f5" fill-rule="nonzero" node-id="258" stroke="none" target-height="637.10004" target-width="592.30005" target-x="2121.9" target-y="904.2"></path><path d="M 9780.60 660.00 L 8538.70 660.00 L 8538.70 2023.90 L 9780.60 2023.90 L 9780.60 660.00 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="260" stroke="none" target-height="1363.9" target-width="1241.8994" target-x="8538.7" target-y="660"></path><path d="M 9915.20 660.00 L 8632.40 660.00 L 8632.40 2023.90 L 9915.20 2023.90 L 9915.20 660.00 Z" fill="#f5f5f5" fill-rule="nonzero" node-id="262" stroke="none" target-height="1363.9" target-width="1282.7998" target-x="8632.4" target-y="660"></path><path d="M 9807.80 1916.30 L 9807.80 767.60 L 8740.00 767.60 L 8740.00 1916.30 L 9807.80 1916.30 Z" fill="#ffffff" fill-rule="nonzero" node-id="264" stroke="none" target-height="1148.7001" target-width="1067.7998" target-x="8740" target-y="767.6"></path><path d="M 9273.80 1675.20 L 9287.60 1674.90 L 9301.30 1674.00 L 9314.70 1672.70 L 9327.90 1670.80 L 9340.90 1668.40 L 9353.90 1665.40 L 9366.70 1662.00 L 9379.20 1658.10 L 9391.40 1653.80 L 9403.50 1649.00 L 9415.40 1643.70 L 9427.00 1637.90 L 9438.30 1631.80 L 9449.30 1625.20 L 9460.10 1618.20 L 9470.60 1610.80 L 9480.80 1603.10 L 9490.60 1594.90 L 9500.20 1586.40 L 9509.40 1577.60 L 9518.30 1568.30 L 9526.80 1558.80 L 9534.90 1548.90 L 9542.70 1538.70 L 9550.10 1528.20 L 9557.10 1517.50 L 9563.60 1506.40 L 9569.80 1495.10 L 9575.50 1483.50 L 9580.80 1471.60 L 9585.60 1459.60 L 9590.00 1447.30 L 9593.90 1434.80 L 9597.30 1422.10 L 9600.20 1409.10 L 9602.60 1396.10 L 9604.50 1382.80 L 9605.90 1369.40 L 9606.70 1355.80 L 9607.00 1341.90 L 9606.70 1328.10 L 9605.90 1314.40 L 9604.50 1301.00 L 9602.60 1287.80 L 9600.20 1274.80 L 9597.30 1261.80 L 9593.90 1249.00 L 9590.00 1236.50 L 9585.60 1224.30 L 9580.80 1212.20 L 9575.50 1200.30 L 9569.80 1188.80 L 9563.60 1177.40 L 9557.10 1166.40 L 9550.10 1155.60 L 9542.70 1145.10 L 9534.90 1134.90 L 9526.80 1125.10 L 9518.30 1115.50 L 9509.40 1106.30 L 9500.20 1097.40 L 9490.60 1088.90 L 9480.80 1080.80 L 9470.60 1073.00 L 9460.10 1065.60 L 9449.30 1058.60 L 9438.30 1052.10 L 9427.00 1045.90 L 9415.40 1040.20 L 9403.50 1034.90 L 9391.40 1030.10 L 9379.20 1025.70 L 9366.70 1021.80 L 9353.90 1018.40 L 9340.90 1015.50 L 9327.90 1013.10 L 9314.70 1011.20 L 9301.30 1009.80 L 9287.60 1009.00 L 9273.80 1008.70 L 9259.90 1009.00 L 9246.30 1009.80 L 9232.90 1011.20 L 9219.70 1013.10 L 9206.60 1015.50 L 9193.60 1018.40 L 9180.90 1021.80 L 9168.40 1025.70 L 9156.10 1030.10 L 9144.10 1034.90 L 9132.20 1040.20 L 9120.60 1045.90 L 9109.30 1052.10 L 9098.20 1058.60 L 9087.50 1065.60 L 9077.00 1073.00 L 9066.80 1080.80 L 9056.90 1088.90 L 9047.40 1097.40 L 9038.20 1106.30 L 9029.30 1115.50 L 9020.80 1125.10 L 9012.60 1134.90 L 9004.90 1145.10 L 8997.50 1155.60 L 8990.50 1166.40 L 8983.90 1177.40 L 8977.80 1188.80 L 8972.10 1200.30 L 8966.70 1212.20 L 8961.90 1224.30 L 8957.60 1236.50 L 8953.70 1249.00 L 8950.30 1261.80 L 8947.30 1274.80 L 8944.90 1287.80 L 8943.00 1301.00 L 8941.70 1314.40 L 8940.90 1328.10 L 8940.60 1341.90 L 8940.90 1355.80 L 8941.70 1369.40 L 8943.00 1382.80 L 8944.90 1396.10 L 8947.30 1409.10 L 8950.30 1422.10 L 8953.70 1434.80 L 8957.60 1447.30 L 8961.90 1459.60 L 8966.70 1471.60 L 8972.10 1483.50 L 8977.80 1495.10 L 8983.90 1506.40 L 8990.50 1517.50 L 8997.50 1528.20 L 9004.90 1538.70 L 9012.60 1548.90 L 9020.80 1558.80 L 9029.30 1568.30 L 9038.20 1577.60 L 9047.40 1586.40 L 9056.90 1594.90 L 9066.80 1603.10 L 9077.00 1610.80 L 9087.50 1618.20 L 9098.20 1625.20 L 9109.30 1631.80 L 9120.60 1637.90 L 9132.20 1643.70 L 9144.10 1649.00 L 9156.10 1653.80 L 9168.40 1658.10 L 9180.90 1662.00 L 9193.60 1665.40 L 9206.60 1668.40 L 9219.70 1670.80 L 9232.90 1672.70 L 9246.30 1674.00 L 9259.90 1674.90 L 9273.80 1675.20 Z" fill="#f5f5f5" fill-rule="nonzero" node-id="266" stroke="none" target-height="666.49994" target-width="666.4004" target-x="8940.6" target-y="1008.7"></path><path d="M 4352.80 1364.40 L 3361.00 1364.40 L 3361.00 2453.50 L 4352.80 2453.50 L 4352.80 1364.40 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="268" stroke="none" target-height="1089.1" target-width="991.7998" target-x="3361" target-y="1364.4"></path><path d="M 4460.10 1364.40 L 3435.70 1364.40 L 3435.70 2453.50 L 4460.10 2453.50 L 4460.10 1364.40 Z" fill="#f5f5f5" fill-rule="nonzero" node-id="270" stroke="none" target-height="1089.1" target-width="1024.4001" target-x="3435.7" target-y="1364.4"></path><path d="M 4374.30 2367.60 L 4374.30 1450.20 L 3521.60 1450.20 L 3521.60 2367.60 L 4374.30 2367.60 Z" fill="#ffffff" fill-rule="nonzero" node-id="272" stroke="none" target-height="917.40015" target-width="852.6997" target-x="3521.6" target-y="1450.2"></path><path d="M 3948.00 1676.80 L 3714.80 2080.90 L 4181.30 2080.90 L 3948.00 1676.80 Z" fill="#f5f5f5" fill-rule="nonzero" node-id="274" stroke="none" target-height="404.09985" target-width="466.49976" target-x="3714.8" target-y="1676.8"></path><path d="M 5963.10 8886.40 L 6122.80 8886.30 L 6435.90 8885.00 L 6591.50 8883.90 L 6895.10 8880.90 L 7045.70 8879.00 L 7338.30 8874.30 L 7483.20 8871.50 L 7763.20 8865.20 L 7901.60 8861.60 L 8167.50 8853.80 L 8298.60 8849.50 L 8548.80 8840.30 L 8671.90 8835.30 L 8904.80 8824.80 L 9019.10 8819.10 L 9233.30 8807.30 L 9337.90 8801.00 L 9531.70 8788.20 L 9626.00 8781.30 L 9714.70 8774.30 L 9798.00 8767.40 L 9881.10 8759.90 L 9958.20 8752.50 L 10029.60 8745.10 L 10083.70 8739.10 L 10134.00 8733.20 L 10180.90 8727.30 L 10224.40 8721.50 L 10269.00 8715.10 L 10309.60 8708.90 L 10346.50 8702.70 L 10379.90 8696.70 L 10407.90 8691.20 L 10433.10 8685.90 L 10455.70 8680.70 L 10475.90 8675.70 L 10493.80 8670.80 L 10510.40 8665.90 L 10524.70 8661.10 L 10537.00 8656.60 L 10547.60 8652.20 L 10556.50 8648.00 L 10563.90 8644.00 L 10570.50 8639.90 L 10575.70 8636.10 L 10582.90 8629.00 L 10585.10 8625.70 L 10586.60 8622.50 L 10587.50 8619.40 L 10587.80 8616.40 L 10587.50 8613.40 L 10586.60 8610.30 L 10585.10 8607.10 L 10582.90 8603.80 L 10579.80 8600.40 L 10570.50 8592.90 L 10563.90 8588.80 L 10556.50 8584.80 L 10547.60 8580.60 L 10524.70 8571.70 L 10510.40 8566.90 L 10475.90 8557.10 L 10455.70 8552.10 L 10433.10 8546.90 L 10407.90 8541.60 L 10346.50 8530.10 L 10269.00 8517.70 L 10224.40 8511.30 L 10180.90 8505.50 L 10083.70 8493.70 L 10029.60 8487.70 L 9958.20 8480.30 L 9881.10 8472.90 L 9798.00 8465.40 L 9714.70 8458.50 L 9626.00 8451.60 L 9531.70 8444.70 L 9337.90 8431.80 L 9233.30 8425.50 L 9019.10 8413.70 L 8904.80 8408.10 L 8671.90 8397.50 L 8548.80 8392.50 L 8298.60 8383.30 L 8167.50 8379.00 L 7901.60 8371.20 L 7763.20 8367.60 L 7483.20 8361.30 L 7193.40 8356.10 L 7045.70 8353.80 L 6744.50 8350.20 L 6591.50 8348.90 L 6280.40 8347.00 L 6122.80 8346.60 L 5963.10 8346.40 L 5645.80 8347.00 L 5490.20 8347.80 L 5181.60 8350.20 L 5031.00 8351.90 L 4732.70 8356.10 L 4587.80 8358.50 L 4301.30 8364.40 L 4024.60 8371.20 L 3889.80 8375.00 L 3627.60 8383.30 L 3500.50 8387.80 L 3254.30 8397.50 L 3135.60 8402.70 L 2907.10 8413.70 L 2797.70 8419.60 L 2588.20 8431.80 L 2488.80 8438.20 L 2394.40 8444.70 L 2300.10 8451.60 L 2211.50 8458.50 L 2128.20 8465.40 L 2045.00 8472.90 L 1967.90 8480.30 L 1896.50 8487.70 L 1842.50 8493.70 L 1745.30 8505.50 L 1701.80 8511.30 L 1657.20 8517.70 L 1616.60 8524.00 L 1579.70 8530.10 L 1546.30 8536.10 L 1518.30 8541.60 L 1493.00 8546.90 L 1450.20 8557.10 L 1432.30 8562.00 L 1415.80 8566.90 L 1401.40 8571.70 L 1389.10 8576.30 L 1378.60 8580.60 L 1369.70 8584.80 L 1362.20 8588.80 L 1355.70 8592.90 L 1350.40 8596.70 L 1346.40 8600.40 L 1343.30 8603.80 L 1339.50 8610.30 L 1338.70 8613.40 L 1338.40 8616.40 L 1338.70 8619.40 L 1339.50 8622.50 L 1341.00 8625.70 L 1343.30 8629.00 L 1346.40 8632.40 L 1350.40 8636.10 L 1355.70 8639.90 L 1362.20 8644.00 L 1369.70 8648.00 L 1378.60 8652.20 L 1389.10 8656.60 L 1401.40 8661.10 L 1415.80 8665.90 L 1432.30 8670.80 L 1450.20 8675.70 L 1470.40 8680.70 L 1493.00 8685.90 L 1518.30 8691.20 L 1546.30 8696.70 L 1579.70 8702.70 L 1616.60 8708.90 L 1657.20 8715.10 L 1701.80 8721.50 L 1745.30 8727.30 L 1842.50 8739.10 L 1896.50 8745.10 L 1967.90 8752.50 L 2045.00 8759.90 L 2128.20 8767.40 L 2211.50 8774.30 L 2300.10 8781.30 L 2394.40 8788.20 L 2588.20 8801.00 L 2692.90 8807.30 L 2907.10 8819.10 L 3021.30 8824.80 L 3254.30 8835.30 L 3377.30 8840.30 L 3627.60 8849.50 L 3758.70 8853.80 L 4024.60 8861.60 L 4162.90 8865.20 L 4443.00 8871.50 L 4587.80 8874.30 L 4880.50 8879.00 L 5031.00 8880.90 L 5334.70 8883.90 L 5490.20 8885.00 L 5803.40 8886.30 L 5963.10 8886.40 Z" fill="#f5f5f5" fill-rule="nonzero" node-id="276" stroke="none" target-height="540" target-width="9249.399" target-x="1338.4" target-y="8346.4"></path><path d="M 8330.40 2586.30 L 8093.80 2490.90 L 8060.60 2550.30 L 8037.30 2591.30 L 7978.20 2693.20 L 7944.20 2750.10 L 7908.50 2808.40 L 7872.20 2866.10 L 7853.90 2894.30 L 7836.00 2921.20 L 7818.10 2947.50 L 7801.10 2971.80 L 7784.00 2995.10 L 7768.30 3015.80 L 7757.60 3029.20 L 7738.50 3051.40 L 7729.00 3061.50 L 7720.40 3069.80 L 7712.80 3076.50 L 7706.60 3081.30 L 7701.10 3084.90 L 7692.10 3089.10 L 7671.20 3096.10 L 7622.00 3113.60 L 7473.20 3168.60 L 7441.10 3180.10 L 7376.20 3202.50 L 7322.70 3219.70 L 7281.50 3231.80 L 7261.00 3237.40 L 7241.30 3242.30 L 7222.50 3246.60 L 7203.60 3250.50 L 7185.90 3253.70 L 7169.30 3256.10 L 7165.00 3261.70 L 7160.00 3269.10 L 7155.10 3277.20 L 7144.90 3296.40 L 7135.30 3317.40 L 7131.10 3328.10 L 7127.20 3338.70 L 7124.00 3348.70 L 7121.40 3358.40 L 7119.70 3366.90 L 7119.00 3372.50 L 7119.00 3381.10 L 7131.50 3385.00 L 7144.80 3388.50 L 7158.90 3391.80 L 7173.80 3394.70 L 7188.50 3397.10 L 7203.90 3399.20 L 7219.90 3400.90 L 7236.60 3402.40 L 7258.70 3403.70 L 7281.50 3404.60 L 7305.30 3404.80 L 7329.00 3404.60 L 7353.20 3403.80 L 7377.90 3402.60 L 7402.60 3400.90 L 7427.40 3398.80 L 7452.40 3396.20 L 7477.30 3393.30 L 7526.70 3386.50 L 7551.20 3382.50 L 7598.80 3373.80 L 7622.30 3369.00 L 7666.60 3358.90 L 7688.30 3353.50 L 7708.80 3347.90 L 7728.20 3342.40 L 7747.50 3336.50 L 7765.20 3330.60 L 7781.40 3324.80 L 7797.60 3318.60 L 7811.80 3312.60 L 7824.30 3306.80 L 7841.90 3297.40 L 7854.80 3289.00 L 7866.10 3280.30 L 7877.60 3270.80 L 7889.40 3260.50 L 7913.00 3238.00 L 7936.70 3213.40 L 7948.80 3200.00 L 7964.50 3181.90 L 7980.30 3162.90 L 7996.20 3142.90 L 8027.50 3101.70 L 8058.40 3058.30 L 8088.60 3013.70 L 8110.70 2979.70 L 8132.20 2945.60 L 8153.20 2911.50 L 8192.70 2844.70 L 8228.50 2781.40 L 8260.10 2723.50 L 8297.50 2652.10 L 8315.40 2616.90 L 8330.40 2586.30 Z" fill="#e58a7b" fill-rule="nonzero" node-id="278" stroke="none" target-height="913.90015" target-width="1211.4004" target-x="7119" target-y="2490.9"></path><path d="M 8403.20 2456.80 L 8405.10 2464.90 L 8406.60 2473.40 L 8408.40 2491.20 L 8408.60 2500.70 L 8408.30 2512.30 L 8407.50 2524.30 L 8406.10 2536.80 L 8401.60 2562.40 L 8395.20 2588.60 L 8391.30 2602.20 L 8385.60 2620.00 L 8379.30 2638.10 L 8372.30 2656.40 L 8364.80 2674.50 L 8356.90 2692.60 L 8340.00 2728.50 L 8322.30 2763.20 L 8308.50 2788.50 L 8295.00 2812.40 L 8281.40 2835.50 L 8256.10 2876.40 L 8234.30 2909.80 L 8208.30 2947.40 L 7895.10 2771.90 L 7923.80 2708.00 L 7944.00 2664.60 L 7968.20 2613.80 L 7995.60 2558.20 L 8010.20 2529.40 L 8025.40 2500.00 L 8033.40 2485.10 L 8041.50 2471.10 L 8049.80 2458.00 L 8058.30 2445.60 L 8066.90 2434.00 L 8076.10 2422.40 L 8085.40 2411.60 L 8094.70 2401.60 L 8104.10 2392.30 L 8113.50 2383.70 L 8123.50 2375.40 L 8133.50 2367.70 L 8143.40 2360.80 L 8153.30 2354.60 L 8163.10 2349.00 L 8173.40 2343.80 L 8183.60 2339.30 L 8193.70 2335.40 L 8203.70 2332.20 L 8213.50 2329.60 L 8223.70 2327.50 L 8233.70 2326.10 L 8243.50 2325.20 L 8253.20 2325.00 L 8262.70 2325.40 L 8272.20 2326.30 L 8281.60 2327.90 L 8290.70 2330.10 L 8299.60 2332.80 L 8308.30 2336.20 L 8322.30 2343.00 L 8329.10 2347.00 L 8342.10 2356.40 L 8348.40 2361.80 L 8354.30 2367.30 L 8360.00 2373.40 L 8365.50 2379.80 L 8370.70 2386.70 L 8375.80 2394.10 L 8380.70 2402.00 L 8385.10 2409.90 L 8389.20 2418.20 L 8396.70 2436.40 L 8400.10 2446.30 L 8403.20 2456.80 Z" fill="#429cfd" fill-rule="nonzero" node-id="280" stroke="none" target-height="622.3999" target-width="513.4995" target-x="7895.1" target-y="2325"></path><g node-id="588"><path d="M 8403.20 2456.80 L 8405.10 2464.90 L 8406.60 2473.40 L 8408.40 2491.20 L 8408.60 2500.70 L 8408.30 2512.30 L 8407.50 2524.30 L 8406.10 2536.80 L 8401.60 2562.40 L 8395.20 2588.60 L 8391.30 2602.20 L 8385.60 2620.00 L 8379.30 2638.10 L 8372.30 2656.40 L 8364.80 2674.50 L 8356.90 2692.60 L 8340.00 2728.50 L 8322.30 2763.20 L 8308.50 2788.50 L 8295.00 2812.40 L 8281.40 2835.50 L 8256.10 2876.40 L 8234.30 2909.80 L 8208.30 2947.40 L 7895.10 2771.90 L 7923.80 2708.00 L 7944.00 2664.60 L 7968.20 2613.80 L 7995.60 2558.20 L 8010.20 2529.40 L 8025.40 2500.00 L 8033.40 2485.10 L 8041.50 2471.10 L 8049.80 2458.00 L 8058.30 2445.60 L 8066.90 2434.00 L 8076.10 2422.40 L 8085.40 2411.60 L 8094.70 2401.60 L 8104.10 2392.30 L 8113.50 2383.70 L 8123.50 2375.40 L 8133.50 2367.70 L 8143.40 2360.80 L 8153.30 2354.60 L 8163.10 2349.00 L 8173.40 2343.80 L 8183.60 2339.30 L 8193.70 2335.40 L 8203.70 2332.20 L 8213.50 2329.60 L 8223.70 2327.50 L 8233.70 2326.10 L 8243.50 2325.20 L 8253.20 2325.00 L 8262.70 2325.40 L 8272.20 2326.30 L 8281.60 2327.90 L 8290.70 2330.10 L 8299.60 2332.80 L 8308.30 2336.20 L 8322.30 2343.00 L 8329.10 2347.00 L 8342.10 2356.40 L 8348.40 2361.80 L 8354.30 2367.30 L 8360.00 2373.40 L 8365.50 2379.80 L 8370.70 2386.70 L 8375.80 2394.10 L 8380.70 2402.00 L 8385.10 2409.90 L 8389.20 2418.20 L 8396.70 2436.40 L 8400.10 2446.30 L 8403.20 2456.80 Z" fill="#000000" fill-opacity="0.2" fill-rule="nonzero" group-id="1" node-id="284" stroke="none" target-height="622.3999" target-width="513.4995" target-x="7895.1" target-y="2325"></path></g><g node-id="589"><path d="M 8019.40 2842.00 L 8142.70 2911.20 L 8164.40 2532.60 L 8157.90 2538.00 L 8151.40 2544.00 L 8145.00 2550.70 L 8138.50 2558.10 L 8126.50 2573.80 L 8114.60 2591.80 L 8107.30 2604.30 L 8100.00 2617.50 L 8086.20 2645.60 L 8073.50 2674.90 L 8064.60 2697.30 L 8056.30 2719.90 L 8048.50 2742.40 L 8034.90 2785.70 L 8029.20 2805.60 L 8019.40 2842.00 Z" fill="#000000" fill-opacity="0.2" fill-rule="nonzero" group-id="2" node-id="289" stroke="none" target-height="378.59985" target-width="145" target-x="8019.4" target-y="2532.6"></path></g><path d="M 8197.10 2334.20 L 8187.40 2358.10 L 8179.70 2379.00 L 8171.50 2402.80 L 8161.20 2434.70 L 8154.50 2457.10 L 8147.10 2482.80 L 8139.10 2512.20 L 8131.60 2541.80 L 8123.80 2574.70 L 8115.70 2611.30 L 8110.00 2638.50 L 8098.60 2698.60 L 8093.00 2731.60 L 8087.80 2764.10 L 8082.80 2798.40 L 8077.90 2834.70 L 8073.20 2873.00 L 8069.10 2910.70 L 8065.30 2950.20 L 8061.80 2991.70 L 8058.60 3035.20 L 8056.10 3078.00 L 8054.00 3122.70 L 8052.40 3169.30 L 8051.40 3217.90 L 8051.00 3256.10 L 8051.00 3295.40 L 8051.40 3336.00 L 8052.30 3377.80 L 8053.60 3420.80 L 8055.40 3463.00 L 8057.60 3506.40 L 8060.40 3550.90 L 8063.70 3596.70 L 8067.60 3643.80 L 8071.90 3689.90 L 8076.80 3737.20 L 8082.40 3785.70 L 8088.60 3835.50 L 8095.50 3886.50 L 9056.00 3886.50 L 9057.90 3858.10 L 9058.60 3838.10 L 9059.20 3806.80 L 9060.00 3627.40 L 9061.20 3511.70 L 9062.50 3447.50 L 9065.10 3358.60 L 9069.10 3263.40 L 9074.90 3163.10 L 9078.50 3112.10 L 9082.70 3059.70 L 9087.50 3005.90 L 9095.70 2924.90 L 9100.40 2883.40 L 9111.00 2799.60 L 9123.40 2714.80 L 9130.30 2671.70 L 9137.50 2628.90 L 9145.40 2585.80 L 9153.70 2542.50 L 9162.60 2498.90 L 9172.00 2455.60 L 9181.90 2412.20 L 9192.50 2368.70 L 9203.70 2325.10 L 9172.00 2319.20 L 9140.30 2313.60 L 9108.50 2308.50 L 9076.70 2303.80 L 9044.80 2299.50 L 9012.80 2295.60 L 8980.80 2292.20 L 8948.70 2289.10 L 8884.50 2284.30 L 8843.70 2282.20 L 8802.90 2280.60 L 8762.10 2279.40 L 8721.30 2278.70 L 8680.50 2278.40 L 8639.60 2278.70 L 8598.80 2279.40 L 8558.00 2280.60 L 8517.20 2282.20 L 8476.40 2284.30 L 8420.00 2290.90 L 8391.80 2294.90 L 8363.80 2299.20 L 8335.80 2304.00 L 8307.90 2309.20 L 8280.10 2314.80 L 8252.30 2320.90 L 8224.70 2327.30 L 8197.10 2334.20 Z" fill="#429cfd" fill-rule="nonzero" node-id="292" stroke="none" target-height="1608.1001" target-width="1152.7002" target-x="8051" target-y="2278.4"></path><g node-id="590"><path d="M 8197.10 2334.20 L 8187.40 2358.10 L 8179.70 2379.00 L 8171.50 2402.80 L 8161.20 2434.70 L 8154.50 2457.10 L 8147.10 2482.80 L 8139.10 2512.20 L 8131.60 2541.80 L 8123.80 2574.70 L 8115.70 2611.30 L 8110.00 2638.50 L 8098.60 2698.60 L 8093.00 2731.60 L 8087.80 2764.10 L 8082.80 2798.40 L 8077.90 2834.70 L 8073.20 2873.00 L 8069.10 2910.70 L 8065.30 2950.20 L 8061.80 2991.70 L 8058.60 3035.20 L 8056.10 3078.00 L 8054.00 3122.70 L 8052.40 3169.30 L 8051.40 3217.90 L 8051.00 3256.10 L 8051.00 3295.40 L 8051.40 3336.00 L 8052.30 3377.80 L 8053.60 3420.80 L 8055.40 3463.00 L 8057.60 3506.40 L 8060.40 3550.90 L 8063.70 3596.70 L 8067.60 3643.80 L 8071.90 3689.90 L 8076.80 3737.20 L 8082.40 3785.70 L 8088.60 3835.50 L 8095.50 3886.50 L 9056.00 3886.50 L 9057.90 3858.10 L 9058.60 3838.10 L 9059.20 3806.80 L 9060.00 3627.40 L 9061.20 3511.70 L 9062.50 3447.50 L 9065.10 3358.60 L 9069.10 3263.40 L 9074.90 3163.10 L 9078.50 3112.10 L 9082.70 3059.70 L 9087.50 3005.90 L 9095.70 2924.90 L 9100.40 2883.40 L 9111.00 2799.60 L 9123.40 2714.80 L 9130.30 2671.70 L 9137.50 2628.90 L 9145.40 2585.80 L 9153.70 2542.50 L 9162.60 2498.90 L 9172.00 2455.60 L 9181.90 2412.20 L 9192.50 2368.70 L 9203.70 2325.10 L 9172.00 2319.20 L 9140.30 2313.60 L 9108.50 2308.50 L 9076.70 2303.80 L 9044.80 2299.50 L 9012.80 2295.60 L 8980.80 2292.20 L 8948.70 2289.10 L 8884.50 2284.30 L 8843.70 2282.20 L 8802.90 2280.60 L 8762.10 2279.40 L 8721.30 2278.70 L 8680.50 2278.40 L 8639.60 2278.70 L 8598.80 2279.40 L 8558.00 2280.60 L 8517.20 2282.20 L 8476.40 2284.30 L 8420.00 2290.90 L 8391.80 2294.90 L 8363.80 2299.20 L 8335.80 2304.00 L 8307.90 2309.20 L 8280.10 2314.80 L 8252.30 2320.90 L 8224.70 2327.30 L 8197.10 2334.20 Z" fill="#000000" fill-opacity="0.2" fill-rule="nonzero" group-id="3" node-id="296" stroke="none" target-height="1608.1001" target-width="1152.7002" target-x="8051" target-y="2278.4"></path></g><g node-id="591"><path d="M 7974.30 8637.90 L 7974.50 8639.00 L 7975.10 8640.10 L 7976.40 8641.40 L 7987.00 8646.30 L 7995.70 8648.70 L 8007.90 8651.30 L 8041.00 8656.50 L 8084.40 8661.40 L 8121.00 8664.60 L 8164.60 8667.90 L 8210.50 8670.70 L 8262.60 8673.50 L 8316.70 8675.90 L 8376.20 8678.10 L 8437.40 8679.90 L 8503.10 8681.50 L 8570.30 8682.70 L 8641.10 8683.60 L 8787.70 8684.40 L 8934.40 8683.60 L 9072.60 8681.50 L 9138.50 8679.90 L 9199.90 8678.10 L 9314.10 8673.50 L 9366.50 8670.70 L 9412.80 8667.90 L 9456.80 8664.60 L 9493.70 8661.40 L 9537.70 8656.50 L 9554.50 8654.10 L 9571.30 8651.30 L 9583.80 8648.70 L 9598.90 8644.10 L 9602.70 8642.30 L 9605.00 8640.60 L 9606.10 8639.20 L 9606.50 8637.90 L 9606.30 8636.80 L 9605.70 8635.60 L 9604.40 8634.30 L 9602.20 8632.90 L 9598.80 8631.30 L 9593.80 8629.50 L 9572.90 8624.50 L 9556.40 8621.60 L 9520.00 8616.90 L 9496.50 8614.40 L 9459.90 8611.20 L 9416.30 8608.00 L 9370.40 8605.10 L 9318.30 8602.40 L 9264.20 8600.00 L 9204.70 8597.90 L 9077.80 8594.50 L 8939.80 8592.30 L 8793.20 8591.60 L 8646.40 8592.30 L 8575.50 8593.30 L 8508.20 8594.50 L 8442.30 8596.10 L 8380.90 8597.90 L 8321.10 8600.10 L 8266.70 8602.50 L 8214.30 8605.20 L 8168.00 8608.10 L 8124.10 8611.30 L 8087.20 8614.50 L 8043.20 8619.40 L 8026.40 8621.70 L 8009.60 8624.50 L 7997.00 8627.20 L 7988.00 8629.60 L 7982.00 8631.70 L 7978.10 8633.50 L 7975.90 8635.20 L 7974.70 8636.60 L 7974.30 8637.90 Z" fill="#e8ecf1" fill-rule="nonzero" group-id="4" node-id="301" stroke="none" target-height="92.80078" target-width="1632.2002" target-x="7974.3" target-y="8591.6"></path></g><path d="M 8615.50 7961.00 L 8445.10 8027.00 L 8591.90 8406.10 L 8762.20 8340.10 L 8615.50 7961.00 Z" fill="#e58a7b" fill-rule="nonzero" node-id="304" stroke="none" target-height="445.0996" target-width="317.1006" target-x="8445.1" target-y="7961"></path><path d="M 9375.20 8454.50 L 9195.50 8454.50 L 9129.70 8038.70 L 9309.30 8038.70 L 9375.20 8454.50 Z" fill="#e58a7b" fill-rule="nonzero" node-id="306" stroke="none" target-height="415.7998" target-width="245.5" target-x="9129.7" target-y="8038.7"></path><path d="M 9183.10 8433.70 L 9384.90 8433.70 L 9387.50 8433.90 L 9390.00 8434.60 L 9394.50 8437.20 L 9396.30 8439.10 L 9397.80 8441.20 L 9398.80 8443.60 L 9399.50 8446.10 L 9425.50 8605.70 L 9425.70 8609.70 L 9424.50 8617.60 L 9423.10 8621.40 L 9415.80 8630.90 L 9412.60 8633.30 L 9409.10 8635.20 L 9405.30 8636.60 L 9401.40 8637.40 L 9397.30 8637.60 L 9369.10 8637.00 L 9269.10 8633.00 L 9239.30 8632.40 L 9204.40 8632.20 L 9177.70 8632.50 L 9145.60 8633.30 L 9033.30 8636.70 L 8996.00 8637.60 L 8961.60 8637.90 L 8954.40 8637.60 L 8941.80 8636.00 L 8930.90 8632.90 L 8925.90 8630.90 L 8921.40 8628.60 L 8917.40 8626.10 L 8913.50 8623.30 L 8910.00 8620.30 L 8907.00 8617.20 L 8904.30 8614.00 L 8898.50 8604.60 L 8896.60 8599.80 L 8895.10 8594.80 L 8894.30 8590.00 L 8894.10 8580.60 L 8894.70 8576.20 L 8895.80 8572.20 L 8897.50 8568.30 L 8899.50 8564.90 L 8904.10 8560.10 L 8906.50 8558.60 L 8909.10 8557.40 L 8911.90 8556.50 L 8935.20 8551.20 L 8956.30 8545.90 L 8975.30 8540.50 L 8994.10 8534.70 L 9011.20 8528.80 L 9041.60 8516.70 L 9067.70 8504.40 L 9079.90 8497.80 L 9101.10 8484.90 L 9111.10 8478.20 L 9128.90 8465.00 L 9153.30 8444.90 L 9156.50 8442.30 L 9159.90 8440.10 L 9163.50 8438.20 L 9171.10 8435.30 L 9179.00 8433.90 L 9183.10 8433.70 Z" fill="#263238" fill-rule="nonzero" node-id="308" stroke="none" target-height="204.2002" target-width="531.6006" target-x="8894.1" target-y="8433.7"></path><path d="M 8586.80 8372.20 L 8777.70 8316.40 L 8782.80 8315.80 L 8785.40 8316.20 L 8787.90 8317.00 L 8790.30 8318.20 L 8792.30 8319.80 L 8795.50 8324.00 L 8867.10 8467.10 L 8868.50 8470.80 L 8869.60 8478.70 L 8869.30 8482.60 L 8868.40 8486.50 L 8867.00 8490.20 L 8865.00 8493.70 L 8859.70 8499.60 L 8856.50 8501.90 L 8853.00 8503.70 L 8849.20 8505.00 L 8722.20 8539.50 L 8646.50 8561.10 L 8631.50 8565.60 L 8601.40 8575.90 L 8578.80 8584.20 L 8511.10 8610.10 L 8488.60 8618.30 L 8466.40 8625.70 L 8460.40 8627.40 L 8453.60 8629.10 L 8445.70 8630.60 L 8433.60 8632.50 L 8406.00 8635.50 L 8376.10 8637.40 L 8360.80 8638.10 L 8330.90 8638.90 L 8303.10 8639.20 L 8279.10 8639.30 L 8272.20 8639.10 L 8259.70 8637.40 L 8254.20 8636.00 L 8248.60 8634.10 L 8243.50 8632.00 L 8234.50 8627.10 L 8224.10 8619.00 L 8216.10 8609.70 L 8212.90 8604.90 L 8210.40 8600.00 L 8208.30 8594.90 L 8206.80 8590.00 L 8205.80 8585.30 L 8205.40 8580.50 L 8205.50 8576.20 L 8206.10 8572.20 L 8206.90 8569.30 L 8208.00 8566.70 L 8209.40 8564.50 L 8211.10 8562.50 L 8213.00 8560.80 L 8215.20 8559.50 L 8217.80 8558.60 L 8220.70 8558.00 L 8293.40 8550.60 L 8309.10 8548.20 L 8316.30 8546.50 L 8323.40 8544.50 L 8330.60 8542.10 L 8338.30 8539.00 L 8373.40 8522.80 L 8391.00 8514.10 L 8408.30 8504.80 L 8425.30 8495.00 L 8441.90 8484.60 L 8458.20 8473.80 L 8474.20 8462.40 L 8489.80 8450.50 L 8505.00 8438.00 L 8519.80 8425.20 L 8534.20 8411.80 L 8552.20 8393.60 L 8556.50 8389.60 L 8561.00 8385.90 L 8565.80 8382.50 L 8575.90 8376.60 L 8586.80 8372.20 Z" fill="#263238" fill-rule="nonzero" node-id="310" stroke="none" target-height="323.5" target-width="664.1992" target-x="8205.4" target-y="8315.8"></path><path d="M 7199.30 3255.10 L 7001.80 3172.40 L 6920.50 3391.30 L 6940.80 3394.10 L 6961.10 3396.50 L 6981.50 3398.30 L 7001.80 3399.50 L 7022.20 3400.30 L 7042.60 3400.60 L 7063.10 3400.30 L 7083.40 3399.50 L 7103.80 3398.30 L 7124.10 3396.50 L 7144.50 3394.10 L 7164.80 3391.30 L 7199.30 3255.10 Z" fill="#e58a7b" fill-rule="nonzero" node-id="312" stroke="none" target-height="228.2002" target-width="278.7998" target-x="6920.5" target-y="3172.4"></path><path d="M 6854.90 3166.60 L 6849.70 3312.10 L 6920.50 3390.80 L 7001.80 3171.90 L 6854.90 3166.60 Z" fill="#e58a7b" fill-rule="nonzero" node-id="314" stroke="none" target-height="224.19995" target-width="152.09961" target-x="6849.7" target-y="3166.6"></path><path d="M 8638.80 7993.20 L 8467.90 8059.20 L 8543.60 8255.20 L 8714.50 8189.20 L 8638.80 7993.20 Z" fill="#cf6f64" fill-rule="nonzero" node-id="316" stroke="none" target-height="262" target-width="246.59961" target-x="8467.9" target-y="7993.2"></path><path d="M 9309.30 8038.70 L 9129.70 8038.70 L 9163.80 8253.10 L 9343.40 8253.10 L 9309.30 8038.70 Z" fill="#cf6f64" fill-rule="nonzero" node-id="318" stroke="none" target-height="214.39941" target-width="213.7002" target-x="9129.7" target-y="8038.7"></path><g node-id="592"><path d="M 8064.90 3613.40 L 8070.80 3678.90 L 8077.80 3746.00 L 8808.60 3508.90 L 8825.00 3503.10 L 8841.10 3496.80 L 8857.00 3490.00 L 8872.60 3482.60 L 8888.00 3474.70 L 8903.00 3466.30 L 8917.80 3457.50 L 8932.20 3448.10 L 8946.30 3438.30 L 8960.10 3427.90 L 8973.50 3417.20 L 8986.50 3406.00 L 8999.20 3394.30 L 9011.50 3382.20 L 9023.40 3369.70 L 9034.90 3356.70 L 9045.90 3343.40 L 9056.50 3329.70 L 9066.70 3315.50 L 9071.70 3217.50 L 9074.90 3167.00 L 9082.60 3063.40 L 9087.00 3010.90 L 9090.50 2974.50 L 9098.90 2898.80 L 8064.90 3613.40 Z" fill="#000000" fill-opacity="0.2" fill-rule="nonzero" group-id="5" node-id="322" stroke="none" target-height="847.19995" target-width="1034.0005" target-x="8064.9" target-y="2898.8"></path></g><path d="M 8298.20 2831.00 L 8274.40 2828.20 L 8276.20 2815.40 L 8278.50 2802.90 L 8281.40 2790.70 L 8284.80 2778.80 L 8288.70 2767.00 L 8293.10 2755.60 L 8297.90 2744.50 L 8303.10 2733.80 L 8308.90 2723.30 L 8315.00 2713.20 L 8321.40 2703.60 L 8328.20 2694.40 L 8335.50 2685.40 L 8343.00 2677.00 L 8350.70 2669.10 L 8358.80 2661.80 L 8367.20 2654.70 L 8375.80 2648.30 L 8384.60 2642.50 L 8393.60 2637.20 L 8402.90 2632.40 L 8412.30 2628.30 L 8421.80 2624.80 L 8431.50 2622.00 L 8439.30 2620.20 L 8447.20 2618.80 L 8463.10 2617.40 L 8471.20 2617.30 L 8471.20 2641.20 L 8462.90 2641.30 L 8454.80 2642.00 L 8446.80 2643.20 L 8438.70 2644.90 L 8430.70 2647.20 L 8422.90 2650.00 L 8415.20 2653.20 L 8407.60 2657.00 L 8400.10 2661.20 L 8392.70 2665.90 L 8385.50 2671.00 L 8378.60 2676.40 L 8371.80 2682.30 L 8358.70 2695.40 L 8346.70 2709.70 L 8335.60 2725.50 L 8330.40 2733.90 L 8325.70 2742.50 L 8321.20 2751.40 L 8317.10 2760.50 L 8309.70 2779.70 L 8306.60 2789.60 L 8303.90 2799.60 L 8301.60 2809.80 L 8299.70 2820.30 L 8298.20 2831.00 Z" fill="#ffffff" fill-rule="nonzero" node-id="325" stroke="none" target-height="213.69995" target-width="196.7998" target-x="8274.4" target-y="2617.3"></path><path d="M 8425.40 3051.20 L 8421.30 3051.20 L 8421.30 3027.30 L 8435.30 3027.00 L 8449.10 3025.00 L 8456.00 3023.40 L 8464.40 3020.90 L 8472.70 3017.80 L 8480.90 3014.10 L 8489.00 3009.80 L 8496.80 3005.10 L 8504.50 2999.90 L 8512.00 2994.10 L 8526.30 2981.10 L 8539.60 2966.40 L 8546.00 2958.40 L 8551.90 2950.10 L 8562.80 2932.30 L 8572.40 2913.20 L 8576.60 2903.30 L 8580.40 2893.00 L 8583.80 2882.40 L 8586.80 2871.70 L 8589.30 2860.80 L 8591.40 2849.60 L 8593.00 2838.20 L 8616.90 2841.10 L 8615.20 2853.50 L 8613.10 2865.80 L 8610.40 2877.70 L 8607.20 2889.40 L 8603.40 2901.00 L 8599.30 2912.20 L 8594.70 2923.10 L 8589.60 2933.70 L 8584.10 2944.10 L 8578.20 2954.10 L 8572.00 2963.60 L 8565.40 2972.70 L 8558.40 2981.60 L 8551.10 2989.90 L 8543.50 2997.80 L 8535.70 3005.20 L 8527.50 3012.20 L 8519.10 3018.70 L 8510.50 3024.60 L 8501.70 3029.90 L 8492.60 3034.80 L 8483.30 3039.00 L 8474.00 3042.70 L 8464.50 3045.70 L 8456.80 3047.60 L 8449.00 3049.20 L 8441.20 3050.30 L 8433.30 3051.00 L 8425.40 3051.20 Z" fill="#ffffff" fill-rule="nonzero" node-id="327" stroke="none" target-height="213" target-width="195.60059" target-x="8421.3" target-y="2838.2"></path><path d="M 8871.20 1824.70 L 8865.60 1847.20 L 8859.90 1872.20 L 8854.50 1898.00 L 8846.10 1944.30 L 8840.40 1982.80 L 8838.00 2002.40 L 8836.00 2022.00 L 8834.40 2041.60 L 8833.20 2061.30 L 8832.40 2080.80 L 8832.20 2099.90 L 8832.40 2114.30 L 8833.90 2142.10 L 8835.10 2155.50 L 8836.70 2168.90 L 8838.80 2181.90 L 8841.20 2194.30 L 8844.00 2206.20 L 8846.70 2215.80 L 8849.60 2224.90 L 8852.90 2233.60 L 8856.40 2241.90 L 8860.10 2249.80 L 8864.40 2257.70 L 8868.90 2265.00 L 8873.80 2271.90 L 8879.00 2278.30 L 8884.50 2284.30 L 8879.20 2293.80 L 8874.20 2301.20 L 8865.70 2312.10 L 8859.80 2318.90 L 8853.60 2325.40 L 8846.40 2332.40 L 8837.80 2339.90 L 8831.30 2345.30 L 8824.10 2350.70 L 8816.10 2356.30 L 8807.30 2362.10 L 8798.60 2367.40 L 8789.10 2372.60 L 8778.60 2377.90 L 8767.30 2383.10 L 8756.10 2387.80 L 8744.00 2392.30 L 8730.90 2396.70 L 8716.80 2400.80 L 8702.80 2404.40 L 8687.90 2407.70 L 8671.80 2410.60 L 8654.60 2413.10 L 8641.20 2414.70 L 8627.10 2415.90 L 8612.20 2416.90 L 8579.90 2417.70 L 8564.40 2417.40 L 8550.60 2416.80 L 8538.20 2415.80 L 8527.20 2414.60 L 8515.80 2412.80 L 8497.10 2408.50 L 8489.50 2406.00 L 8481.70 2403.00 L 8475.00 2399.90 L 8469.30 2396.60 L 8459.90 2389.50 L 8456.00 2385.50 L 8452.80 2381.60 L 8450.30 2377.60 L 8448.10 2373.30 L 8446.50 2368.90 L 8445.40 2364.50 L 8444.70 2360.10 L 8444.40 2354.00 L 8444.80 2348.00 L 8445.70 2341.90 L 8449.00 2330.10 L 8455.00 2316.20 L 8459.10 2308.70 L 8463.40 2301.70 L 8471.10 2290.80 L 8476.40 2284.30 L 8487.70 2281.50 L 8498.20 2278.40 L 8508.00 2275.10 L 8517.00 2271.60 L 8525.40 2268.00 L 8534.00 2263.80 L 8541.90 2259.50 L 8549.30 2255.00 L 8556.00 2250.40 L 8562.30 2245.70 L 8568.40 2240.50 L 8574.10 2235.10 L 8579.20 2229.70 L 8584.00 2224.10 L 8588.30 2218.30 L 8593.30 2210.80 L 8597.80 2203.00 L 8601.70 2195.10 L 8605.10 2186.90 L 8608.00 2178.50 L 8610.40 2170.00 L 8612.40 2161.20 L 8613.90 2152.30 L 8615.10 2143.30 L 8615.90 2134.20 L 8616.40 2115.40 L 8616.00 2102.80 L 8615.20 2090.10 L 8612.20 2064.40 L 8607.80 2038.70 L 8871.20 1824.70 Z" fill="#e58a7b" fill-rule="nonzero" node-id="329" stroke="none" target-height="593" target-width="440.0996" target-x="8444.4" target-y="1824.7"></path><path d="M 8763.80 1912.00 L 8608.10 2038.70 L 8610.70 2052.90 L 8612.90 2067.10 L 8614.50 2081.40 L 8615.70 2095.80 L 8616.40 2110.20 L 8619.60 2113.10 L 8622.90 2115.40 L 8626.20 2117.30 L 8633.10 2119.70 L 8637.60 2120.40 L 8642.30 2120.50 L 8647.10 2120.10 L 8652.20 2119.10 L 8657.00 2117.60 L 8662.00 2115.70 L 8667.20 2113.20 L 8672.50 2110.20 L 8679.20 2105.80 L 8686.10 2100.60 L 8693.10 2094.80 L 8706.30 2081.80 L 8712.80 2074.60 L 8725.00 2059.40 L 8736.10 2043.30 L 8741.10 2035.20 L 8745.70 2027.20 L 8749.90 2018.90 L 8756.70 2003.50 L 8759.30 1995.70 L 8761.30 1988.60 L 8764.20 1973.40 L 8765.40 1964.70 L 8766.10 1955.90 L 8766.50 1947.10 L 8766.40 1938.30 L 8766.00 1929.50 L 8765.10 1920.80 L 8763.80 1912.00 Z" fill="#cf6f64" fill-rule="nonzero" node-id="331" stroke="none" target-height="208.5" target-width="158.40039" target-x="8608.1" target-y="1912"></path><path d="M 8340.20 1823.00 L 8344.40 1830.00 L 8349.00 1836.70 L 8353.90 1843.10 L 8359.20 1849.30 L 8370.50 1860.70 L 8383.10 1871.10 L 8389.70 1875.70 L 8396.60 1880.00 L 8403.70 1883.90 L 8410.90 1887.50 L 8418.40 1890.70 L 8426.00 1893.50 L 8441.60 1897.90 L 8448.90 1899.20 L 8455.60 1899.80 L 8461.90 1899.70 L 8467.70 1899.10 L 8473.60 1897.90 L 8479.00 1896.10 L 8484.00 1893.90 L 8488.70 1891.20 L 8493.10 1888.00 L 8497.10 1884.40 L 8500.70 1880.40 L 8504.00 1876.00 L 8506.90 1871.30 L 8509.40 1866.30 L 8511.50 1861.00 L 8513.20 1855.40 L 8514.50 1849.60 L 8515.40 1843.70 L 8515.90 1837.50 L 8515.90 1831.10 L 8515.60 1824.70 L 8514.70 1818.20 L 8513.50 1811.60 L 8511.70 1804.90 L 8506.90 1791.00 L 8500.30 1776.50 L 8496.40 1769.30 L 8492.10 1762.10 L 8487.50 1755.10 L 8482.50 1748.30 L 8477.20 1741.70 L 8471.50 1735.40 L 8465.60 1729.50 L 8459.40 1724.10 L 8452.80 1719.10 L 8446.10 1714.70 L 8439.10 1710.90 L 8433.60 1708.40 L 8428.10 1706.40 L 8422.40 1704.80 L 8416.60 1703.60 L 8410.80 1703.00 L 8404.80 1702.80 L 8398.70 1703.10 L 8392.40 1704.00 L 8386.30 1705.50 L 8380.30 1707.50 L 8374.50 1710.00 L 8368.90 1712.90 L 8363.50 1716.30 L 8358.40 1720.10 L 8353.60 1724.30 L 8349.20 1728.80 L 8345.20 1733.70 L 8341.60 1738.90 L 8338.30 1744.30 L 8335.60 1750.00 L 8333.20 1755.90 L 8331.40 1762.00 L 8330.10 1768.20 L 8329.20 1774.40 L 8328.90 1780.70 L 8329.00 1787.10 L 8329.70 1793.40 L 8332.40 1805.70 L 8337.10 1817.40 L 8340.20 1823.00 Z" fill="#cf6f64" fill-rule="nonzero" node-id="333" stroke="none" target-height="197" target-width="187" target-x="8328.9" target-y="1702.8"></path><path d="M 8434.90 1402.50 L 8430.60 1404.70 L 8426.40 1407.40 L 8422.20 1410.70 L 8418.00 1414.50 L 8413.80 1418.90 L 8409.20 1424.60 L 8404.50 1431.00 L 8400.00 1438.10 L 8395.50 1446.20 L 8391.50 1454.20 L 8387.60 1462.90 L 8380.10 1482.30 L 8375.80 1495.70 L 8371.80 1509.90 L 8368.10 1525.10 L 8364.90 1540.30 L 8362.10 1556.00 L 8359.70 1572.30 L 8357.70 1588.70 L 8356.20 1605.20 L 8355.10 1622.00 L 8354.50 1638.80 L 8354.40 1655.40 L 8354.70 1671.90 L 8355.60 1688.40 L 8357.00 1704.40 L 8358.90 1719.90 L 8360.70 1731.60 L 8365.20 1753.50 L 8370.90 1774.20 L 8374.30 1784.00 L 8377.90 1793.10 L 8381.80 1801.70 L 8386.30 1810.10 L 8391.00 1817.80 L 8396.00 1824.80 L 8401.20 1831.20 L 8405.80 1836.00 L 8410.70 1840.30 L 8415.70 1844.10 L 8420.90 1847.40 L 8426.30 1850.20 L 8430.90 1852.00 L 8435.40 1853.10 L 8439.70 1853.60 L 8443.90 1853.40 L 8448.00 1852.70 L 8452.10 1851.40 L 8456.10 1849.60 L 8460.10 1847.30 L 8464.10 1844.30 L 8468.20 1840.60 L 8472.70 1835.90 L 8477.30 1830.30 L 8481.90 1823.80 L 8486.50 1816.40 L 8494.60 1800.80 L 8502.50 1782.10 L 8507.10 1769.20 L 8511.60 1755.20 L 8515.90 1740.20 L 8519.70 1725.10 L 8523.20 1709.40 L 8526.40 1693.00 L 8529.20 1676.50 L 8531.60 1659.70 L 8533.70 1642.60 L 8535.30 1625.50 L 8537.30 1591.40 L 8537.70 1574.40 L 8537.60 1557.90 L 8537.10 1541.70 L 8536.10 1525.60 L 8534.60 1510.30 L 8532.60 1495.80 L 8530.80 1484.80 L 8526.20 1464.90 L 8523.60 1455.90 L 8520.40 1446.80 L 8517.10 1438.60 L 8513.40 1431.10 L 8509.60 1424.30 L 8506.10 1419.00 L 8498.50 1410.20 L 8494.50 1406.50 L 8490.40 1403.40 L 8485.90 1400.70 L 8481.20 1398.50 L 8476.30 1396.90 L 8471.10 1395.90 L 8465.60 1395.40 L 8460.30 1395.50 L 8454.60 1396.20 L 8448.50 1397.50 L 8441.90 1399.60 L 8434.90 1402.50 Z" fill="#263238" fill-rule="nonzero" node-id="335" stroke="none" target-height="458.19995" target-width="183.2998" target-x="8354.4" target-y="1395.4"></path><path d="M 8898.30 1627.00 L 8886.00 1732.30 L 8882.00 1762.90 L 8879.00 1783.00 L 8875.80 1802.10 L 8872.40 1820.10 L 8869.50 1833.60 L 8863.00 1859.20 L 8855.20 1883.50 L 8850.80 1895.30 L 8845.90 1906.70 L 8840.70 1917.90 L 8835.00 1928.80 L 8828.80 1939.60 L 8822.00 1950.20 L 8814.60 1960.70 L 8806.80 1970.70 L 8798.40 1980.70 L 8789.10 1990.70 L 8771.50 2008.10 L 8755.50 2021.70 L 8747.10 2028.00 L 8738.40 2033.80 L 8729.40 2039.20 L 8710.80 2048.80 L 8701.20 2053.00 L 8681.40 2060.00 L 8671.20 2062.80 L 8660.80 2065.10 L 8650.50 2067.00 L 8640.10 2068.40 L 8629.70 2069.30 L 8619.20 2069.70 L 8598.30 2069.20 L 8587.80 2068.20 L 8577.40 2066.70 L 8567.10 2064.70 L 8556.90 2062.30 L 8546.70 2059.40 L 8536.70 2056.00 L 8526.90 2052.20 L 8517.30 2047.90 L 8498.80 2038.10 L 8489.90 2032.60 L 8481.20 2026.70 L 8472.80 2020.40 L 8464.80 2013.70 L 8457.00 2006.70 L 8449.50 1999.30 L 8442.40 1991.50 L 8435.60 1983.40 L 8429.20 1975.00 L 8423.30 1966.40 L 8417.70 1957.50 L 8412.60 1948.30 L 8407.90 1939.00 L 8403.60 1929.40 L 8399.70 1919.70 L 8396.30 1909.80 L 8393.40 1899.70 L 8390.90 1889.50 L 8386.20 1863.00 L 8384.10 1846.10 L 8382.30 1828.30 L 8381.10 1810.50 L 8380.50 1792.10 L 8380.30 1773.10 L 8380.80 1754.10 L 8381.70 1734.80 L 8383.30 1715.10 L 8385.50 1695.60 L 8388.30 1676.00 L 8391.70 1656.30 L 8398.00 1627.10 L 8401.70 1612.70 L 8405.80 1598.40 L 8410.30 1584.20 L 8415.20 1570.20 L 8420.50 1556.50 L 8426.20 1543.10 L 8432.30 1529.80 L 8438.90 1516.90 L 8445.80 1504.40 L 8453.20 1492.30 L 8461.10 1480.40 L 8469.40 1469.00 L 8478.10 1458.10 L 8487.30 1447.70 L 8495.00 1439.70 L 8503.00 1432.00 L 8511.30 1424.70 L 8519.90 1417.70 L 8528.80 1411.10 L 8538.00 1405.00 L 8547.50 1399.20 L 8557.40 1393.90 L 8567.60 1388.90 L 8587.70 1380.50 L 8597.30 1377.00 L 8607.00 1373.90 L 8616.80 1371.30 L 8626.80 1369.10 L 8636.70 1367.40 L 8646.80 1366.10 L 8656.90 1365.30 L 8667.10 1364.90 L 8677.30 1364.90 L 8687.40 1365.40 L 8697.70 1366.40 L 8707.80 1367.80 L 8717.80 1369.70 L 8727.70 1372.00 L 8737.50 1374.70 L 8747.20 1377.90 L 8756.70 1381.40 L 8766.00 1385.40 L 8775.20 1389.90 L 8784.10 1394.70 L 8792.90 1399.90 L 8801.40 1405.50 L 8809.70 1411.50 L 8817.70 1417.90 L 8825.40 1424.50 L 8832.80 1431.50 L 8839.80 1438.90 L 8846.60 1446.50 L 8852.90 1454.40 L 8858.90 1462.60 L 8864.60 1471.00 L 8869.90 1479.70 L 8874.70 1488.60 L 8879.20 1497.80 L 8883.40 1507.20 L 8887.00 1516.70 L 8890.30 1526.40 L 8895.50 1546.00 L 8897.40 1556.00 L 8898.90 1566.10 L 8899.90 1576.10 L 8900.50 1586.30 L 8900.60 1596.50 L 8900.30 1606.60 L 8899.60 1616.80 L 8898.30 1627.00 Z" fill="#e58a7b" fill-rule="nonzero" node-id="337" stroke="none" target-height="704.7999" target-width="520.2998" target-x="8380.3" target-y="1364.9"></path><path d="M 8605.20 1494.60 L 8652.90 1482.70 L 8675.50 1477.50 L 8696.20 1473.30 L 8709.90 1470.90 L 8722.40 1469.00 L 8742.80 1467.10 L 8750.90 1466.80 L 8765.00 1467.50 L 8771.90 1468.40 L 8778.00 1469.90 L 8783.40 1471.70 L 8788.20 1473.90 L 8795.10 1478.70 L 8797.90 1481.60 L 8800.40 1484.90 L 8802.50 1488.50 L 8804.10 1492.30 L 8805.30 1496.50 L 8806.20 1501.30 L 8806.70 1506.70 L 8806.20 1518.40 L 8804.00 1531.60 L 8799.60 1548.00 L 8765.50 1655.30 L 8757.90 1680.70 L 8751.20 1704.40 L 8747.10 1720.10 L 8740.90 1748.50 L 8739.10 1758.90 L 8736.80 1777.60 L 8736.30 1785.90 L 8736.20 1794.60 L 8736.60 1802.30 L 8737.50 1809.20 L 8738.90 1815.30 L 8740.50 1820.30 L 8742.40 1824.70 L 8744.70 1828.60 L 8747.40 1832.00 L 8750.40 1835.00 L 8753.70 1837.60 L 8757.50 1839.70 L 8761.90 1841.40 L 8766.80 1842.70 L 8772.40 1843.50 L 8777.00 1843.70 L 8782.90 1843.30 L 8797.50 1840.70 L 8805.80 1838.70 L 8824.40 1833.00 L 8844.80 1825.50 L 8855.20 1821.20 L 8876.80 1811.30 L 8887.60 1805.90 L 8909.00 1793.90 L 8919.50 1787.40 L 8939.10 1773.70 L 8948.50 1766.40 L 8957.00 1758.90 L 8964.80 1751.40 L 8975.40 1739.40 L 8979.90 1733.40 L 8983.80 1727.40 L 8987.30 1721.10 L 8990.10 1714.80 L 8992.30 1708.50 L 8993.80 1702.30 L 8994.60 1697.20 L 8994.80 1692.10 L 8994.60 1687.00 L 8993.80 1681.80 L 8990.80 1669.40 L 8989.70 1662.80 L 8989.20 1656.60 L 8989.30 1650.90 L 8989.90 1645.10 L 8991.10 1639.60 L 8992.60 1634.40 L 8994.60 1629.50 L 8997.10 1624.70 L 8999.80 1620.10 L 9002.90 1615.70 L 9006.30 1611.50 L 9011.20 1606.10 L 9022.10 1596.00 L 9030.80 1589.00 L 9062.20 1565.20 L 9071.30 1556.80 L 9074.30 1553.40 L 9079.10 1546.70 L 9080.80 1543.20 L 9084.80 1533.60 L 9088.00 1524.80 L 9090.40 1516.80 L 9092.10 1509.40 L 9093.30 1501.70 L 9093.90 1494.70 L 9094.00 1488.20 L 9093.50 1482.20 L 9092.60 1476.10 L 9091.20 1470.50 L 9089.40 1465.20 L 9087.20 1460.30 L 9084.60 1455.60 L 9081.70 1451.00 L 9075.10 1442.70 L 9070.10 1437.60 L 9059.20 1428.10 L 9050.40 1421.60 L 9024.10 1403.60 L 9013.60 1395.50 L 9009.00 1391.30 L 9004.70 1386.90 L 9001.10 1382.30 L 8998.10 1377.50 L 8996.30 1373.70 L 8995.00 1369.70 L 8994.00 1365.50 L 8993.60 1360.90 L 8993.60 1356.50 L 8994.20 1351.70 L 8995.40 1346.30 L 8999.00 1335.40 L 9000.10 1330.30 L 9000.80 1325.00 L 9001.10 1319.70 L 9000.90 1314.20 L 9000.00 1307.40 L 8998.60 1300.50 L 8996.50 1293.50 L 8993.80 1286.30 L 8990.60 1279.40 L 8986.90 1272.40 L 8977.70 1258.40 L 8967.00 1245.00 L 8960.90 1238.50 L 8947.70 1225.80 L 8933.30 1214.10 L 8917.80 1203.30 L 8901.50 1193.90 L 8893.00 1189.70 L 8884.40 1185.80 L 8875.80 1182.40 L 8867.10 1179.40 L 8858.30 1176.80 L 8849.50 1174.70 L 8840.60 1173.10 L 8831.90 1172.00 L 8823.30 1171.50 L 8814.60 1171.50 L 8806.10 1172.10 L 8797.70 1173.40 L 8789.50 1175.20 L 8783.10 1177.10 L 8776.80 1179.50 L 8770.70 1182.40 L 8764.70 1185.70 L 8748.10 1196.70 L 8737.00 1203.60 L 8725.70 1210.10 L 8714.10 1216.30 L 8702.30 1222.10 L 8678.30 1232.60 L 8653.90 1241.60 L 8629.50 1249.10 L 8605.20 1255.00 L 8581.60 1259.30 L 8570.20 1260.80 L 8558.60 1262.00 L 8536.90 1262.90 L 8516.40 1262.20 L 8506.80 1261.10 L 8497.90 1259.60 L 8482.70 1255.80 L 8470.80 1251.20 L 8460.70 1245.50 L 8456.10 1242.10 L 8452.00 1238.40 L 8448.40 1234.50 L 8445.40 1230.20 L 8442.80 1225.70 L 8440.70 1221.00 L 8439.20 1215.90 L 8438.20 1210.40 L 8437.80 1204.50 L 8437.90 1198.00 L 8438.70 1192.00 L 8440.00 1185.40 L 8442.10 1178.30 L 8444.90 1170.60 L 8448.60 1162.20 L 8452.50 1154.60 L 8457.20 1146.50 L 8462.80 1137.70 L 8469.40 1128.20 L 8483.90 1109.00 L 8489.60 1100.80 L 8494.20 1093.50 L 8497.90 1086.90 L 8501.40 1079.80 L 8504.00 1073.40 L 8505.80 1067.80 L 8506.90 1062.70 L 8507.60 1057.40 L 8507.60 1052.60 L 8507.20 1048.30 L 8506.20 1044.50 L 8502.80 1037.30 L 8500.50 1034.20 L 8497.80 1031.40 L 8494.80 1028.90 L 8487.80 1024.50 L 8483.80 1022.70 L 8472.50 1018.90 L 8466.40 1017.50 L 8456.90 1016.00 L 8447.40 1015.10 L 8437.90 1014.70 L 8420.50 1014.90 L 8398.40 1016.80 L 8404.10 1020.90 L 8408.20 1024.30 L 8412.30 1028.30 L 8416.90 1033.40 L 8419.70 1037.10 L 8422.40 1041.20 L 8426.90 1050.50 L 8428.60 1055.70 L 8429.80 1061.30 L 8430.30 1070.00 L 8429.20 1079.80 L 8428.00 1084.70 L 8426.30 1089.80 L 8423.90 1095.30 L 8420.80 1101.20 L 8413.30 1112.40 L 8408.20 1118.50 L 8402.10 1125.10 L 8396.00 1131.10 L 8388.80 1137.50 L 8380.40 1144.30 L 8370.80 1151.50 L 8361.30 1158.10 L 8350.50 1165.10 L 8338.20 1172.40 L 8314.80 1185.90 L 8306.00 1191.70 L 8297.90 1197.70 L 8290.50 1203.80 L 8283.80 1210.10 L 8277.30 1217.00 L 8271.50 1223.90 L 8266.30 1231.00 L 8261.70 1238.20 L 8257.70 1245.60 L 8254.20 1253.20 L 8251.10 1261.00 L 8248.70 1268.80 L 8246.70 1276.70 L 8245.30 1284.70 L 8244.40 1292.80 L 8243.90 1300.90 L 8244.00 1309.10 L 8244.60 1317.40 L 8245.60 1325.70 L 8247.20 1333.90 L 8249.10 1342.10 L 8251.60 1350.20 L 8254.50 1358.40 L 8257.90 1366.60 L 8261.70 1374.60 L 8265.90 1382.40 L 8270.60 1390.20 L 8275.70 1398.00 L 8281.30 1405.60 L 8293.40 1420.20 L 8300.10 1427.20 L 8307.20 1434.10 L 8314.80 1440.90 L 8322.60 1447.20 L 8330.70 1453.40 L 8339.30 1459.30 L 8348.30 1465.00 L 8357.70 1470.40 L 8377.10 1480.20 L 8387.30 1484.60 L 8398.00 1488.70 L 8409.00 1492.50 L 8420.10 1495.80 L 8431.50 1498.70 L 8443.30 1501.20 L 8455.40 1503.30 L 8468.00 1505.10 L 8480.40 1506.30 L 8493.10 1507.10 L 8506.30 1507.30 L 8519.80 1507.10 L 8533.70 1506.40 L 8547.20 1505.20 L 8561.20 1503.40 L 8575.50 1501.10 L 8590.10 1498.20 L 8605.20 1494.60 Z" fill="#263238" fill-rule="nonzero" node-id="339" stroke="none" target-height="828.99994" target-width="850.0996" target-x="8243.9" target-y="1014.7"></path><path d="M 8968.50 1823.00 L 8963.00 1830.50 L 8957.30 1837.60 L 8951.30 1844.30 L 8944.90 1850.80 L 8938.30 1856.90 L 8931.30 1862.70 L 8924.10 1868.10 L 8909.00 1877.90 L 8901.10 1882.30 L 8892.90 1886.20 L 8884.60 1889.70 L 8876.10 1892.90 L 8858.50 1897.90 L 8852.40 1899.00 L 8846.80 1899.60 L 8841.60 1899.80 L 8832.10 1899.10 L 8826.30 1897.90 L 8821.10 1896.10 L 8816.30 1893.90 L 8812.00 1891.20 L 8808.00 1888.00 L 8804.40 1884.40 L 8801.20 1880.40 L 8798.40 1876.00 L 8796.10 1871.40 L 8794.20 1866.40 L 8792.70 1861.10 L 8791.60 1855.40 L 8790.90 1849.70 L 8790.80 1843.70 L 8791.00 1837.50 L 8791.70 1831.10 L 8794.50 1818.30 L 8796.50 1811.70 L 8799.10 1804.90 L 8802.10 1798.10 L 8805.60 1791.10 L 8809.60 1783.70 L 8818.60 1769.30 L 8823.80 1762.10 L 8835.00 1748.30 L 8841.10 1741.70 L 8847.40 1735.40 L 8854.00 1729.50 L 8860.80 1724.10 L 8868.00 1719.10 L 8875.20 1714.70 L 8882.60 1710.90 L 8888.30 1708.40 L 8894.00 1706.40 L 8899.80 1704.80 L 8905.70 1703.60 L 8911.60 1702.90 L 8917.50 1702.80 L 8923.50 1703.10 L 8929.60 1704.00 L 8936.60 1705.70 L 8943.00 1707.90 L 8948.80 1710.40 L 8954.10 1713.40 L 8959.20 1716.90 L 8963.70 1720.70 L 8967.80 1724.90 L 8971.40 1729.40 L 8974.60 1734.20 L 8977.40 1739.20 L 8979.60 1744.50 L 8981.50 1750.10 L 8982.90 1755.80 L 8983.80 1761.60 L 8984.30 1767.60 L 8984.40 1773.80 L 8984.00 1780.00 L 8983.10 1786.20 L 8981.80 1792.40 L 8980.00 1798.70 L 8977.80 1804.90 L 8975.20 1811.00 L 8972.10 1817.00 L 8968.50 1823.00 Z" fill="#e58a7b" fill-rule="nonzero" node-id="341" stroke="none" target-height="197" target-width="193.60059" target-x="8790.8" target-y="1702.8"></path><path d="M 8095.50 3886.50 L 8075.00 4074.60 L 8053.20 4280.90 L 8027.10 4536.30 L 8013.30 4676.50 L 7999.30 4821.70 L 7978.80 5043.80 L 7965.90 5191.30 L 7954.10 5335.50 L 7943.50 5473.90 L 7934.60 5604.10 L 7927.60 5723.80 L 7925.00 5778.00 L 7922.90 5830.50 L 7921.60 5877.20 L 7921.70 5921.00 L 7922.50 5945.30 L 7923.80 5971.30 L 7927.80 6023.50 L 7930.50 6051.70 L 7933.80 6081.30 L 7938.60 6120.40 L 7944.20 6161.60 L 7950.60 6205.00 L 7957.50 6248.20 L 7965.10 6293.20 L 7982.20 6386.70 L 8001.50 6484.20 L 8022.60 6583.90 L 8033.90 6635.30 L 8051.40 6712.50 L 8069.80 6791.10 L 8088.80 6870.00 L 8108.40 6949.40 L 8128.40 7028.70 L 8169.30 7186.60 L 8210.80 7341.30 L 8251.90 7490.60 L 8291.90 7632.30 L 8347.60 7824.80 L 8380.90 7937.40 L 8410.00 8034.70 L 8434.10 8114.20 L 8468.10 8224.00 L 8754.30 8104.80 L 8732.10 7993.90 L 8697.90 7816.40 L 8676.90 7703.60 L 8654.10 7578.00 L 8630.10 7441.80 L 8605.50 7297.40 L 8581.10 7147.30 L 8557.30 6993.60 L 8545.90 6916.20 L 8534.90 6838.80 L 8524.40 6761.60 L 8514.50 6685.30 L 8505.20 6609.40 L 8496.60 6535.30 L 8491.30 6486.10 L 8482.00 6391.30 L 8477.90 6344.40 L 8474.30 6299.20 L 8468.50 6211.90 L 8466.40 6170.20 L 8464.80 6130.50 L 8463.80 6090.70 L 8463.40 6053.30 L 8463.60 6018.30 L 8464.20 5991.80 L 8465.10 5966.80 L 8466.40 5943.50 L 8472.50 5869.80 L 8477.90 5812.80 L 8483.70 5754.10 L 8497.20 5626.90 L 8512.80 5490.30 L 8529.90 5346.70 L 8557.80 5123.10 L 8577.30 4972.20 L 8616.80 4675.80 L 8636.00 4534.90 L 8671.50 4279.30 L 8700.60 4073.90 L 8727.60 3887.50 L 8095.50 3886.50 Z" fill="#429cfd" fill-rule="nonzero" node-id="343" stroke="none" target-height="4337.5" target-width="832.6997" target-x="7921.6" target-y="3886.5"></path><g node-id="593"><path d="M 8496.20 4282.70 L 8489.50 4297.20 L 8483.20 4312.40 L 8477.20 4328.10 L 8471.60 4344.50 L 8466.30 4361.50 L 8461.60 4378.40 L 8457.20 4395.80 L 8453.10 4413.70 L 8449.40 4432.10 L 8446.00 4451.10 L 8442.40 4474.70 L 8439.20 4498.90 L 8436.50 4523.80 L 8434.30 4549.40 L 8432.60 4574.80 L 8431.40 4600.80 L 8430.50 4627.20 L 8430.10 4654.10 L 8430.50 4708.10 L 8432.40 4763.20 L 8434.50 4800.10 L 8437.00 4837.20 L 8440.20 4874.60 L 8443.80 4911.80 L 8447.80 4949.00 L 8452.30 4985.90 L 8457.20 5022.90 L 8467.80 5095.20 L 8473.60 5131.10 L 8485.60 5200.30 L 8495.10 5251.00 L 8504.60 5299.00 L 8514.40 5345.90 L 8523.90 5389.20 L 8540.30 5254.80 L 8557.60 5117.70 L 8575.50 4979.60 L 8611.60 4707.50 L 8611.00 4656.70 L 8610.00 4608.00 L 8608.60 4560.20 L 8605.30 4485.80 L 8601.70 4431.70 L 8597.10 4381.40 L 8594.60 4359.30 L 8591.60 4337.20 L 8588.40 4317.60 L 8585.10 4300.30 L 8579.20 4275.50 L 8576.20 4265.50 L 8573.20 4256.80 L 8570.20 4249.50 L 8567.20 4243.40 L 8564.30 4238.40 L 8561.40 4234.40 L 8558.60 4231.20 L 8555.70 4228.60 L 8552.80 4226.80 L 8550.00 4225.60 L 8547.10 4225.00 L 8544.10 4225.00 L 8541.00 4225.50 L 8538.20 4226.40 L 8535.10 4227.90 L 8531.90 4230.10 L 8528.30 4233.10 L 8524.50 4237.00 L 8520.30 4241.90 L 8511.80 4254.00 L 8507.00 4262.00 L 8496.20 4282.70 Z" fill="#000000" fill-opacity="0.2" fill-rule="nonzero" group-id="6" node-id="347" stroke="none" target-height="1164.2002" target-width="181.5" target-x="8430.1" target-y="4225"></path></g><path d="M 8741.60 8010.30 L 8743.50 8014.20 L 8753.00 8039.20 L 8775.50 8101.40 L 8445.40 8240.30 L 8393.40 8143.40 L 8741.60 8010.30 Z" fill="#429cfd" fill-rule="nonzero" node-id="350" stroke="none" target-height="230" target-width="382.0996" target-x="8393.4" target-y="8010.3"></path><path d="M 8436.30 3886.50 L 8461.30 4156.40 L 8494.90 4503.90 L 8522.20 4774.80 L 8536.80 4915.00 L 8551.80 5055.40 L 8567.10 5193.70 L 8582.50 5327.60 L 8597.70 5454.80 L 8612.70 5572.90 L 8620.10 5628.60 L 8634.50 5729.10 L 8648.20 5816.50 L 8675.40 5978.10 L 8721.50 6242.80 L 8776.10 6548.10 L 8865.50 7040.00 L 8953.00 7515.40 L 9082.50 8211.60 L 9411.40 8211.60 L 9380.00 7917.00 L 9339.70 7531.80 L 9308.20 7226.90 L 9275.70 6905.40 L 9244.20 6585.70 L 9229.50 6432.30 L 9215.80 6286.50 L 9203.50 6150.40 L 9188.00 5968.70 L 9180.20 5867.10 L 9177.20 5824.00 L 9154.10 5470.50 L 9101.40 4628.40 L 9056.00 3886.50 L 8436.30 3886.50 Z" fill="#429cfd" fill-rule="nonzero" node-id="352" stroke="none" target-height="4325.0996" target-width="975.1006" target-x="8436.3" target-y="3886.5"></path><path d="M 9416.40 8126.00 L 9416.60 8126.50 L 9417.30 8129.90 L 9420.50 8155.10 L 9427.40 8217.80 L 9065.80 8217.80 L 9040.00 8112.40 L 9416.40 8126.00 Z" fill="#429cfd" fill-rule="nonzero" node-id="354" stroke="none" target-height="105.3999" target-width="387.4004" target-x="9040" target-y="8112.4"></path><g node-id="594"><path d="M 9290.50 8017.20 L 9289.40 8017.10 L 9288.40 8016.50 L 9287.70 8015.70 L 9287.40 8014.60 L 9285.70 7998.80 L 9255.20 7712.30 L 9215.40 7331.40 L 9169.40 6881.40 L 9139.10 6575.40 L 9124.90 6428.00 L 9111.70 6287.60 L 9099.70 6156.50 L 9084.70 5981.90 L 9077.20 5884.80 L 9074.40 5844.10 L 9052.30 5504.70 L 8994.40 4575.70 L 8964.40 4083.30 L 8965.20 4081.10 L 8966.10 4080.40 L 8967.30 4080.20 L 8969.40 4081.00 L 8970.10 4081.90 L 8970.40 4083.00 L 8973.30 4133.80 L 9015.90 4829.90 L 9058.30 5504.20 L 9080.30 5843.80 L 9083.20 5884.60 L 9090.70 5981.60 L 9105.70 6156.20 L 9117.60 6287.20 L 9145.10 6575.00 L 9175.40 6880.80 L 9221.30 7330.70 L 9261.00 7711.60 L 9293.10 8014.40 L 9293.10 8015.60 L 9292.50 8016.60 L 9291.60 8017.40 L 9290.50 8017.70 L 9290.50 8017.20 Z" fill="#000000" fill-opacity="0.2" fill-rule="nonzero" group-id="7" node-id="358" stroke="none" target-height="3937.5002" target-width="328.69922" target-x="8964.4" target-y="4080.2"></path></g><path d="M 8625.50 1676.60 L 8625.30 1680.20 L 8623.90 1686.80 L 8622.70 1689.90 L 8619.70 1695.20 L 8617.90 1697.40 L 8615.90 1699.30 L 8613.80 1700.80 L 8611.50 1701.90 L 8609.20 1702.60 L 8606.90 1702.80 L 8604.50 1702.60 L 8600.30 1700.80 L 8598.40 1699.30 L 8596.20 1696.70 L 8594.40 1693.60 L 8592.80 1689.80 L 8591.80 1685.90 L 8591.30 1681.50 L 8591.40 1676.60 L 8592.00 1671.70 L 8593.00 1667.30 L 8594.50 1663.30 L 8596.50 1659.50 L 8598.70 1656.40 L 8603.20 1652.30 L 8605.40 1651.30 L 8607.60 1650.60 L 8610.00 1650.30 L 8612.30 1650.60 L 8616.50 1652.40 L 8618.40 1654.00 L 8620.60 1656.50 L 8622.50 1659.60 L 8624.00 1663.40 L 8625.00 1667.40 L 8625.50 1671.80 L 8625.50 1676.60 Z" fill="#263238" fill-rule="nonzero" node-id="361" stroke="none" target-height="52.5" target-width="34.200195" target-x="8591.3" target-y="1650.3"></path><path d="M 8461.80 1676.60 L 8461.60 1680.20 L 8460.20 1686.80 L 8459.10 1689.90 L 8457.60 1692.70 L 8456.00 1695.20 L 8454.10 1697.40 L 8450.10 1700.80 L 8447.80 1701.90 L 8445.50 1702.60 L 8443.20 1702.80 L 8438.80 1701.90 L 8436.80 1700.80 L 8434.90 1699.30 L 8432.70 1696.80 L 8430.80 1693.70 L 8429.20 1689.80 L 8428.10 1685.90 L 8427.60 1681.50 L 8427.50 1676.60 L 8427.90 1672.90 L 8428.50 1669.50 L 8429.40 1666.30 L 8430.60 1663.20 L 8432.00 1660.50 L 8433.60 1658.00 L 8435.50 1655.70 L 8439.50 1652.40 L 8441.70 1651.30 L 8444.00 1650.60 L 8446.30 1650.30 L 8448.70 1650.60 L 8452.90 1652.40 L 8454.80 1654.00 L 8457.00 1656.50 L 8458.80 1659.60 L 8460.40 1663.40 L 8461.40 1667.40 L 8461.90 1671.80 L 8461.80 1676.60 Z" fill="#263238" fill-rule="nonzero" node-id="363" stroke="none" target-height="52.5" target-width="34.40039" target-x="8427.5" target-y="1650.3"></path><path d="M 8513.40 1684.00 L 8505.60 1700.50 L 8497.40 1716.80 L 8488.60 1732.70 L 8479.30 1748.40 L 8469.50 1763.70 L 8459.10 1778.70 L 8448.30 1793.40 L 8437.00 1807.80 L 8441.20 1811.50 L 8445.70 1814.50 L 8450.40 1817.00 L 8455.30 1819.00 L 8460.30 1820.60 L 8465.40 1821.70 L 8475.50 1823.00 L 8487.20 1823.00 L 8498.40 1822.00 L 8505.50 1820.70 L 8513.40 1684.00 Z" fill="#df5753" fill-rule="nonzero" node-id="365" stroke="none" target-height="139" target-width="76.40039" target-x="8437" target-y="1684"></path><path d="M 8517.70 1881.50 L 8516.80 1881.40 L 8516.10 1881.10 L 8515.30 1880.70 L 8514.70 1880.20 L 8513.60 1878.00 L 8513.60 1877.20 L 8514.00 1875.50 L 8515.00 1874.10 L 8516.50 1873.20 L 8531.80 1872.20 L 8538.50 1871.30 L 8545.20 1870.00 L 8551.80 1868.40 L 8564.70 1864.00 L 8570.90 1861.30 L 8577.00 1858.20 L 8588.60 1851.10 L 8594.10 1847.10 L 8604.30 1838.10 L 8613.50 1828.00 L 8616.50 1826.90 L 8618.10 1827.20 L 8619.50 1828.00 L 8620.40 1829.40 L 8620.70 1831.00 L 8620.40 1832.60 L 8619.50 1834.00 L 8614.70 1839.50 L 8609.70 1844.70 L 8598.70 1854.20 L 8592.90 1858.50 L 8580.50 1866.00 L 8574.00 1869.30 L 8567.30 1872.20 L 8553.50 1876.80 L 8546.50 1878.50 L 8532.20 1880.80 L 8525.00 1881.30 L 8517.70 1881.50 Z" fill="#263238" fill-rule="nonzero" node-id="367" stroke="none" target-height="54.599976" target-width="107.100586" target-x="8513.6" target-y="1826.9"></path><path d="M 8654.60 1632.70 L 8652.80 1633.40 L 8649.00 1633.40 L 8647.20 1632.70 L 8642.60 1630.00 L 8637.90 1627.70 L 8633.00 1625.70 L 8627.90 1624.10 L 8622.80 1623.00 L 8617.50 1622.20 L 8612.20 1621.90 L 8601.70 1622.40 L 8596.50 1623.30 L 8591.40 1624.60 L 8584.80 1626.80 L 8583.30 1627.10 L 8580.10 1626.70 L 8577.30 1625.00 L 8576.20 1623.80 L 8575.40 1622.40 L 8574.90 1620.80 L 8574.70 1619.10 L 8574.80 1617.40 L 8575.30 1615.80 L 8576.10 1614.30 L 8577.10 1612.90 L 8578.40 1611.80 L 8579.90 1611.00 L 8586.20 1608.70 L 8592.70 1606.90 L 8599.30 1605.70 L 8605.90 1605.00 L 8612.60 1604.80 L 8619.30 1605.10 L 8626.00 1606.00 L 8632.50 1607.40 L 8638.90 1609.40 L 8645.10 1611.80 L 8651.20 1614.70 L 8658.20 1619.20 L 8660.00 1622.00 L 8660.50 1623.50 L 8660.40 1626.80 L 8659.10 1629.80 L 8657.80 1631.10 L 8656.30 1632.00 L 8654.60 1632.70 Z" fill="#263238" fill-rule="nonzero" node-id="369" stroke="none" target-height="28.599976" target-width="85.799805" target-x="8574.7" target-y="1604.8"></path><path d="M 8398.60 1585.00 L 8395.30 1583.10 L 8393.40 1580.40 L 8393.00 1578.80 L 8393.00 1575.50 L 8394.20 1572.40 L 8400.10 1566.20 L 8405.20 1561.70 L 8410.60 1557.50 L 8416.30 1553.80 L 8422.30 1550.60 L 8428.60 1547.80 L 8435.00 1545.40 L 8441.60 1543.60 L 8448.30 1542.30 L 8455.10 1541.60 L 8461.90 1541.30 L 8468.80 1541.60 L 8470.40 1542.00 L 8473.20 1543.80 L 8475.00 1546.50 L 8475.50 1548.10 L 8475.60 1549.70 L 8475.40 1551.40 L 8474.90 1553.00 L 8474.10 1554.40 L 8473.10 1555.70 L 8471.80 1556.80 L 8470.30 1557.70 L 8468.80 1558.20 L 8467.10 1558.50 L 8460.00 1558.40 L 8449.30 1559.30 L 8438.80 1561.90 L 8433.70 1563.80 L 8428.80 1566.00 L 8419.50 1571.60 L 8415.20 1574.80 L 8411.20 1578.40 L 8406.30 1583.50 L 8404.90 1584.30 L 8401.80 1585.20 L 8400.20 1585.30 L 8398.60 1585.00 Z" fill="#263238" fill-rule="nonzero" node-id="371" stroke="none" target-height="44" target-width="82.59961" target-x="8393" target-y="1541.3"></path><path d="M 9296.00 2559.80 L 9047.90 2493.80 L 9003.60 2599.60 L 8981.60 2650.50 L 8956.70 2707.10 L 8915.50 2798.00 L 8886.40 2859.90 L 8871.60 2890.30 L 8842.20 2948.90 L 8827.50 2977.00 L 8813.30 3003.00 L 8799.10 3028.00 L 8785.80 3050.30 L 8768.30 3077.60 L 8760.40 3088.90 L 8752.00 3100.00 L 8744.50 3109.20 L 8737.70 3116.70 L 8732.10 3122.10 L 8727.00 3126.30 L 8722.50 3129.40 L 8682.40 3149.20 L 8662.00 3160.00 L 8639.10 3172.40 L 8590.00 3200.00 L 8509.20 3247.50 L 8424.00 3299.30 L 8367.70 3334.50 L 8313.60 3368.90 L 8263.10 3401.60 L 8218.00 3431.50 L 8179.70 3457.60 L 8178.40 3464.40 L 8177.50 3473.10 L 8176.90 3482.30 L 8176.70 3492.70 L 8176.90 3503.40 L 8177.40 3514.70 L 8178.30 3525.90 L 8181.20 3547.90 L 8184.60 3564.50 L 8187.90 3575.60 L 8189.90 3580.70 L 8194.00 3588.10 L 8231.00 3580.60 L 8271.80 3571.00 L 8315.30 3559.50 L 8348.70 3550.00 L 8383.60 3539.50 L 8418.80 3528.40 L 8454.80 3516.60 L 8490.80 3504.30 L 8526.80 3491.40 L 8562.70 3478.20 L 8598.00 3464.80 L 8632.90 3451.00 L 8666.60 3437.20 L 8699.50 3423.20 L 8730.60 3409.50 L 8760.70 3395.60 L 8788.30 3382.30 L 8822.80 3364.50 L 8837.90 3356.20 L 8852.80 3347.60 L 8866.00 3339.60 L 8877.60 3332.10 L 8893.70 3320.40 L 8905.50 3310.50 L 8915.70 3300.50 L 8926.10 3289.70 L 8936.70 3278.10 L 8957.60 3253.10 L 8967.90 3239.80 L 8978.30 3225.80 L 8988.80 3211.10 L 9002.30 3191.40 L 9015.80 3170.70 L 9029.30 3149.00 L 9042.50 3127.20 L 9055.50 3104.60 L 9081.20 3058.10 L 9106.00 3010.40 L 9124.00 2974.20 L 9141.40 2938.00 L 9158.20 2901.80 L 9189.60 2831.20 L 9217.70 2764.40 L 9242.30 2703.50 L 9271.10 2628.60 L 9284.60 2591.80 L 9296.00 2559.80 Z" fill="#e58a7b" fill-rule="nonzero" node-id="373" stroke="none" target-height="1094.3" target-width="1119.2998" target-x="8176.7" target-y="2493.8"></path><path d="M 9122.80 8465.20 L 9118.20 8465.30 L 9113.70 8465.10 L 9104.90 8463.50 L 9100.60 8462.10 L 9088.70 8455.90 L 9086.50 8453.70 L 9084.70 8451.20 L 9083.20 8448.50 L 9082.00 8445.70 L 9081.20 8442.60 L 9080.90 8439.60 L 9081.30 8433.50 L 9081.70 8431.00 L 9082.50 8428.60 L 9083.70 8426.40 L 9085.30 8424.40 L 9087.20 8422.80 L 9089.40 8421.50 L 9092.40 8420.40 L 9095.90 8419.80 L 9108.50 8420.90 L 9113.40 8422.20 L 9120.70 8424.50 L 9128.30 8427.50 L 9135.80 8430.80 L 9143.10 8434.40 L 9150.10 8438.10 L 9156.30 8441.50 L 9166.40 8447.40 L 9171.90 8450.90 L 9172.90 8451.70 L 9173.50 8452.70 L 9173.70 8453.90 L 9173.60 8455.20 L 9173.10 8456.40 L 9172.40 8457.40 L 9171.40 8458.20 L 9170.30 8458.70 L 9158.50 8461.30 L 9146.70 8463.20 L 9134.80 8464.50 L 9122.80 8465.20 Z M 9098.90 8428.00 L 9096.20 8427.70 L 9093.50 8428.00 L 9092.20 8428.90 L 9091.30 8430.10 L 9090.70 8431.50 L 9090.60 8433.00 L 9090.20 8435.80 L 9090.30 8438.60 L 9090.90 8441.40 L 9092.00 8444.00 L 9093.50 8446.40 L 9097.80 8450.30 L 9100.80 8451.90 L 9104.50 8453.30 L 9109.10 8454.40 L 9115.10 8455.20 L 9122.30 8455.60 L 9139.10 8454.80 L 9148.50 8453.60 L 9158.80 8451.80 L 9151.80 8447.50 L 9144.60 8443.50 L 9137.30 8439.90 L 9129.80 8436.80 L 9114.20 8431.60 L 9106.30 8429.60 L 9098.20 8428.00 L 9098.90 8428.00 Z" fill="#429cfd" fill-rule="nonzero" node-id="375" stroke="none" target-height="45.5" target-width="92.799805" target-x="9080.9" target-y="8419.8"></path><path d="M 9169.10 8458.70 L 9168.10 8458.90 L 9167.20 8458.70 L 9160.90 8454.50 L 9157.20 8451.50 L 9146.00 8440.70 L 9140.50 8434.50 L 9135.30 8427.80 L 9130.50 8420.90 L 9126.50 8414.00 L 9124.20 8409.30 L 9122.40 8404.90 L 9121.10 8400.70 L 9120.30 8396.50 L 9120.20 8392.70 L 9120.60 8389.30 L 9121.40 8387.00 L 9122.50 8384.90 L 9124.00 8383.00 L 9125.70 8381.30 L 9127.80 8379.90 L 9130.00 8379.00 L 9132.30 8378.30 L 9134.70 8378.10 L 9137.90 8377.90 L 9144.30 8378.90 L 9152.60 8383.60 L 9155.70 8386.70 L 9158.50 8390.30 L 9161.00 8394.60 L 9163.20 8399.10 L 9165.10 8403.90 L 9166.80 8409.10 L 9168.90 8417.00 L 9170.50 8424.80 L 9171.70 8432.40 L 9172.60 8439.20 L 9173.40 8450.10 L 9173.60 8455.20 L 9173.20 8457.40 L 9171.50 8459.00 L 9170.20 8459.00 L 9169.10 8458.70 Z M 9137.10 8386.00 L 9135.40 8386.00 L 9132.90 8386.30 L 9131.40 8386.80 L 9130.20 8387.70 L 9129.50 8388.60 L 9129.00 8390.30 L 9128.70 8392.70 L 9129.00 8395.60 L 9129.90 8399.20 L 9131.30 8402.70 L 9133.00 8406.60 L 9139.30 8417.10 L 9143.90 8423.60 L 9148.90 8429.90 L 9165.00 8445.90 L 9164.70 8438.30 L 9163.80 8430.90 L 9162.40 8423.60 L 9160.40 8416.30 L 9157.80 8409.30 L 9154.80 8402.50 L 9147.10 8389.60 L 9144.90 8388.00 L 9142.40 8386.80 L 9139.80 8386.20 L 9137.10 8386.00 Z" fill="#429cfd" fill-rule="nonzero" node-id="377" stroke="none" target-height="81.09961" target-width="53.399414" target-x="9120.2" target-y="8377.9"></path><path d="M 8489.50 8435.40 L 8483.90 8437.00 L 8478.30 8438.20 L 8472.70 8438.90 L 8467.00 8439.20 L 8461.30 8438.90 L 8455.60 8438.20 L 8450.00 8437.00 L 8444.40 8435.40 L 8441.30 8433.30 L 8438.60 8430.80 L 8436.30 8427.80 L 8434.50 8424.50 L 8433.30 8421.00 L 8432.70 8417.20 L 8432.60 8414.90 L 8432.90 8412.60 L 8433.60 8410.30 L 8434.70 8408.10 L 8436.10 8406.30 L 8437.80 8404.60 L 8439.90 8403.00 L 8442.50 8401.50 L 8445.50 8400.30 L 8449.20 8399.20 L 8454.00 8398.20 L 8459.40 8397.50 L 8465.50 8397.20 L 8474.80 8397.10 L 8484.70 8397.70 L 8494.60 8398.60 L 8504.30 8399.80 L 8522.10 8402.80 L 8536.00 8405.80 L 8544.80 8408.50 L 8545.80 8409.30 L 8546.30 8410.50 L 8546.40 8413.10 L 8545.90 8414.30 L 8545.00 8415.30 L 8543.90 8416.10 L 8530.60 8421.70 L 8517.10 8426.80 L 8503.40 8431.40 L 8489.50 8435.40 Z M 8450.60 8407.00 L 8448.00 8407.80 L 8445.50 8408.90 L 8443.20 8410.60 L 8442.20 8411.60 L 8441.60 8412.80 L 8441.20 8414.20 L 8441.30 8415.60 L 8441.70 8418.10 L 8442.50 8420.40 L 8443.60 8422.60 L 8445.10 8424.60 L 8446.90 8426.30 L 8449.00 8427.70 L 8452.10 8429.10 L 8456.10 8430.00 L 8460.90 8430.50 L 8466.90 8430.50 L 8478.80 8429.10 L 8494.10 8425.50 L 8504.50 8422.40 L 8516.10 8418.30 L 8528.90 8413.20 L 8519.20 8411.00 L 8509.50 8409.20 L 8490.00 8406.80 L 8480.20 8406.20 L 8470.30 8406.10 L 8460.50 8406.30 L 8450.60 8407.00 Z" fill="#429cfd" fill-rule="nonzero" node-id="379" stroke="none" target-height="42.100586" target-width="113.80078" target-x="8432.6" target-y="8397.1"></path><path d="M 8543.20 8416.10 L 8537.30 8415.20 L 8527.70 8412.10 L 8512.10 8405.20 L 8504.10 8400.80 L 8496.40 8396.00 L 8491.40 8392.60 L 8486.80 8389.10 L 8482.50 8385.50 L 8478.50 8381.70 L 8475.20 8378.10 L 8472.50 8374.50 L 8470.80 8371.60 L 8469.50 8368.90 L 8468.70 8366.30 L 8468.30 8363.80 L 8468.50 8361.40 L 8469.30 8358.60 L 8470.10 8356.80 L 8471.40 8355.00 L 8473.30 8353.10 L 8478.20 8350.00 L 8481.90 8348.60 L 8489.80 8346.90 L 8494.00 8346.80 L 8498.00 8347.40 L 8505.70 8350.00 L 8513.80 8355.30 L 8517.60 8358.70 L 8521.10 8362.50 L 8524.30 8366.50 L 8531.60 8377.50 L 8535.20 8384.20 L 8538.30 8390.90 L 8540.80 8396.70 L 8545.80 8410.60 L 8545.90 8411.70 L 8545.70 8412.80 L 8545.30 8413.80 L 8544.00 8415.40 L 8543.20 8416.10 Z M 8486.70 8355.70 L 8484.30 8355.70 L 8480.90 8357.10 L 8477.70 8359.80 L 8477.10 8360.80 L 8476.90 8361.80 L 8476.90 8362.40 L 8477.10 8364.20 L 8477.70 8366.10 L 8478.90 8368.30 L 8480.70 8370.80 L 8483.30 8373.90 L 8486.50 8377.10 L 8490.60 8380.60 L 8504.50 8390.30 L 8512.30 8394.80 L 8520.20 8398.90 L 8528.20 8402.40 L 8535.60 8405.10 L 8533.20 8398.00 L 8530.20 8391.20 L 8526.80 8384.70 L 8518.30 8372.60 L 8513.40 8367.10 L 8508.00 8362.00 L 8502.20 8357.40 L 8499.70 8356.30 L 8497.20 8355.60 L 8494.60 8355.10 L 8491.90 8355.00 L 8486.70 8355.70 Z" fill="#429cfd" fill-rule="nonzero" node-id="381" stroke="none" target-height="69.299805" target-width="77.600586" target-x="8468.3" target-y="8346.8"></path><path d="M 8200.00 3443.60 L 7951.20 3558.10 L 7922.30 3629.60 L 8014.10 3691.40 L 8032.40 3688.60 L 8052.60 3684.40 L 8064.30 3681.50 L 8077.70 3677.80 L 8091.40 3673.50 L 8106.10 3668.30 L 8115.90 3664.40 L 8136.00 3655.50 L 8145.90 3650.40 L 8155.80 3644.90 L 8165.50 3638.90 L 8175.00 3632.50 L 8184.20 3625.60 L 8193.00 3618.20 L 8199.40 3612.30 L 8205.50 3606.10 L 8216.70 3592.80 L 8221.80 3585.70 L 8226.50 3578.30 L 8230.80 3570.50 L 8234.80 3562.30 L 8200.00 3443.60 Z" fill="#e58a7b" fill-rule="nonzero" node-id="383" stroke="none" target-height="247.7998" target-width="312.5" target-x="7922.3" target-y="3443.6"></path><path d="M 8210.40 3592.20 L 8208.50 3577.80 L 8205.90 3563.70 L 8202.70 3549.70 L 8198.90 3535.90 L 8194.60 3522.20 L 8189.60 3508.70 L 8184.10 3495.40 L 8178.00 3482.40 L 8171.40 3469.70 L 8164.20 3457.20 L 8203.50 3427.60 L 8210.70 3439.60 L 8217.40 3451.80 L 8223.70 3464.20 L 8229.50 3476.80 L 8234.90 3489.70 L 8239.70 3502.70 L 8244.10 3515.80 L 8248.00 3529.20 L 8251.50 3542.70 L 8254.40 3556.20 L 8256.90 3570.00 L 8258.90 3583.80 L 8210.40 3592.20 Z" fill="#263238" fill-rule="nonzero" node-id="385" stroke="none" target-height="164.59985" target-width="94.700195" target-x="8164.2" target-y="3427.6"></path><path d="M 8252.90 3477.40 L 8255.30 3481.70 L 8257.30 3486.10 L 8258.70 3490.70 L 8259.70 3495.50 L 8260.30 3500.30 L 8260.30 3505.20 L 8258.90 3514.80 L 8257.40 3519.40 L 8255.50 3523.90 L 8253.20 3528.10 L 8250.40 3532.20 L 8247.20 3535.90 L 8243.70 3539.20 L 8235.80 3544.70 L 8231.40 3546.90 L 8226.80 3548.60 L 8222.10 3549.80 L 8217.30 3550.50 L 8212.50 3550.70 L 8207.60 3550.50 L 8202.90 3549.70 L 8198.10 3548.50 L 8189.20 3544.60 L 8185.10 3542.00 L 8181.30 3539.00 L 8174.70 3531.90 L 8171.90 3527.80 L 8169.60 3523.60 L 8167.70 3519.10 L 8165.40 3509.70 L 8165.00 3504.80 L 8165.60 3495.10 L 8168.20 3485.80 L 8170.10 3481.40 L 8172.60 3477.10 L 8175.40 3473.20 L 8178.70 3469.50 L 8186.10 3463.40 L 8190.30 3460.90 L 8198.60 3457.10 L 8202.60 3455.70 L 8206.70 3454.80 L 8215.10 3454.10 L 8223.50 3455.00 L 8227.60 3456.10 L 8231.70 3457.70 L 8239.00 3461.70 L 8242.40 3464.30 L 8245.50 3467.20 L 8248.20 3470.30 L 8252.90 3477.40 Z" fill="#263238" fill-rule="nonzero" node-id="387" stroke="none" target-height="96.59985" target-width="95.299805" target-x="8165" target-y="3454.1"></path><path d="M 7783.00 3650.60 L 7816.20 3684.00 L 8039.40 3588.60 L 7951.20 3558.10 L 7783.00 3650.60 Z" fill="#e58a7b" fill-rule="nonzero" node-id="389" stroke="none" target-height="125.8999" target-width="256.3999" target-x="7783" target-y="3558.1"></path><g node-id="595"><path d="M 8741.60 8010.30 L 8743.50 8014.20 L 8753.00 8039.20 L 8775.50 8101.40 L 8445.40 8240.30 L 8393.40 8143.40 L 8741.60 8010.30 Z" fill="#000000" fill-opacity="0.2" fill-rule="nonzero" group-id="8" node-id="393" stroke="none" target-height="230" target-width="382.0996" target-x="8393.4" target-y="8010.3"></path></g><g node-id="596"><path d="M 9416.40 8126.00 L 9416.60 8126.50 L 9417.30 8129.90 L 9420.50 8155.10 L 9427.40 8217.80 L 9065.80 8217.80 L 9040.00 8112.40 L 9416.40 8126.00 Z" fill="#000000" fill-opacity="0.2" fill-rule="nonzero" group-id="9" node-id="398" stroke="none" target-height="105.3999" target-width="387.4004" target-x="9040" target-y="8112.4"></path></g><path d="M 9360.60 2491.10 L 9363.60 2501.80 L 9366.10 2512.50 L 9369.60 2534.30 L 9370.60 2545.40 L 9371.20 2556.50 L 9371.30 2567.70 L 9371.00 2578.90 L 9370.40 2590.30 L 9369.20 2601.70 L 9367.30 2615.90 L 9364.90 2630.10 L 9362.00 2644.30 L 9358.50 2658.70 L 9354.50 2672.80 L 9350.20 2686.90 L 9345.40 2701.00 L 9340.30 2715.00 L 9332.90 2733.50 L 9325.00 2751.70 L 9316.70 2769.50 L 9307.90 2787.20 L 9289.80 2820.90 L 9270.80 2853.00 L 9247.20 2889.70 L 9220.60 2927.70 L 9197.50 2958.20 L 9188.50 2969.60 L 9169.80 2992.00 L 8815.60 2847.50 L 8831.60 2801.70 L 8848.30 2756.20 L 8865.50 2710.90 L 8883.30 2665.80 L 8901.80 2621.00 L 8920.80 2576.40 L 8940.40 2532.10 L 8960.60 2488.00 L 8967.00 2474.80 L 8973.50 2462.40 L 8980.20 2450.60 L 8986.90 2439.60 L 9000.80 2419.40 L 9008.40 2409.40 L 9016.10 2400.20 L 9023.80 2391.60 L 9031.60 2383.60 L 9039.40 2376.10 L 9055.70 2362.50 L 9064.20 2356.40 L 9072.60 2350.80 L 9081.00 2345.70 L 9089.50 2341.20 L 9106.70 2333.50 L 9115.50 2330.30 L 9124.30 2327.70 L 9133.00 2325.50 L 9141.70 2323.80 L 9159.20 2321.80 L 9167.90 2321.50 L 9176.60 2321.60 L 9193.80 2323.30 L 9202.30 2324.80 L 9210.70 2326.80 L 9227.30 2332.00 L 9243.60 2339.10 L 9251.60 2343.30 L 9259.30 2347.90 L 9266.90 2352.90 L 9274.30 2358.40 L 9281.60 2364.30 L 9288.80 2370.70 L 9302.50 2384.70 L 9309.00 2392.20 L 9315.20 2400.20 L 9321.30 2408.60 L 9327.30 2417.60 L 9333.00 2427.10 L 9338.20 2436.50 L 9343.20 2446.40 L 9347.90 2456.80 L 9352.40 2467.70 L 9360.60 2491.10 Z" fill="#429cfd" fill-rule="nonzero" node-id="401" stroke="none" target-height="670.5" target-width="555.7002" target-x="8815.6" target-y="2321.5"></path><g node-id="597"><path d="M 9360.60 2491.10 L 9363.60 2501.80 L 9366.10 2512.50 L 9369.60 2534.30 L 9370.60 2545.40 L 9371.20 2556.50 L 9371.30 2567.70 L 9371.00 2578.90 L 9370.40 2590.30 L 9369.20 2601.70 L 9367.30 2615.90 L 9364.90 2630.10 L 9362.00 2644.30 L 9358.50 2658.70 L 9354.50 2672.80 L 9350.20 2686.90 L 9345.40 2701.00 L 9340.30 2715.00 L 9332.90 2733.50 L 9325.00 2751.70 L 9316.70 2769.50 L 9307.90 2787.20 L 9289.80 2820.90 L 9270.80 2853.00 L 9247.20 2889.70 L 9220.60 2927.70 L 9197.50 2958.20 L 9188.50 2969.60 L 9169.80 2992.00 L 8815.60 2847.50 L 8831.60 2801.70 L 8848.30 2756.20 L 8865.50 2710.90 L 8883.30 2665.80 L 8901.80 2621.00 L 8920.80 2576.40 L 8940.40 2532.10 L 8960.60 2488.00 L 8967.00 2474.80 L 8973.50 2462.40 L 8980.20 2450.60 L 8986.90 2439.60 L 9000.80 2419.40 L 9008.40 2409.40 L 9016.10 2400.20 L 9023.80 2391.60 L 9031.60 2383.60 L 9039.40 2376.10 L 9055.70 2362.50 L 9064.20 2356.40 L 9072.60 2350.80 L 9081.00 2345.70 L 9089.50 2341.20 L 9106.70 2333.50 L 9115.50 2330.30 L 9124.30 2327.70 L 9133.00 2325.50 L 9141.70 2323.80 L 9159.20 2321.80 L 9167.90 2321.50 L 9176.60 2321.60 L 9193.80 2323.30 L 9202.30 2324.80 L 9210.70 2326.80 L 9227.30 2332.00 L 9243.60 2339.10 L 9251.60 2343.30 L 9259.30 2347.90 L 9266.90 2352.90 L 9274.30 2358.40 L 9281.60 2364.30 L 9288.80 2370.70 L 9302.50 2384.70 L 9309.00 2392.20 L 9315.20 2400.20 L 9321.30 2408.60 L 9327.30 2417.60 L 9333.00 2427.10 L 9338.20 2436.50 L 9343.20 2446.40 L 9347.90 2456.80 L 9352.40 2467.70 L 9360.60 2491.10 Z" fill="#000000" fill-opacity="0.2" fill-rule="nonzero" group-id="10" node-id="405" stroke="none" target-height="670.5" target-width="555.7002" target-x="8815.6" target-y="2321.5"></path></g><path d="M 5841.00 371.40 L 5688.50 371.40 L 5688.50 395.90 L 5841.00 395.90 L 5841.00 371.40 Z" fill="#429cfd" fill-rule="nonzero" node-id="408" stroke="none" target-height="24.5" target-width="152.5" target-x="5688.5" target-y="371.4"></path><path d="M 7814.00 1343.80 L 7795.50 1753.10 L 7820.10 1754.20 L 7838.50 1344.90 L 7814.00 1343.80 Z" fill="#429cfd" fill-rule="nonzero" node-id="410" stroke="none" target-height="410.3999" target-width="43" target-x="7795.5" target-y="1343.8"></path><path d="M 7766.60 481.60 L 4952.00 481.60 L 4690.10 6342.60 L 7504.70 6342.60 L 7766.60 481.60 Z" fill="#429cfd" fill-rule="nonzero" node-id="412" stroke="none" target-height="5861" target-width="3076.5" target-x="4690.1" target-y="481.6"></path><g node-id="598"><path d="M 7682.60 562.20 L 4868.00 562.20 L 4606.10 6423.20 L 7420.70 6423.20 L 7682.60 562.20 Z" fill="#fafafa" fill-opacity="0.56" fill-rule="nonzero" group-id="11" node-id="416" stroke="none" target-height="5861" target-width="3076.5" target-x="4606.1" target-y="562.2"></path></g><g node-id="599"><path d="M 7598.60 642.60 L 4784.10 642.60 L 4521.90 6503.60 L 7336.50 6503.60 L 7598.60 642.60 Z" fill="#429cfd" fill-opacity="0.07" fill-rule="nonzero" group-id="12" node-id="421" stroke="none" target-height="5861" target-width="3076.7002" target-x="4521.9" target-y="642.6"></path></g><path d="M 7278.30 3198.80 L 4875.90 3198.80 L 4855.90 3647.30 L 7258.30 3647.30 L 7278.30 3198.80 Z" fill="#ffffff" fill-rule="nonzero" node-id="424" stroke="none" target-height="448.5" target-width="2422.4" target-x="4855.9" target-y="3198.8"></path><path d="M 6197.10 2871.60 L 4890.40 2871.60 L 4881.60 3072.20 L 6188.20 3072.20 L 6197.10 2871.60 Z" fill="#429cfd" fill-rule="nonzero" node-id="426" stroke="none" target-height="200.59985" target-width="1315.5" target-x="4881.6" target-y="2871.6"></path><path d="M 7224.20 4409.10 L 4821.70 4409.10 L 4801.70 4857.50 L 7204.10 4857.50 L 7224.20 4409.10 Z" fill="#ffffff" fill-rule="nonzero" node-id="428" stroke="none" target-height="448.3999" target-width="2422.5" target-x="4801.7" target-y="4409.1"></path><g node-id="600"><path d="M 6783.60 5463.10 L 5168.10 5463.10 L 5144.00 6000.50 L 6759.50 6000.50 L 6783.60 5463.10 Z" fill="#429cfd" fill-opacity="0.47" fill-rule="nonzero" group-id="13" node-id="432" stroke="none" target-height="537.3999" target-width="1639.6001" target-x="5144" target-y="5463.1"></path></g><path d="M 6675.50 6084.00 L 5057.90 6084.00 L 5057.70 6082.90 L 5081.70 5544.20 L 5082.00 5543.20 L 5082.60 5542.30 L 5083.50 5541.70 L 5084.60 5541.60 L 6700.10 5541.60 L 6701.10 5541.40 L 6702.00 5541.60 L 6702.30 5542.60 L 6702.00 5543.50 L 6678.70 6082.30 L 6677.40 6083.70 L 6676.50 6084.00 L 6675.50 6084.00 Z M 5062.90 6078.30 L 6672.70 6078.30 L 6696.50 5546.40 L 5086.70 5546.40 L 5062.90 6078.30 Z" fill="#ffffff" fill-rule="nonzero" node-id="435" stroke="none" target-height="542.6001" target-width="1644.5996" target-x="5057.7" target-y="5541.4"></path><path d="M 5914.90 4081.90 L 4836.30 4081.90 L 4827.50 4282.70 L 5905.80 4282.70 L 5914.90 4081.90 Z" fill="#429cfd" fill-rule="nonzero" node-id="437" stroke="none" target-height="200.8003" target-width="1087.3999" target-x="4827.5" target-y="4081.9"></path><path d="M 6752.80 1750.30 L 6751.60 1769.90 L 6749.80 1789.30 L 6747.40 1808.60 L 6744.40 1827.80 L 6740.80 1846.90 L 6736.60 1865.90 L 6731.80 1884.70 L 6726.50 1903.30 L 6720.60 1921.80 L 6714.20 1940.10 L 6707.20 1958.10 L 6699.60 1976.00 L 6691.60 1993.50 L 6682.90 2010.90 L 6673.70 2028.00 L 6664.00 2044.90 L 6653.80 2061.40 L 6643.10 2077.60 L 6631.80 2093.60 L 6620.00 2109.30 L 6607.80 2124.70 L 6595.30 2139.60 L 6582.30 2154.20 L 6568.90 2168.30 L 6555.10 2182.10 L 6540.80 2195.40 L 6526.20 2208.20 L 6511.20 2220.60 L 6495.90 2232.50 L 6480.10 2244.00 L 6464.00 2255.00 L 6447.60 2265.50 L 6430.90 2275.50 L 6413.80 2285.00 L 6396.40 2294.00 L 6378.70 2302.50 L 6360.90 2310.40 L 6342.80 2317.80 L 6324.50 2324.50 L 6306.10 2330.80 L 6287.40 2336.40 L 6268.70 2341.50 L 6249.70 2346.00 L 6230.70 2350.00 L 6211.50 2353.40 L 6192.20 2356.10 L 6172.90 2358.30 L 6153.40 2359.90 L 6133.90 2361.00 L 6114.30 2361.40 L 6098.50 2361.30 L 6082.70 2360.80 L 6066.90 2359.80 L 6051.20 2358.40 L 6035.60 2356.60 L 6020.00 2354.40 L 6004.40 2351.70 L 5989.00 2348.70 L 5973.70 2345.20 L 5958.40 2341.30 L 5943.20 2336.90 L 5928.20 2332.20 L 5913.30 2327.10 L 5898.50 2321.50 L 5883.80 2315.50 L 5869.30 2309.10 L 5841.00 2295.30 L 5827.10 2287.80 L 5813.50 2279.90 L 5800.00 2271.70 L 5786.80 2263.10 L 5773.90 2254.10 L 5761.20 2244.80 L 5748.70 2235.20 L 5736.50 2225.20 L 5724.60 2214.90 L 5712.90 2204.20 L 5690.50 2182.00 L 5679.70 2170.30 L 5669.30 2158.50 L 5659.20 2146.40 L 5649.40 2134.00 L 5640.00 2121.30 L 5631.00 2108.40 L 5622.30 2095.30 L 5605.90 2068.40 L 5598.30 2054.60 L 5591.10 2040.60 L 5584.30 2026.40 L 5577.80 2012.00 L 5571.70 1997.40 L 5566.00 1982.60 L 5555.80 1952.70 L 5551.40 1937.50 L 5547.40 1922.30 L 5543.80 1907.00 L 5540.60 1891.50 L 5537.80 1876.00 L 5535.40 1860.50 L 5533.50 1844.80 L 5532.00 1829.20 L 5530.90 1813.40 L 5530.30 1797.70 L 5530.00 1781.90 L 5530.20 1766.10 L 5530.90 1750.30 L 5532.20 1730.60 L 5534.10 1711.00 L 5536.60 1691.60 L 5539.60 1672.40 L 5543.30 1653.30 L 5547.50 1634.20 L 5552.30 1615.40 L 5557.60 1596.70 L 5563.50 1578.30 L 5569.90 1560.00 L 5576.90 1541.90 L 5584.40 1524.10 L 5592.50 1506.50 L 5601.10 1489.20 L 5610.20 1472.10 L 5619.80 1455.20 L 5629.90 1438.70 L 5640.50 1422.50 L 5651.60 1406.60 L 5663.30 1390.90 L 5675.40 1375.60 L 5687.90 1360.70 L 5701.00 1346.20 L 5714.50 1332.00 L 5728.60 1318.10 L 5743.10 1304.60 L 5757.80 1291.70 L 5773.00 1279.30 L 5788.40 1267.40 L 5804.20 1256.00 L 5820.30 1245.00 L 5836.70 1234.60 L 5853.40 1224.70 L 5870.30 1215.30 L 5887.50 1206.40 L 5905.00 1198.10 L 5922.70 1190.20 L 5940.60 1183.00 L 5958.80 1176.20 L 5977.10 1170.00 L 5995.70 1164.40 L 6014.40 1159.30 L 6033.20 1154.80 L 6052.20 1150.80 L 6071.50 1147.40 L 6090.70 1144.60 L 6110.10 1142.30 L 6129.60 1140.70 L 6149.20 1139.70 L 6168.90 1139.20 L 6184.80 1139.30 L 6200.60 1139.80 L 6216.30 1140.70 L 6232.00 1142.00 L 6247.70 1143.80 L 6263.30 1146.00 L 6278.90 1148.70 L 6294.30 1151.70 L 6309.70 1155.20 L 6325.00 1159.10 L 6340.20 1163.40 L 6355.20 1168.10 L 6370.10 1173.20 L 6384.90 1178.80 L 6399.60 1184.80 L 6414.20 1191.10 L 6428.50 1197.90 L 6442.50 1205.00 L 6456.40 1212.50 L 6470.10 1220.30 L 6483.60 1228.60 L 6496.80 1237.20 L 6509.70 1246.10 L 6522.40 1255.40 L 6534.90 1265.10 L 6547.10 1275.00 L 6559.10 1285.40 L 6570.70 1296.00 L 6582.10 1307.00 L 6593.20 1318.30 L 6604.00 1329.90 L 6624.60 1353.90 L 6634.30 1366.30 L 6643.70 1378.90 L 6652.80 1391.80 L 6661.50 1405.00 L 6669.90 1418.30 L 6677.80 1431.90 L 6692.70 1459.70 L 6699.50 1474.00 L 6706.00 1488.30 L 6712.10 1502.90 L 6717.80 1517.70 L 6723.10 1532.70 L 6727.90 1547.70 L 6732.40 1562.90 L 6736.40 1578.10 L 6743.20 1608.90 L 6746.00 1624.40 L 6748.40 1640.00 L 6750.30 1655.60 L 6751.80 1671.40 L 6752.80 1687.10 L 6753.50 1702.80 L 6753.70 1718.60 L 6753.50 1734.40 L 6752.80 1750.30 Z" fill="#ffffff" fill-rule="nonzero" node-id="439" stroke="none" target-height="1222.2" target-width="1223.7002" target-x="5530" target-y="1139.2"></path><path d="M 6810.30 1267.00 L 6808.60 1284.30 L 6807.10 1292.70 L 6805.20 1301.10 L 6800.10 1317.50 L 6796.90 1325.50 L 6793.30 1333.30 L 6785.00 1348.30 L 6780.20 1355.50 L 6775.00 1362.40 L 6768.80 1370.00 L 6762.30 1377.20 L 6755.40 1384.00 L 6748.10 1390.40 L 6740.40 1396.30 L 6732.40 1401.90 L 6724.10 1406.90 L 6715.50 1411.50 L 6706.60 1415.60 L 6697.60 1419.20 L 6688.40 1422.30 L 6679.00 1424.80 L 6669.50 1426.80 L 6659.90 1428.20 L 6650.30 1429.10 L 6640.50 1429.50 L 6631.00 1429.20 L 6621.60 1428.40 L 6612.40 1427.10 L 6603.20 1425.10 L 6594.10 1422.60 L 6585.20 1419.60 L 6576.50 1416.00 L 6568.00 1411.90 L 6559.70 1407.30 L 6551.80 1402.20 L 6544.30 1396.60 L 6537.00 1390.60 L 6530.20 1384.10 L 6523.80 1377.30 L 6517.80 1370.10 L 6512.20 1362.40 L 6507.60 1355.30 L 6503.50 1347.90 L 6499.80 1340.40 L 6496.50 1332.70 L 6493.60 1324.90 L 6491.10 1316.90 L 6489.10 1308.80 L 6487.50 1300.60 L 6486.30 1292.30 L 6485.60 1283.90 L 6485.30 1275.50 L 6485.40 1267.00 L 6486.10 1258.30 L 6487.20 1249.70 L 6488.80 1241.20 L 6490.70 1232.90 L 6493.10 1224.60 L 6495.90 1216.50 L 6499.10 1208.60 L 6502.60 1200.80 L 6506.60 1193.20 L 6510.90 1185.80 L 6515.70 1178.60 L 6520.70 1171.70 L 6526.10 1165.00 L 6531.90 1158.50 L 6538.10 1152.30 L 6544.60 1146.40 L 6551.30 1141.00 L 6558.20 1135.80 L 6565.30 1131.10 L 6572.70 1126.70 L 6580.30 1122.70 L 6588.10 1119.10 L 6596.00 1115.90 L 6604.10 1113.00 L 6612.30 1110.60 L 6620.70 1108.60 L 6629.10 1107.00 L 6637.70 1105.90 L 6646.30 1105.10 L 6655.00 1104.80 L 6662.90 1105.00 L 6670.80 1105.50 L 6678.50 1106.40 L 6686.30 1107.80 L 6694.00 1109.50 L 6701.50 1111.60 L 6716.30 1116.90 L 6723.50 1120.20 L 6730.50 1123.80 L 6737.30 1127.70 L 6743.80 1132.00 L 6750.20 1136.60 L 6756.30 1141.60 L 6767.70 1152.40 L 6773.10 1158.20 L 6778.00 1164.30 L 6782.70 1170.60 L 6787.00 1177.10 L 6791.00 1183.90 L 6794.70 1190.80 L 6798.00 1198.00 L 6800.90 1205.30 L 6803.40 1212.80 L 6805.60 1220.30 L 6808.80 1235.70 L 6809.70 1243.50 L 6810.30 1251.30 L 6810.30 1267.00 Z" fill="#429cfd" fill-rule="nonzero" node-id="441" stroke="none" target-height="324.69995" target-width="325" target-x="6485.3" target-y="1104.8"></path><path d="M 5134.70 3423.10 L 5134.00 3429.40 L 5132.90 3435.70 L 5131.20 3441.80 L 5129.00 3447.70 L 5126.40 3453.40 L 5123.20 3458.90 L 5119.70 3464.10 L 5115.70 3469.00 L 5111.20 3473.60 L 5106.40 3477.80 L 5101.30 3481.60 L 5095.90 3484.90 L 5090.30 3487.80 L 5084.40 3490.10 L 5078.40 3492.00 L 5072.20 3493.40 L 5066.00 3494.30 L 5059.60 3494.60 L 5054.90 3494.50 L 5045.70 3493.40 L 5041.20 3492.30 L 5032.50 3489.30 L 5028.20 3487.40 L 5024.20 3485.20 L 5020.30 3482.70 L 5016.60 3479.90 L 5009.70 3473.70 L 5006.60 3470.20 L 5001.20 3462.80 L 4998.80 3458.80 L 4996.80 3454.60 L 4995.00 3450.30 L 4993.60 3445.90 L 4992.40 3441.40 L 4991.50 3436.90 L 4990.80 3427.70 L 4990.90 3423.10 L 4991.50 3416.70 L 4992.60 3410.40 L 4996.40 3398.40 L 5002.20 3387.10 L 5005.70 3381.90 L 5009.80 3377.00 L 5014.20 3372.40 L 5019.10 3368.10 L 5024.20 3364.40 L 5029.60 3361.00 L 5035.20 3358.20 L 5041.00 3355.80 L 5047.10 3354.00 L 5053.30 3352.60 L 5066.00 3351.50 L 5070.60 3351.60 L 5075.20 3352.00 L 5079.80 3352.80 L 5088.70 3355.20 L 5093.00 3356.80 L 5097.30 3358.80 L 5105.20 3363.50 L 5112.40 3369.20 L 5115.80 3372.50 L 5118.90 3375.90 L 5124.30 3383.40 L 5126.60 3387.40 L 5130.40 3395.80 L 5131.90 3400.20 L 5133.10 3404.70 L 5134.00 3409.20 L 5134.50 3413.80 L 5134.80 3418.40 L 5134.70 3423.10 Z" fill="#263238" fill-rule="nonzero" node-id="443" stroke="none" target-height="143.1001" target-width="144" target-x="4990.8" target-y="3351.5"></path><path d="M 5456.20 3423.00 L 5455.60 3429.40 L 5454.40 3435.70 L 5452.80 3441.80 L 5450.60 3447.70 L 5447.90 3453.40 L 5444.80 3458.90 L 5441.30 3464.10 L 5437.30 3469.10 L 5432.80 3473.70 L 5428.00 3477.90 L 5422.90 3481.70 L 5417.50 3485.00 L 5411.80 3487.80 L 5406.00 3490.20 L 5400.00 3492.10 L 5393.80 3493.40 L 5387.50 3494.30 L 5381.10 3494.60 L 5376.40 3494.50 L 5367.30 3493.30 L 5362.80 3492.30 L 5358.40 3490.90 L 5354.10 3489.30 L 5349.90 3487.30 L 5341.90 3482.60 L 5334.70 3476.90 L 5331.40 3473.60 L 5328.30 3470.20 L 5322.90 3462.70 L 5320.60 3458.70 L 5318.50 3454.60 L 5316.80 3450.30 L 5315.30 3445.90 L 5313.30 3436.90 L 5312.50 3427.70 L 5312.60 3423.00 L 5313.20 3416.70 L 5314.40 3410.40 L 5318.20 3398.40 L 5320.80 3392.70 L 5323.90 3387.20 L 5327.50 3382.00 L 5331.40 3377.10 L 5335.90 3372.40 L 5340.70 3368.20 L 5345.80 3364.40 L 5351.20 3361.10 L 5356.80 3358.30 L 5362.60 3355.90 L 5368.70 3354.00 L 5374.80 3352.70 L 5381.10 3351.80 L 5387.50 3351.50 L 5392.20 3351.60 L 5401.30 3352.70 L 5405.80 3353.80 L 5414.60 3356.80 L 5418.80 3358.70 L 5426.80 3363.40 L 5430.50 3366.20 L 5434.00 3369.10 L 5437.40 3372.40 L 5440.50 3375.80 L 5443.30 3379.50 L 5445.90 3383.30 L 5450.30 3391.50 L 5452.00 3395.80 L 5453.50 3400.20 L 5454.70 3404.60 L 5455.50 3409.20 L 5456.10 3413.80 L 5456.20 3423.00 Z" fill="#263238" fill-rule="nonzero" node-id="445" stroke="none" target-height="143.1001" target-width="143.7002" target-x="5312.5" target-y="3351.5"></path><path d="M 5778.00 3423.00 L 5777.30 3429.40 L 5776.20 3435.70 L 5774.50 3441.80 L 5772.40 3447.70 L 5769.70 3453.40 L 5766.60 3458.90 L 5763.00 3464.10 L 5754.60 3473.70 L 5749.70 3477.90 L 5744.60 3481.70 L 5739.20 3485.00 L 5733.60 3487.80 L 5727.80 3490.20 L 5721.70 3492.10 L 5715.50 3493.40 L 5709.30 3494.30 L 5702.90 3494.60 L 5698.20 3494.50 L 5693.60 3494.10 L 5689.10 3493.30 L 5680.10 3490.90 L 5675.80 3489.30 L 5671.60 3487.30 L 5667.50 3485.10 L 5663.60 3482.60 L 5656.40 3476.90 L 5653.10 3473.60 L 5650.00 3470.20 L 5647.10 3466.50 L 5644.50 3462.70 L 5642.20 3458.70 L 5638.40 3450.30 L 5636.90 3445.90 L 5634.90 3436.90 L 5634.10 3427.70 L 5634.10 3423.00 L 5634.80 3416.60 L 5635.90 3410.40 L 5639.80 3398.40 L 5642.40 3392.70 L 5645.50 3387.20 L 5649.10 3381.90 L 5653.10 3377.00 L 5657.60 3372.40 L 5662.40 3368.20 L 5667.50 3364.40 L 5678.50 3358.30 L 5684.40 3355.90 L 5690.40 3354.00 L 5696.60 3352.70 L 5702.90 3351.80 L 5709.30 3351.50 L 5713.90 3351.60 L 5723.10 3352.70 L 5732.00 3355.10 L 5736.40 3356.80 L 5740.60 3358.70 L 5748.60 3363.40 L 5752.30 3366.20 L 5755.80 3369.10 L 5759.10 3372.40 L 5762.20 3375.80 L 5767.70 3383.30 L 5772.00 3391.50 L 5773.80 3395.80 L 5775.30 3400.20 L 5777.30 3409.20 L 5777.80 3413.80 L 5778.10 3418.40 L 5778.00 3423.00 Z" fill="#263238" fill-rule="nonzero" node-id="447" stroke="none" target-height="143.1001" target-width="144" target-x="5634.1" target-y="3351.5"></path><path d="M 5080.50 4633.30 L 5079.90 4639.70 L 5078.70 4645.90 L 5077.00 4652.00 L 5074.90 4657.90 L 5069.10 4669.10 L 5065.50 4674.40 L 5061.50 4679.30 L 5057.10 4683.90 L 5052.20 4688.10 L 5047.10 4691.90 L 5041.80 4695.20 L 5036.10 4698.00 L 5030.30 4700.40 L 5024.30 4702.30 L 5018.10 4703.70 L 5011.80 4704.50 L 5005.40 4704.90 L 5000.80 4704.70 L 4991.60 4703.60 L 4982.70 4701.20 L 4978.40 4699.50 L 4974.10 4697.60 L 4970.10 4695.40 L 4966.20 4692.90 L 4962.50 4690.10 L 4955.60 4683.90 L 4949.70 4676.80 L 4947.10 4673.00 L 4944.80 4669.00 L 4941.00 4660.60 L 4939.50 4656.20 L 4938.30 4651.70 L 4937.40 4647.20 L 4936.60 4638.00 L 4936.70 4633.30 L 4937.30 4626.90 L 4938.50 4620.70 L 4940.20 4614.60 L 4942.30 4608.60 L 4945.00 4602.90 L 4948.10 4597.40 L 4951.70 4592.20 L 4960.10 4582.70 L 4965.00 4578.50 L 4970.10 4574.70 L 4975.50 4571.40 L 4981.10 4568.50 L 4986.90 4566.20 L 4993.00 4564.30 L 4999.20 4562.90 L 5005.40 4562.10 L 5011.90 4561.80 L 5016.50 4561.90 L 5021.10 4562.30 L 5025.60 4563.10 L 5034.60 4565.50 L 5038.90 4567.10 L 5043.10 4569.10 L 5051.10 4573.80 L 5054.80 4576.50 L 5058.30 4579.50 L 5061.60 4582.70 L 5064.70 4586.20 L 5070.10 4593.70 L 5072.50 4597.60 L 5074.50 4601.80 L 5076.30 4606.10 L 5077.80 4610.50 L 5078.90 4614.90 L 5079.80 4619.50 L 5080.60 4628.70 L 5080.50 4633.30 Z" fill="#263238" fill-rule="nonzero" node-id="449" stroke="none" target-height="143.1001" target-width="144" target-x="4936.6" target-y="4561.8"></path><path d="M 5402.10 4633.30 L 5401.40 4639.70 L 5400.30 4645.90 L 5398.60 4652.00 L 5396.50 4657.90 L 5393.80 4663.60 L 5390.70 4669.10 L 5387.20 4674.30 L 5383.20 4679.20 L 5378.70 4683.90 L 5373.90 4688.10 L 5368.80 4691.90 L 5363.50 4695.20 L 5357.80 4698.00 L 5352.00 4700.40 L 5346.00 4702.30 L 5339.80 4703.70 L 5327.20 4704.90 L 5322.50 4704.80 L 5317.90 4704.30 L 5313.40 4703.60 L 5308.90 4702.60 L 5300.10 4699.60 L 5295.90 4697.60 L 5287.90 4692.90 L 5280.70 4687.20 L 5277.30 4684.00 L 5274.20 4680.50 L 5271.40 4676.90 L 5268.80 4673.00 L 5266.50 4669.00 L 5262.70 4660.60 L 5261.20 4656.20 L 5259.20 4647.20 L 5258.40 4638.00 L 5258.50 4633.30 L 5260.30 4620.70 L 5261.90 4614.60 L 5264.10 4608.60 L 5266.70 4602.90 L 5273.40 4592.20 L 5277.40 4587.30 L 5281.90 4582.70 L 5286.70 4578.50 L 5291.80 4574.70 L 5297.20 4571.40 L 5302.90 4568.50 L 5314.70 4564.30 L 5320.90 4562.90 L 5333.60 4561.80 L 5338.20 4561.90 L 5347.40 4563.10 L 5356.30 4565.50 L 5360.60 4567.10 L 5364.80 4569.10 L 5368.90 4571.30 L 5372.80 4573.80 L 5380.00 4579.50 L 5383.30 4582.70 L 5386.40 4586.20 L 5389.20 4589.80 L 5391.80 4593.70 L 5394.10 4597.70 L 5397.90 4606.10 L 5399.40 4610.50 L 5401.40 4619.50 L 5401.90 4624.10 L 5402.10 4633.30 Z" fill="#263238" fill-rule="nonzero" node-id="451" stroke="none" target-height="143.1001" target-width="143.7002" target-x="5258.4" target-y="4561.8"></path><path d="M 5724.60 4633.30 L 5723.90 4639.70 L 5722.80 4645.90 L 5721.10 4652.00 L 5718.90 4658.00 L 5716.30 4663.70 L 5713.20 4669.20 L 5709.60 4674.40 L 5705.60 4679.30 L 5701.10 4684.00 L 5691.20 4691.90 L 5685.80 4695.20 L 5680.20 4698.10 L 5674.30 4700.50 L 5668.30 4702.30 L 5662.10 4703.70 L 5655.80 4704.50 L 5649.40 4704.90 L 5644.80 4704.70 L 5635.60 4703.50 L 5631.10 4702.50 L 5622.40 4699.50 L 5618.20 4697.50 L 5610.30 4692.80 L 5606.60 4690.00 L 5603.10 4687.10 L 5599.70 4683.80 L 5596.60 4680.40 L 5591.20 4672.90 L 5588.90 4668.90 L 5585.00 4660.50 L 5583.60 4656.10 L 5582.40 4651.70 L 5581.50 4647.10 L 5580.90 4642.60 L 5580.70 4633.30 L 5581.40 4626.90 L 5582.50 4620.70 L 5584.20 4614.60 L 5586.40 4608.70 L 5589.00 4603.00 L 5592.20 4597.50 L 5595.70 4592.30 L 5599.70 4587.40 L 5604.20 4582.70 L 5609.00 4578.50 L 5614.10 4574.80 L 5619.50 4571.40 L 5625.20 4568.60 L 5631.00 4566.20 L 5637.00 4564.30 L 5643.20 4563.00 L 5649.50 4562.10 L 5655.90 4561.80 L 5660.50 4561.90 L 5669.70 4563.00 L 5678.60 4565.40 L 5683.00 4567.00 L 5687.20 4569.00 L 5691.20 4571.20 L 5695.10 4573.70 L 5702.40 4579.40 L 5705.70 4582.70 L 5708.80 4586.10 L 5714.20 4593.60 L 5716.60 4597.60 L 5720.40 4606.00 L 5721.90 4610.40 L 5723.90 4619.50 L 5724.40 4624.00 L 5724.60 4633.30 Z" fill="#263238" fill-rule="nonzero" node-id="453" stroke="none" target-height="143.1001" target-width="143.8999" target-x="5580.7" target-y="4561.8"></path><path d="M 6045.60 4633.30 L 6043.80 4645.90 L 6039.90 4657.90 L 6037.30 4663.60 L 6034.20 4669.10 L 6030.60 4674.40 L 6026.60 4679.30 L 6022.10 4683.90 L 6017.30 4688.10 L 6012.20 4691.90 L 6006.80 4695.20 L 6001.20 4698.00 L 5995.40 4700.40 L 5989.30 4702.30 L 5983.20 4703.70 L 5976.90 4704.50 L 5970.50 4704.90 L 5965.80 4704.70 L 5956.70 4703.50 L 5952.20 4702.50 L 5943.50 4699.50 L 5939.30 4697.50 L 5931.30 4692.80 L 5927.60 4690.00 L 5920.80 4683.80 L 5917.70 4680.40 L 5912.30 4672.90 L 5909.90 4668.90 L 5907.90 4664.80 L 5906.10 4660.50 L 5902.60 4647.10 L 5901.70 4637.90 L 5901.80 4633.30 L 5902.40 4626.90 L 5903.60 4620.70 L 5905.30 4614.60 L 5907.50 4608.70 L 5913.20 4597.50 L 5916.80 4592.30 L 5925.30 4582.70 L 5930.10 4578.50 L 5935.20 4574.80 L 5940.60 4571.40 L 5946.20 4568.60 L 5958.10 4564.30 L 5964.20 4563.00 L 5976.90 4561.80 L 5981.60 4561.90 L 5986.20 4562.30 L 5990.70 4563.10 L 5999.60 4565.50 L 6004.00 4567.10 L 6008.20 4569.10 L 6016.10 4573.80 L 6019.80 4576.50 L 6023.40 4579.50 L 6026.70 4582.70 L 6029.80 4586.20 L 6032.60 4589.80 L 6035.20 4593.70 L 6039.60 4601.80 L 6041.40 4606.10 L 6042.80 4610.50 L 6044.90 4619.50 L 6045.70 4628.70 L 6045.60 4633.30 Z" fill="#263238" fill-rule="nonzero" node-id="455" stroke="none" target-height="143.1001" target-width="144" target-x="5901.7" target-y="4561.8"></path><path d="M 6368.60 4633.30 L 6367.90 4639.70 L 6366.70 4645.90 L 6365.10 4652.00 L 6362.90 4657.90 L 6360.20 4663.60 L 6357.10 4669.10 L 6353.50 4674.40 L 6349.50 4679.30 L 6345.10 4683.90 L 6340.30 4688.10 L 6335.20 4691.90 L 6329.80 4695.20 L 6324.10 4698.00 L 6318.30 4700.40 L 6312.30 4702.30 L 6306.10 4703.70 L 6299.80 4704.50 L 6293.40 4704.90 L 6288.80 4704.80 L 6284.20 4704.30 L 6279.60 4703.60 L 6275.10 4702.60 L 6266.30 4699.60 L 6258.10 4695.40 L 6250.40 4690.20 L 6246.90 4687.20 L 6243.60 4684.00 L 6240.50 4680.50 L 6237.60 4676.90 L 6232.70 4669.00 L 6230.70 4664.90 L 6228.90 4660.60 L 6227.40 4656.20 L 6226.30 4651.70 L 6224.90 4642.60 L 6224.70 4638.00 L 6224.70 4633.30 L 6225.40 4626.90 L 6226.50 4620.70 L 6228.20 4614.60 L 6230.40 4608.60 L 6236.10 4597.40 L 6239.70 4592.20 L 6243.70 4587.30 L 6248.20 4582.70 L 6253.00 4578.50 L 6258.10 4574.70 L 6263.50 4571.40 L 6269.10 4568.50 L 6275.00 4566.20 L 6281.00 4564.30 L 6287.20 4562.90 L 6293.50 4562.10 L 6299.90 4561.80 L 6304.50 4561.90 L 6309.10 4562.30 L 6313.70 4563.10 L 6322.60 4565.50 L 6326.90 4567.10 L 6331.10 4569.10 L 6335.20 4571.30 L 6339.10 4573.80 L 6342.80 4576.50 L 6346.30 4579.50 L 6349.60 4582.70 L 6352.70 4586.20 L 6355.60 4589.80 L 6358.20 4593.70 L 6360.50 4597.60 L 6362.50 4601.80 L 6364.30 4606.10 L 6365.80 4610.50 L 6367.80 4619.50 L 6368.60 4628.70 L 6368.60 4633.30 Z" fill="#263238" fill-rule="nonzero" node-id="457" stroke="none" target-height="143.1001" target-width="143.8999" target-x="6224.7" target-y="4561.8"></path><path d="M 6319.80 1718.50 L 6328.20 1709.90 L 6336.00 1701.00 L 6343.40 1692.00 L 6350.30 1682.80 L 6356.70 1673.40 L 6362.80 1663.60 L 6368.30 1653.70 L 6373.40 1643.60 L 6378.00 1633.50 L 6382.10 1623.20 L 6385.80 1612.60 L 6389.00 1602.00 L 6391.70 1591.40 L 6393.90 1580.70 L 6395.70 1569.90 L 6397.00 1559.10 L 6397.80 1548.20 L 6398.20 1537.40 L 6398.10 1526.60 L 6397.50 1515.80 L 6396.50 1505.00 L 6394.90 1494.40 L 6392.90 1483.80 L 6390.50 1473.30 L 6387.50 1462.90 L 6384.10 1452.60 L 6380.20 1442.50 L 6375.80 1432.60 L 6370.90 1422.80 L 6365.50 1413.20 L 6359.80 1403.90 L 6353.50 1394.80 L 6346.70 1386.00 L 6339.40 1377.30 L 6331.60 1368.90 L 6323.30 1360.90 L 6314.90 1353.40 L 6306.20 1346.50 L 6297.20 1340.00 L 6288.10 1334.00 L 6278.60 1328.40 L 6268.90 1323.30 L 6259.10 1318.70 L 6249.10 1314.60 L 6238.90 1310.90 L 6228.60 1307.70 L 6218.10 1305.00 L 6207.60 1302.80 L 6197.00 1301.00 L 6186.20 1299.70 L 6175.40 1298.90 L 6164.60 1298.50 L 6153.80 1298.70 L 6142.90 1299.30 L 6132.10 1300.30 L 6121.20 1301.90 L 6110.50 1303.90 L 6099.80 1306.40 L 6089.20 1309.30 L 6078.50 1312.80 L 6068.10 1316.60 L 6057.90 1321.00 L 6047.70 1325.80 L 6037.70 1331.20 L 6027.80 1337.00 L 6018.20 1343.20 L 6008.80 1349.90 L 5999.70 1357.10 L 5990.70 1364.70 L 5981.90 1373.00 L 5973.50 1381.60 L 5965.60 1390.40 L 5958.20 1399.40 L 5951.30 1408.60 L 5944.90 1418.00 L 5938.90 1427.80 L 5933.30 1437.70 L 5928.30 1447.80 L 5923.70 1457.90 L 5919.60 1468.30 L 5915.90 1478.80 L 5912.70 1489.40 L 5910.00 1500.00 L 5907.70 1510.70 L 5905.90 1521.50 L 5904.60 1532.30 L 5903.80 1543.20 L 5903.40 1554.00 L 5903.50 1564.80 L 5904.10 1575.60 L 5905.20 1586.40 L 5906.70 1597.00 L 5908.70 1607.60 L 5911.20 1618.10 L 5914.10 1628.50 L 5917.60 1638.80 L 5921.50 1648.90 L 5925.80 1658.80 L 5930.70 1668.60 L 5936.10 1678.20 L 5941.90 1687.50 L 5948.20 1696.60 L 5954.90 1705.40 L 5962.20 1714.10 L 5970.10 1722.50 L 5978.30 1730.50 L 5986.80 1738.00 L 5995.40 1744.90 L 6004.40 1751.40 L 6013.60 1757.40 L 6023.10 1763.00 L 6032.70 1768.10 L 6042.50 1772.70 L 6052.50 1776.80 L 6062.70 1780.50 L 6073.10 1783.70 L 6083.50 1786.40 L 6094.00 1788.60 L 6104.70 1790.40 L 6115.40 1791.70 L 6126.20 1792.50 L 6137.00 1792.90 L 6147.80 1792.70 L 6158.70 1792.10 L 6169.60 1791.10 L 6180.40 1789.50 L 6191.10 1787.50 L 6201.80 1785.00 L 6212.50 1782.10 L 6223.10 1778.60 L 6233.50 1774.80 L 6243.80 1770.40 L 6253.90 1765.60 L 6263.90 1760.20 L 6273.80 1754.40 L 6283.40 1748.20 L 6292.80 1741.50 L 6302.00 1734.40 L 6311.00 1726.70 L 6319.80 1718.50 Z" fill="#429cfd" fill-rule="nonzero" node-id="459" stroke="none" target-height="494.40002" target-width="494.8003" target-x="5903.4" target-y="1298.5"></path><path d="M 6620.00 2109.30 L 6607.80 2124.70 L 6595.30 2139.60 L 6582.30 2154.20 L 6568.90 2168.30 L 6555.10 2182.10 L 6540.80 2195.40 L 6526.20 2208.20 L 6511.20 2220.60 L 6495.90 2232.50 L 6480.10 2244.00 L 6464.00 2255.00 L 6447.60 2265.50 L 6430.90 2275.50 L 6413.80 2285.00 L 6396.40 2294.00 L 6378.70 2302.50 L 6360.90 2310.40 L 6342.80 2317.80 L 6324.50 2324.50 L 6306.10 2330.80 L 6287.40 2336.40 L 6268.60 2341.50 L 6249.70 2346.00 L 6230.70 2350.00 L 6211.50 2353.40 L 6192.20 2356.10 L 6172.90 2358.30 L 6153.40 2359.90 L 6133.90 2361.00 L 6114.30 2361.40 L 6095.30 2361.20 L 6076.30 2360.50 L 6057.50 2359.10 L 6038.70 2357.10 L 6020.00 2354.50 L 6001.40 2351.20 L 5983.00 2347.40 L 5964.70 2343.00 L 5946.50 2338.00 L 5928.50 2332.40 L 5910.60 2326.20 L 5893.00 2319.40 L 5875.60 2312.10 L 5858.40 2304.20 L 5841.40 2295.60 L 5824.70 2286.60 L 5808.40 2277.00 L 5792.40 2266.90 L 5776.80 2256.30 L 5761.50 2245.20 L 5746.60 2233.60 L 5732.10 2221.60 L 5718.00 2209.10 L 5704.30 2196.10 L 5691.10 2182.70 L 5678.20 2168.80 L 5665.90 2154.50 L 5654.00 2139.90 L 5642.50 2124.80 L 5631.50 2109.30 L 5643.30 2097.90 L 5655.50 2086.80 L 5668.10 2076.00 L 5681.30 2065.40 L 5694.90 2055.00 L 5708.70 2045.10 L 5722.90 2035.50 L 5737.60 2026.20 L 5752.70 2017.20 L 5768.20 2008.50 L 5783.80 2000.20 L 5799.80 1992.30 L 5816.20 1984.70 L 5832.90 1977.50 L 5850.10 1970.60 L 5867.30 1964.10 L 5884.80 1958.10 L 5902.60 1952.40 L 5920.80 1947.10 L 5939.20 1942.30 L 5957.70 1937.90 L 5976.50 1933.90 L 5995.50 1930.30 L 6014.80 1927.20 L 6034.40 1924.60 L 6053.90 1922.40 L 6073.60 1920.70 L 6093.50 1919.40 L 6113.70 1918.70 L 6134.10 1918.40 L 6154.50 1918.70 L 6174.60 1919.50 L 6194.50 1920.70 L 6214.00 1922.40 L 6233.30 1924.60 L 6252.70 1927.20 L 6271.70 1930.30 L 6290.40 1933.90 L 6308.80 1937.90 L 6326.90 1942.30 L 6345.00 1947.20 L 6362.70 1952.40 L 6380.10 1958.10 L 6397.00 1964.10 L 6413.60 1970.60 L 6430.20 1977.50 L 6446.30 1984.70 L 6462.00 1992.30 L 6477.30 2000.20 L 6492.20 2008.50 L 6507.00 2017.20 L 6521.30 2026.20 L 6535.10 2035.60 L 6548.50 2045.20 L 6561.40 2055.00 L 6574.10 2065.40 L 6586.30 2076.00 L 6598.00 2086.80 L 6609.20 2097.90 L 6620.00 2109.30 Z" fill="#429cfd" fill-rule="nonzero" node-id="461" stroke="none" target-height="442.99988" target-width="988.5" target-x="5631.5" target-y="1918.4"></path><g node-id="601"><path d="M 6319.80 1718.50 L 6328.20 1709.90 L 6336.00 1701.00 L 6343.40 1692.00 L 6350.30 1682.80 L 6356.70 1673.40 L 6362.80 1663.60 L 6368.30 1653.70 L 6373.40 1643.60 L 6378.00 1633.50 L 6382.10 1623.20 L 6385.80 1612.60 L 6389.00 1602.00 L 6391.70 1591.40 L 6393.90 1580.70 L 6395.70 1569.90 L 6397.00 1559.10 L 6397.80 1548.20 L 6398.20 1537.40 L 6398.10 1526.60 L 6397.50 1515.80 L 6396.50 1505.00 L 6394.90 1494.40 L 6392.90 1483.80 L 6390.50 1473.30 L 6387.50 1462.90 L 6384.10 1452.60 L 6380.20 1442.50 L 6375.80 1432.60 L 6370.90 1422.80 L 6365.50 1413.20 L 6359.80 1403.90 L 6353.50 1394.80 L 6346.70 1386.00 L 6339.40 1377.30 L 6331.60 1368.90 L 6323.30 1360.90 L 6314.90 1353.40 L 6306.20 1346.50 L 6297.20 1340.00 L 6288.10 1334.00 L 6278.60 1328.40 L 6268.90 1323.30 L 6259.10 1318.70 L 6249.10 1314.60 L 6238.90 1310.90 L 6228.60 1307.70 L 6218.10 1305.00 L 6207.60 1302.80 L 6197.00 1301.00 L 6186.20 1299.70 L 6175.40 1298.90 L 6164.60 1298.50 L 6153.80 1298.70 L 6142.90 1299.30 L 6132.10 1300.30 L 6121.20 1301.90 L 6110.50 1303.90 L 6099.80 1306.40 L 6089.20 1309.30 L 6078.50 1312.80 L 6068.10 1316.60 L 6057.90 1321.00 L 6047.70 1325.80 L 6037.70 1331.20 L 6027.80 1337.00 L 6018.20 1343.20 L 6008.80 1349.90 L 5999.70 1357.10 L 5990.70 1364.70 L 5981.90 1373.00 L 5973.50 1381.60 L 5965.60 1390.40 L 5958.20 1399.40 L 5951.30 1408.60 L 5944.90 1418.00 L 5938.90 1427.80 L 5933.30 1437.70 L 5928.30 1447.80 L 5923.70 1457.90 L 5919.60 1468.30 L 5915.90 1478.80 L 5912.70 1489.40 L 5910.00 1500.00 L 5907.70 1510.70 L 5905.90 1521.50 L 5904.60 1532.30 L 5903.80 1543.20 L 5903.40 1554.00 L 5903.50 1564.80 L 5904.10 1575.60 L 5905.20 1586.40 L 5906.70 1597.00 L 5908.70 1607.60 L 5911.20 1618.10 L 5914.10 1628.50 L 5917.60 1638.80 L 5921.50 1648.90 L 5925.80 1658.80 L 5930.70 1668.60 L 5936.10 1678.20 L 5941.90 1687.50 L 5948.20 1696.60 L 5954.90 1705.40 L 5962.20 1714.10 L 5970.10 1722.50 L 5978.30 1730.50 L 5986.80 1738.00 L 5995.40 1744.90 L 6004.40 1751.40 L 6013.60 1757.40 L 6023.10 1763.00 L 6032.70 1768.10 L 6042.50 1772.70 L 6052.50 1776.80 L 6062.70 1780.50 L 6073.10 1783.70 L 6083.50 1786.40 L 6094.00 1788.60 L 6104.70 1790.40 L 6115.40 1791.70 L 6126.20 1792.50 L 6137.00 1792.90 L 6147.80 1792.70 L 6158.70 1792.10 L 6169.60 1791.10 L 6180.40 1789.50 L 6191.10 1787.50 L 6201.80 1785.00 L 6212.50 1782.10 L 6223.10 1778.60 L 6233.50 1774.80 L 6243.80 1770.40 L 6253.90 1765.60 L 6263.90 1760.20 L 6273.80 1754.40 L 6283.40 1748.20 L 6292.80 1741.50 L 6302.00 1734.40 L 6311.00 1726.70 L 6319.80 1718.50 Z" fill="#ffffff" fill-opacity="0.1" fill-rule="nonzero" group-id="14" node-id="465" stroke="none" target-height="494.40002" target-width="494.8003" target-x="5903.4" target-y="1298.5"></path></g><g node-id="602"><path d="M 6620.00 2109.30 L 6607.80 2124.70 L 6595.30 2139.60 L 6582.30 2154.20 L 6568.90 2168.30 L 6555.10 2182.10 L 6540.80 2195.40 L 6526.20 2208.20 L 6511.20 2220.60 L 6495.90 2232.50 L 6480.10 2244.00 L 6464.00 2255.00 L 6447.60 2265.50 L 6430.90 2275.50 L 6413.80 2285.00 L 6396.40 2294.00 L 6378.70 2302.50 L 6360.90 2310.40 L 6342.80 2317.80 L 6324.50 2324.50 L 6306.10 2330.80 L 6287.40 2336.40 L 6268.60 2341.50 L 6249.70 2346.00 L 6230.70 2350.00 L 6211.50 2353.40 L 6192.20 2356.10 L 6172.90 2358.30 L 6153.40 2359.90 L 6133.90 2361.00 L 6114.30 2361.40 L 6095.30 2361.20 L 6076.30 2360.50 L 6057.50 2359.10 L 6038.70 2357.10 L 6020.00 2354.50 L 6001.40 2351.20 L 5983.00 2347.40 L 5964.70 2343.00 L 5946.50 2338.00 L 5928.50 2332.40 L 5910.60 2326.20 L 5893.00 2319.40 L 5875.60 2312.10 L 5858.40 2304.20 L 5841.40 2295.60 L 5824.70 2286.60 L 5808.40 2277.00 L 5792.40 2266.90 L 5776.80 2256.30 L 5761.50 2245.20 L 5746.60 2233.60 L 5732.10 2221.60 L 5718.00 2209.10 L 5704.30 2196.10 L 5691.10 2182.70 L 5678.20 2168.80 L 5665.90 2154.50 L 5654.00 2139.90 L 5642.50 2124.80 L 5631.50 2109.30 L 5643.30 2097.90 L 5655.50 2086.80 L 5668.10 2076.00 L 5681.30 2065.40 L 5694.90 2055.00 L 5708.70 2045.10 L 5722.90 2035.50 L 5737.60 2026.20 L 5752.70 2017.20 L 5768.20 2008.50 L 5783.80 2000.20 L 5799.80 1992.30 L 5816.20 1984.70 L 5832.90 1977.50 L 5850.10 1970.60 L 5867.30 1964.10 L 5884.80 1958.10 L 5902.60 1952.40 L 5920.80 1947.10 L 5939.20 1942.30 L 5957.70 1937.90 L 5976.50 1933.90 L 5995.50 1930.30 L 6014.80 1927.20 L 6034.40 1924.60 L 6053.90 1922.40 L 6073.60 1920.70 L 6093.50 1919.40 L 6113.70 1918.70 L 6134.10 1918.40 L 6154.50 1918.70 L 6174.60 1919.50 L 6194.50 1920.70 L 6214.00 1922.40 L 6233.30 1924.60 L 6252.70 1927.20 L 6271.70 1930.30 L 6290.40 1933.90 L 6308.80 1937.90 L 6326.90 1942.30 L 6345.00 1947.20 L 6362.70 1952.40 L 6380.10 1958.10 L 6397.00 1964.10 L 6413.60 1970.60 L 6430.20 1977.50 L 6446.30 1984.70 L 6462.00 1992.30 L 6477.30 2000.20 L 6492.20 2008.50 L 6507.00 2017.20 L 6521.30 2026.20 L 6535.10 2035.60 L 6548.50 2045.20 L 6561.40 2055.00 L 6574.10 2065.40 L 6586.30 2076.00 L 6598.00 2086.80 L 6609.20 2097.90 L 6620.00 2109.30 Z" fill="#ffffff" fill-opacity="0.1" fill-rule="nonzero" group-id="15" node-id="470" stroke="none" target-height="442.99988" target-width="988.5" target-x="5631.5" target-y="1918.4"></path></g><path d="M 3110.60 3755.30 L 3087.70 3806.30 L 3064.90 3859.20 L 3043.30 3912.50 L 3022.50 3965.90 L 3002.20 4020.10 L 2992.40 4047.30 L 2972.00 4106.70 L 2961.50 4139.10 L 2951.60 4171.70 L 2942.30 4204.40 L 2933.50 4237.30 L 2925.20 4270.30 L 2917.50 4303.40 L 2910.30 4336.70 L 2903.70 4370.10 L 2897.60 4403.60 L 2892.30 4438.70 L 2892.20 4443.20 L 2892.40 4447.70 L 2894.00 4456.60 L 2897.10 4466.40 L 2900.70 4475.90 L 2904.80 4485.30 L 2909.50 4494.40 L 2914.60 4503.30 L 2920.20 4511.90 L 2932.00 4528.10 L 2944.10 4544.00 L 2956.70 4559.40 L 2969.80 4574.60 L 2983.20 4589.30 L 2997.10 4603.70 L 3011.40 4617.60 L 3047.30 4650.50 L 3068.70 4669.40 L 3090.50 4687.90 L 3112.60 4706.00 L 3135.00 4723.80 L 3180.90 4758.70 L 3204.20 4776.00 L 3263.60 4818.60 L 3299.30 4843.20 L 3254.70 4926.90 L 3226.40 4914.30 L 3198.40 4901.20 L 3170.70 4887.60 L 3143.30 4873.50 L 3116.10 4858.90 L 3089.20 4843.80 L 3062.60 4828.20 L 3036.20 4812.10 L 3010.20 4795.60 L 2984.40 4778.50 L 2959.00 4761.00 L 2933.80 4743.00 L 2918.00 4731.20 L 2902.50 4718.90 L 2887.30 4706.30 L 2872.50 4693.30 L 2857.90 4679.90 L 2843.80 4666.10 L 2830.00 4652.00 L 2816.50 4637.50 L 2803.40 4622.70 L 2790.70 4607.60 L 2781.70 4595.90 L 2773.20 4584.00 L 2765.20 4571.70 L 2757.80 4559.10 L 2750.90 4546.20 L 2744.60 4533.00 L 2738.90 4519.50 L 2733.70 4505.70 L 2730.20 4494.10 L 2727.40 4482.30 L 2725.20 4470.40 L 2723.60 4458.40 L 2722.60 4446.30 L 2722.30 4434.10 L 2722.30 4404.30 L 2724.70 4380.50 L 2728.30 4349.40 L 2732.50 4318.50 L 2737.20 4287.60 L 2742.50 4256.80 L 2748.20 4226.20 L 2754.50 4195.60 L 2761.20 4165.10 L 2768.50 4134.70 L 2776.30 4104.50 L 2784.60 4074.40 L 2793.30 4044.40 L 2816.90 3970.60 L 2831.70 3926.90 L 2841.90 3897.90 L 2852.60 3869.10 L 2875.10 3812.00 L 2898.80 3755.50 L 2920.10 3708.20 L 2938.60 3669.70 L 3110.60 3755.30 Z" fill="#e58a7b" fill-rule="nonzero" node-id="473" stroke="none" target-height="1257.2" target-width="577" target-x="2722.3" target-y="3669.7"></path><path d="M 3265.60 4828.40 L 3434.30 4743.00 L 3390.60 4928.10 L 3367.90 4933.00 L 3348.80 4935.70 L 3337.70 4936.70 L 3326.20 4937.30 L 3314.00 4937.40 L 3297.40 4936.50 L 3289.00 4935.50 L 3280.70 4934.10 L 3272.40 4932.30 L 3264.30 4930.00 L 3256.30 4927.20 L 3248.60 4923.80 L 3241.20 4919.90 L 3230.80 4912.90 L 3225.90 4908.80 L 3221.30 4904.30 L 3265.60 4828.40 Z" fill="#e58a7b" fill-rule="nonzero" node-id="475" stroke="none" target-height="194.3999" target-width="213" target-x="3221.3" target-y="4743"></path><path d="M 3588.60 4773.30 L 3543.70 4926.50 L 3390.60 4928.10 L 3434.30 4743.00 L 3588.60 4773.30 Z" fill="#e58a7b" fill-rule="nonzero" node-id="477" stroke="none" target-height="185.1001" target-width="198" target-x="3390.6" target-y="4743"></path><path d="M 3826.60 2862.30 L 3831.40 2878.70 L 3836.80 2895.00 L 3842.80 2911.00 L 3849.30 2926.80 L 3856.40 2942.40 L 3864.10 2957.70 L 3861.60 2960.20 L 3856.20 2964.50 L 3850.10 2967.80 L 3846.80 2969.00 L 3843.40 2969.90 L 3836.50 2970.80 L 3833.10 2970.80 L 3829.60 2970.50 L 3826.20 2969.90 L 3826.60 2862.30 Z" fill="#df5753" fill-rule="nonzero" node-id="479" stroke="none" target-height="108.5" target-width="37.900146" target-x="3826.2" target-y="2862.3"></path><path d="M 3821.60 2863.70 L 3824.00 2863.50 L 3826.40 2862.70 L 3828.60 2861.60 L 3830.60 2860.00 L 3832.50 2858.10 L 3834.30 2855.70 L 3837.00 2850.20 L 3838.10 2847.00 L 3839.30 2840.10 L 3839.50 2836.30 L 3839.30 2832.50 L 3838.10 2825.60 L 3837.00 2822.40 L 3835.80 2819.50 L 3834.30 2816.90 L 3832.50 2814.50 L 3830.60 2812.60 L 3828.60 2811.00 L 3826.40 2809.80 L 3824.00 2809.10 L 3821.60 2808.90 L 3816.90 2809.80 L 3814.70 2811.00 L 3810.70 2814.50 L 3809.00 2816.90 L 3807.50 2819.50 L 3806.20 2822.40 L 3805.10 2825.60 L 3803.90 2832.50 L 3803.70 2836.30 L 3803.90 2840.10 L 3805.10 2847.00 L 3806.20 2850.20 L 3809.00 2855.70 L 3810.70 2858.10 L 3812.60 2860.00 L 3814.70 2861.60 L 3816.90 2862.70 L 3821.60 2863.70 Z" fill="#263238" fill-rule="nonzero" node-id="481" stroke="none" target-height="54.80005" target-width="35.80005" target-x="3803.7" target-y="2808.9"></path><path d="M 3816.40 2809.80 L 3852.60 2799.50 L 3849.70 2803.50 L 3844.70 2808.80 L 3839.10 2812.90 L 3834.00 2815.10 L 3831.30 2815.70 L 3828.50 2815.70 L 3825.50 2815.30 L 3823.40 2814.60 L 3821.20 2813.50 L 3816.40 2809.80 Z" fill="#263238" fill-rule="nonzero" node-id="483" stroke="none" target-height="16.199951" target-width="36.200195" target-x="3816.4" target-y="2799.5"></path><path d="M 3360.80 3037.40 L 3367.90 3077.40 L 3371.10 3099.20 L 3374.20 3122.60 L 3376.70 3146.30 L 3378.80 3170.90 L 3380.60 3204.00 L 3381.00 3220.60 L 3381.00 3237.20 L 3380.70 3253.70 L 3379.90 3269.90 L 3378.70 3286.00 L 3376.90 3301.70 L 3374.70 3316.90 L 3372.70 3328.20 L 3370.30 3339.10 L 3367.60 3349.60 L 3364.60 3359.70 L 3361.20 3369.80 L 3357.40 3379.30 L 3353.30 3388.30 L 3348.80 3396.70 L 3344.80 3403.30 L 3340.60 3409.60 L 3336.10 3415.40 L 3331.40 3420.80 L 3326.40 3425.90 L 3329.50 3433.90 L 3332.60 3440.10 L 3335.00 3444.40 L 3338.10 3449.40 L 3342.00 3455.10 L 3351.20 3466.50 L 3357.30 3472.90 L 3363.60 3478.90 L 3371.00 3485.10 L 3379.60 3491.50 L 3393.40 3500.40 L 3410.20 3509.30 L 3418.90 3513.20 L 3428.40 3517.10 L 3438.90 3520.80 L 3450.20 3524.30 L 3461.40 3527.30 L 3473.50 3530.00 L 3486.60 3532.50 L 3500.80 3534.60 L 3514.70 3536.20 L 3529.70 3537.40 L 3545.90 3538.20 L 3563.30 3538.50 L 3579.00 3538.30 L 3593.10 3537.80 L 3616.90 3535.90 L 3628.60 3534.30 L 3638.80 3532.60 L 3655.60 3528.70 L 3663.60 3526.10 L 3670.50 3523.40 L 3676.30 3520.70 L 3681.20 3517.90 L 3686.10 3514.60 L 3690.10 3511.30 L 3693.40 3508.00 L 3696.00 3504.70 L 3698.30 3501.00 L 3700.10 3497.30 L 3702.10 3489.90 L 3702.50 3484.80 L 3702.30 3479.70 L 3701.50 3474.50 L 3700.20 3469.50 L 3698.40 3464.60 L 3696.30 3459.70 L 3692.60 3452.80 L 3684.40 3440.60 L 3676.80 3431.40 L 3671.60 3425.90 L 3660.80 3423.10 L 3650.90 3420.00 L 3641.80 3416.70 L 3633.50 3413.30 L 3626.00 3409.60 L 3618.30 3405.40 L 3611.30 3401.00 L 3605.00 3396.40 L 3599.20 3391.70 L 3594.00 3386.90 L 3589.00 3381.60 L 3580.50 3370.50 L 3577.00 3364.80 L 3573.80 3358.90 L 3570.40 3351.20 L 3567.50 3343.40 L 3565.20 3335.20 L 3563.50 3326.90 L 3562.20 3318.40 L 3561.40 3309.80 L 3561.00 3301.00 L 3561.10 3291.90 L 3561.70 3279.90 L 3562.90 3267.70 L 3564.80 3255.30 L 3567.00 3242.90 L 3572.60 3218.20 L 3360.80 3037.40 Z" fill="#e58a7b" fill-rule="nonzero" node-id="485" stroke="none" target-height="501.1001" target-width="376.1001" target-x="3326.4" target-y="3037.4"></path><path d="M 3300.40 2784.50 L 3304.40 2849.30 L 3308.70 2905.40 L 3312.30 2939.20 L 3316.80 2969.80 L 3319.50 2984.30 L 3322.60 2997.90 L 3326.20 3011.50 L 3330.30 3024.50 L 3334.80 3037.10 L 3338.50 3046.40 L 3347.10 3064.60 L 3352.00 3073.50 L 3357.10 3082.20 L 3362.70 3090.90 L 3368.80 3099.70 L 3375.40 3108.60 L 3389.60 3125.80 L 3397.70 3134.60 L 3406.40 3143.70 L 3416.00 3153.10 L 3425.90 3161.80 L 3435.80 3169.90 L 3445.90 3177.40 L 3456.10 3184.20 L 3466.70 3190.70 L 3477.40 3196.50 L 3488.20 3201.80 L 3499.00 3206.50 L 3509.90 3210.60 L 3521.10 3214.30 L 3532.20 3217.40 L 3543.30 3220.00 L 3554.40 3222.00 L 3565.60 3223.50 L 3576.80 3224.50 L 3588.00 3225.00 L 3599.00 3225.00 L 3610.00 3224.50 L 3620.90 3223.50 L 3631.80 3221.90 L 3642.50 3219.90 L 3653.10 3217.40 L 3663.50 3214.50 L 3673.80 3211.00 L 3683.90 3207.10 L 3693.70 3202.70 L 3703.40 3197.80 L 3712.80 3192.50 L 3722.00 3186.70 L 3730.80 3180.50 L 3739.40 3173.80 L 3747.70 3166.70 L 3755.60 3159.20 L 3763.30 3151.10 L 3770.50 3142.80 L 3777.30 3134.00 L 3783.80 3124.70 L 3789.90 3115.00 L 3795.60 3104.80 L 3800.70 3094.50 L 3805.40 3083.70 L 3809.60 3072.40 L 3813.30 3060.60 L 3816.60 3048.30 L 3820.30 3031.50 L 3823.60 3013.90 L 3826.50 2995.30 L 3828.80 2976.70 L 3830.70 2957.50 L 3832.00 2937.70 L 3832.70 2917.90 L 3832.90 2897.70 L 3832.40 2877.30 L 3831.70 2862.00 L 3829.10 2831.30 L 3825.10 2800.70 L 3822.50 2785.50 L 3819.50 2770.50 L 3816.10 2755.60 L 3812.30 2740.80 L 3808.10 2726.30 L 3803.40 2712.00 L 3798.30 2698.10 L 3792.70 2684.20 L 3786.70 2670.80 L 3780.20 2657.80 L 3773.30 2645.20 L 3767.30 2635.20 L 3761.10 2625.60 L 3754.50 2616.40 L 3747.70 2607.40 L 3740.50 2598.80 L 3733.00 2590.40 L 3725.10 2582.40 L 3716.90 2574.80 L 3708.40 2567.60 L 3699.50 2560.70 L 3690.30 2554.30 L 3680.80 2548.30 L 3670.80 2542.70 L 3660.40 2537.60 L 3649.60 2532.90 L 3639.60 2529.00 L 3629.40 2525.50 L 3619.20 2522.50 L 3608.80 2520.00 L 3598.30 2517.80 L 3587.70 2516.20 L 3577.10 2514.90 L 3566.50 2514.10 L 3555.80 2513.80 L 3545.10 2513.90 L 3534.40 2514.50 L 3523.60 2515.50 L 3512.90 2517.00 L 3502.40 2518.90 L 3492.00 2521.30 L 3481.60 2524.10 L 3471.40 2527.30 L 3461.40 2531.00 L 3451.50 2535.10 L 3441.80 2539.60 L 3432.30 2544.50 L 3423.00 2549.80 L 3413.90 2555.50 L 3405.10 2561.60 L 3388.20 2574.90 L 3380.20 2582.00 L 3372.50 2589.50 L 3365.20 2597.30 L 3358.20 2605.40 L 3351.60 2613.70 L 3345.30 2622.40 L 3339.30 2631.30 L 3333.80 2640.40 L 3328.60 2649.80 L 3323.80 2659.50 L 3319.50 2669.30 L 3315.50 2679.30 L 3312.00 2689.40 L 3309.00 2699.70 L 3306.30 2710.10 L 3302.40 2731.00 L 3301.10 2741.70 L 3300.30 2752.30 L 3299.90 2763.00 L 3299.90 2773.70 L 3300.40 2784.50 Z" fill="#e58a7b" fill-rule="nonzero" node-id="487" stroke="none" target-height="711.19995" target-width="533" target-x="3299.9" target-y="2513.8"></path><path d="M 3318.80 2576.10 L 3306.00 2580.30 L 3292.80 2584.10 L 3252.90 2594.30 L 3231.30 2600.90 L 3214.80 2607.40 L 3206.90 2611.20 L 3199.20 2615.60 L 3191.90 2620.50 L 3181.60 2628.80 L 3176.70 2633.70 L 3172.00 2638.90 L 3167.70 2644.40 L 3163.60 2650.50 L 3159.70 2657.20 L 3156.00 2664.60 L 3150.00 2680.10 L 3147.40 2689.10 L 3145.00 2698.90 L 3143.20 2708.60 L 3141.70 2719.30 L 3140.50 2731.00 L 3139.80 2743.80 L 3139.50 2756.20 L 3139.60 2769.80 L 3140.20 2784.60 L 3141.30 2800.80 L 3142.70 2816.40 L 3144.70 2833.40 L 3147.20 2851.70 L 3154.50 2897.20 L 3158.00 2921.40 L 3160.80 2944.10 L 3163.00 2965.50 L 3164.90 2987.40 L 3166.30 3007.90 L 3167.70 3045.10 L 3167.80 3063.50 L 3167.00 3096.60 L 3166.10 3111.30 L 3164.40 3131.30 L 3162.40 3149.10 L 3160.10 3165.00 L 3157.20 3180.90 L 3154.30 3194.90 L 3151.20 3207.00 L 3147.80 3219.20 L 3141.40 3238.20 L 3136.30 3250.60 L 3132.20 3259.50 L 3128.10 3267.10 L 3122.70 3275.40 L 3162.30 3282.60 L 3208.70 3289.50 L 3236.70 3293.10 L 3292.80 3299.00 L 3344.20 3303.00 L 3370.90 3304.60 L 3399.20 3305.90 L 3429.10 3306.90 L 3459.10 3307.40 L 3490.30 3307.40 L 3522.90 3307.00 L 3572.00 3305.20 L 3623.20 3301.90 L 3648.90 3299.70 L 3675.00 3297.10 L 3701.40 3294.00 L 3728.20 3290.40 L 3754.80 3286.30 L 3781.60 3281.70 L 3808.50 3276.60 L 3835.70 3270.90 L 3824.50 3249.70 L 3819.20 3239.10 L 3806.20 3211.00 L 3799.00 3194.10 L 3791.80 3176.50 L 3784.60 3157.00 L 3775.40 3130.00 L 3767.10 3101.30 L 3760.00 3071.50 L 3757.10 3056.50 L 3754.80 3041.30 L 3752.90 3026.10 L 3751.90 3014.60 L 3751.30 3003.20 L 3751.20 2980.70 L 3751.80 2969.50 L 3752.90 2958.50 L 3754.50 2947.60 L 3761.80 2911.30 L 3766.10 2888.10 L 3769.40 2867.00 L 3772.10 2845.90 L 3774.10 2827.00 L 3775.40 2810.10 L 3776.20 2793.20 L 3776.50 2778.40 L 3776.30 2765.40 L 3775.80 2752.40 L 3775.00 2741.40 L 3774.00 2732.10 L 3772.00 2718.80 L 3770.10 2709.20 L 3768.00 2701.00 L 3764.90 2692.00 L 3740.90 2680.10 L 3697.20 2659.70 L 3658.50 2642.80 L 3614.50 2625.00 L 3590.80 2616.10 L 3566.80 2607.60 L 3542.20 2599.40 L 3525.60 2594.20 L 3492.50 2584.90 L 3475.90 2580.80 L 3459.60 2577.20 L 3443.60 2574.00 L 3427.60 2571.40 L 3412.10 2569.40 L 3397.20 2568.10 L 3386.00 2567.50 L 3375.20 2567.30 L 3364.90 2567.60 L 3355.00 2568.30 L 3345.10 2569.50 L 3335.80 2571.20 L 3327.00 2573.40 L 3318.80 2576.10 Z" fill="#263238" fill-rule="nonzero" node-id="489" stroke="none" target-height="740.09985" target-width="713" target-x="3122.7" target-y="2567.3"></path><path d="M 3712.40 2881.10 L 3712.60 2889.90 L 3713.20 2898.70 L 3714.30 2907.30 L 3715.80 2916.00 L 3717.60 2924.50 L 3719.90 2932.90 L 3725.50 2949.40 L 3732.70 2965.30 L 3741.50 2980.60 L 3745.60 2986.30 L 3749.50 2991.00 L 3753.30 2994.70 L 3757.00 2997.60 L 3761.10 3000.10 L 3765.00 3001.80 L 3768.70 3002.70 L 3775.90 3002.80 L 3779.40 3001.90 L 3782.70 3000.40 L 3786.10 2998.30 L 3789.00 2995.80 L 3791.80 2992.70 L 3794.50 2989.00 L 3797.10 2984.70 L 3799.10 2980.20 L 3801.00 2975.20 L 3804.00 2963.40 L 3804.90 2957.30 L 3805.70 2950.70 L 3806.30 2943.40 L 3806.50 2928.50 L 3806.20 2920.60 L 3804.60 2904.90 L 3801.60 2889.10 L 3797.10 2874.20 L 3794.30 2867.10 L 3791.00 2860.40 L 3787.30 2854.20 L 3784.20 2849.80 L 3780.90 2845.80 L 3777.30 2842.20 L 3773.40 2838.90 L 3768.30 2835.40 L 3763.60 2832.70 L 3759.00 2830.90 L 3754.80 2829.70 L 3750.30 2829.00 L 3746.00 2829.00 L 3742.00 2829.60 L 3738.20 2830.70 L 3734.50 2832.40 L 3731.10 2834.60 L 3727.90 2837.40 L 3724.80 2840.70 L 3719.80 2848.30 L 3717.70 2852.80 L 3715.80 2858.00 L 3713.40 2868.60 L 3712.40 2881.10 Z" fill="#e58a7b" fill-rule="nonzero" node-id="491" stroke="none" target-height="173.80005" target-width="94.1001" target-x="3712.4" target-y="2829"></path><path d="M 3238.00 2607.30 L 3236.20 2601.60 L 3235.10 2595.80 L 3234.50 2589.90 L 3234.50 2583.80 L 3235.10 2577.60 L 3236.30 2571.60 L 3237.90 2565.50 L 3240.20 2559.30 L 3243.10 2553.00 L 3246.60 2546.50 L 3250.50 2540.60 L 3254.90 2534.60 L 3260.00 2528.50 L 3265.80 2522.50 L 3272.30 2516.40 L 3278.80 2510.90 L 3286.00 2505.50 L 3293.90 2500.10 L 3302.60 2494.90 L 3312.00 2489.70 L 3321.20 2485.10 L 3331.10 2480.70 L 3341.80 2476.50 L 3353.30 2472.50 L 3365.50 2468.70 L 3377.40 2465.50 L 3390.10 2462.60 L 3403.50 2460.00 L 3417.80 2457.70 L 3432.80 2455.90 L 3447.40 2454.50 L 3462.80 2453.60 L 3478.90 2453.10 L 3495.90 2453.10 L 3513.80 2453.60 L 3531.00 2454.50 L 3549.00 2456.00 L 3567.80 2458.10 L 3587.50 2460.80 L 3608.10 2464.20 L 3623.00 2467.10 L 3637.10 2470.20 L 3650.60 2473.70 L 3663.30 2477.30 L 3675.40 2481.30 L 3687.80 2485.70 L 3710.60 2495.20 L 3721.10 2500.20 L 3731.10 2505.30 L 3743.50 2512.40 L 3755.20 2519.60 L 3766.00 2527.00 L 3776.10 2534.50 L 3786.00 2542.60 L 3795.30 2550.70 L 3803.80 2558.80 L 3811.70 2567.00 L 3819.40 2575.70 L 3826.50 2584.20 L 3833.00 2592.70 L 3839.00 2601.10 L 3846.50 2612.70 L 3853.20 2624.00 L 3859.00 2634.90 L 3864.40 2646.10 L 3869.00 2656.60 L 3872.90 2666.60 L 3879.40 2686.00 L 3884.80 2706.70 L 3888.20 2725.40 L 3889.40 2736.30 L 3835.00 2671.50 L 3837.40 2739.70 L 3560.40 2792.90 L 3238.00 2607.30 Z" fill="#263238" fill-rule="nonzero" node-id="493" stroke="none" target-height="339.7998" target-width="654.8999" target-x="3234.5" target-y="2453.1"></path><path d="M 3260.90 3438.60 L 3260.50 3430.40 L 3260.80 3426.40 L 3261.60 3422.40 L 3262.80 3418.50 L 3264.30 3414.70 L 3266.20 3411.10 L 3268.40 3407.70 L 3271.00 3404.40 L 3277.00 3398.90 L 3283.80 3394.60 L 3291.40 3391.80 L 3295.40 3390.80 L 3331.50 3385.10 L 3375.30 3378.70 L 3424.30 3372.20 L 3450.00 3369.20 L 3502.70 3363.80 L 3528.80 3361.80 L 3554.80 3360.10 L 3596.00 3358.80 L 3626.10 3359.00 L 3640.70 3359.70 L 3654.00 3360.80 L 3666.10 3362.20 L 3675.10 3363.70 L 3683.40 3365.50 L 3690.80 3367.50 L 3697.60 3369.70 L 3704.50 3372.30 L 3710.70 3375.20 L 3721.40 3381.30 L 3730.80 3388.50 L 3734.80 3392.20 L 3738.40 3396.00 L 3742.80 3401.50 L 3746.60 3407.10 L 3749.70 3412.70 L 3752.40 3418.60 L 3754.60 3424.50 L 3756.30 3430.40 L 3757.70 3436.30 L 3759.40 3447.90 L 3759.50 3452.40 L 3759.10 3456.80 L 3758.30 3461.10 L 3757.00 3465.50 L 3755.30 3469.60 L 3753.10 3473.50 L 3750.60 3477.10 L 3747.60 3480.50 L 3744.30 3483.60 L 3740.70 3486.20 L 3736.90 3488.50 L 3732.80 3490.40 L 3728.50 3491.80 L 3719.70 3493.20 L 3300.70 3474.60 L 3295.80 3474.00 L 3291.10 3473.00 L 3286.60 3471.40 L 3282.20 3469.30 L 3278.20 3466.70 L 3274.30 3463.70 L 3270.90 3460.20 L 3268.00 3456.40 L 3265.40 3452.30 L 3263.40 3447.90 L 3261.90 3443.30 L 3260.90 3438.60 Z" fill="#429cfd" fill-rule="nonzero" node-id="495" stroke="none" target-height="134.3999" target-width="499" target-x="3260.5" target-y="3358.8"></path><g node-id="603"><path d="M 3260.90 3438.60 L 3260.50 3430.40 L 3260.80 3426.40 L 3261.60 3422.40 L 3262.80 3418.50 L 3264.30 3414.70 L 3266.20 3411.10 L 3268.40 3407.70 L 3271.00 3404.40 L 3277.00 3398.90 L 3283.80 3394.60 L 3291.40 3391.80 L 3295.40 3390.80 L 3331.50 3385.10 L 3375.30 3378.70 L 3424.30 3372.20 L 3450.00 3369.20 L 3502.70 3363.80 L 3528.80 3361.80 L 3554.80 3360.10 L 3596.00 3358.80 L 3626.10 3359.00 L 3640.70 3359.70 L 3654.00 3360.80 L 3666.10 3362.20 L 3675.10 3363.70 L 3683.40 3365.50 L 3690.80 3367.50 L 3697.60 3369.70 L 3704.50 3372.30 L 3710.70 3375.20 L 3721.40 3381.30 L 3730.80 3388.50 L 3734.80 3392.20 L 3738.40 3396.00 L 3742.80 3401.50 L 3746.60 3407.10 L 3749.70 3412.70 L 3752.40 3418.60 L 3754.60 3424.50 L 3756.30 3430.40 L 3757.70 3436.30 L 3759.40 3447.90 L 3759.50 3452.40 L 3759.10 3456.80 L 3758.30 3461.10 L 3757.00 3465.50 L 3755.30 3469.60 L 3753.10 3473.50 L 3750.60 3477.10 L 3747.60 3480.50 L 3744.30 3483.60 L 3740.70 3486.20 L 3736.90 3488.50 L 3732.80 3490.40 L 3728.50 3491.80 L 3719.70 3493.20 L 3300.70 3474.60 L 3295.80 3474.00 L 3291.10 3473.00 L 3286.60 3471.40 L 3282.20 3469.30 L 3278.20 3466.70 L 3274.30 3463.70 L 3270.90 3460.20 L 3268.00 3456.40 L 3265.40 3452.30 L 3263.40 3447.90 L 3261.90 3443.30 L 3260.90 3438.60 Z" fill="#000000" fill-opacity="0.6" fill-rule="nonzero" group-id="16" node-id="499" stroke="none" target-height="134.3999" target-width="499" target-x="3260.5" target-y="3358.8"></path></g><path d="M 3500.60 7605.60 L 3654.40 7712.40 L 3911.80 7363.50 L 3757.90 7256.80 L 3500.60 7605.60 Z" fill="#e58a7b" fill-rule="nonzero" node-id="502" stroke="none" target-height="455.6001" target-width="411.19995" target-x="3500.6" target-y="7256.8"></path><path d="M 3141.80 7598.60 L 3304.30 7691.20 L 3532.10 7322.20 L 3369.60 7229.60 L 3141.80 7598.60 Z" fill="#e58a7b" fill-rule="nonzero" node-id="504" stroke="none" target-height="461.6001" target-width="390.30005" target-x="3141.8" target-y="7229.6"></path><path d="M 3911.50 7363.50 L 3778.90 7543.30 L 3625.10 7436.70 L 3757.70 7256.80 L 3911.50 7363.50 Z" fill="#cf6f64" fill-rule="nonzero" node-id="506" stroke="none" target-height="286.5" target-width="286.3999" target-x="3625.1" target-y="7256.8"></path><path d="M 3369.40 7229.60 L 3532.10 7322.40 L 3414.70 7512.50 L 3252.00 7419.70 L 3369.40 7229.60 Z" fill="#cf6f64" fill-rule="nonzero" node-id="508" stroke="none" target-height="282.8999" target-width="280.1001" target-x="3252" target-y="7229.6"></path><path d="M 3630.30 4603.50 L 3655.90 4637.40 L 3685.00 4678.10 L 3702.30 4703.10 L 3722.40 4733.10 L 3743.20 4764.80 L 3766.20 4801.10 L 3797.60 4852.60 L 3830.90 4909.80 L 3847.80 4940.10 L 3865.20 4972.00 L 3882.10 5004.00 L 3899.20 5037.50 L 3916.50 5072.40 L 3941.60 5125.50 L 3966.60 5181.30 L 3978.60 5209.60 L 4002.00 5267.70 L 4013.30 5297.60 L 4024.30 5327.50 L 4034.90 5357.90 L 4045.10 5388.80 L 4055.00 5420.00 L 4064.30 5451.40 L 4073.20 5483.10 L 4081.60 5515.10 L 4089.50 5547.50 L 4100.90 5599.50 L 4106.00 5625.80 L 4115.00 5678.80 L 4118.80 5705.40 L 4122.20 5732.10 L 4125.10 5758.90 L 4127.60 5785.80 L 4129.50 5812.80 L 4131.00 5839.80 L 4131.90 5866.80 L 4132.20 5893.90 L 4132.00 5921.10 L 4131.20 5948.30 L 4129.90 5975.50 L 4127.90 6002.60 L 4125.20 6029.80 L 4122.00 6057.00 L 4118.10 6084.30 L 4114.30 6107.50 L 4105.30 6154.90 L 4100.10 6179.10 L 4094.40 6203.60 L 4086.90 6233.90 L 4078.80 6264.50 L 4070.10 6295.50 L 4060.80 6326.80 L 4051.00 6357.80 L 4040.80 6389.00 L 4030.10 6420.30 L 4018.90 6451.90 L 4003.50 6493.70 L 3987.40 6535.40 L 3970.60 6577.20 L 3953.30 6618.70 L 3935.60 6660.00 L 3917.50 6700.90 L 3898.90 6741.60 L 3880.20 6781.80 L 3861.30 6821.30 L 3842.00 6860.60 L 3803.80 6936.50 L 3784.40 6973.80 L 3746.60 7044.80 L 3718.40 7096.40 L 3691.50 7144.50 L 3664.80 7191.10 L 3615.90 7274.50 L 3594.30 7310.60 L 3555.60 7373.50 L 3525.90 7420.70 L 3500.10 7460.50 L 3252.30 7337.50 L 3281.10 7279.00 L 3309.60 7220.30 L 3337.50 7161.40 L 3364.90 7102.30 L 3391.90 7043.00 L 3418.40 6983.50 L 3444.40 6923.70 L 3469.90 6863.80 L 3494.90 6803.60 L 3519.50 6743.20 L 3543.60 6682.70 L 3567.10 6622.00 L 3590.20 6561.00 L 3612.80 6499.90 L 3634.90 6438.60 L 3656.50 6377.20 L 3677.70 6315.50 L 3698.30 6253.60 L 3704.30 6234.30 L 3709.90 6214.80 L 3714.90 6195.30 L 3719.50 6175.70 L 3723.50 6156.00 L 3727.10 6136.20 L 3730.10 6116.30 L 3732.70 6096.40 L 3734.70 6076.40 L 3736.30 6056.40 L 3737.30 6036.30 L 3737.90 6016.20 L 3737.90 5996.10 L 3737.40 5976.00 L 3736.50 5955.90 L 3735.00 5935.80 L 3733.00 5915.70 L 3730.50 5895.60 L 3727.50 5875.50 L 3724.00 5855.60 L 3720.00 5835.90 L 3715.50 5816.30 L 3710.50 5796.80 L 3705.00 5777.40 L 3699.10 5758.20 L 3692.70 5739.10 L 3685.80 5720.30 L 3678.40 5701.60 L 3670.60 5683.10 L 3662.30 5664.80 L 3653.50 5646.60 L 3644.30 5628.80 L 3634.60 5611.10 L 3624.50 5593.70 L 3613.90 5576.50 L 3602.90 5559.50 L 3591.80 5543.80 L 3580.80 5528.90 L 3569.70 5514.60 L 3558.60 5501.00 L 3547.50 5488.10 L 3532.90 5472.00 L 3518.40 5456.90 L 3503.90 5442.70 L 3489.50 5429.20 L 3474.50 5416.10 L 3459.60 5403.70 L 3444.80 5391.90 L 3430.10 5380.90 L 3415.00 5370.00 L 3385.30 5350.00 L 3370.60 5340.80 L 3350.80 5328.80 L 3312.20 5306.90 L 3203.50 5249.00 L 3186.70 5239.60 L 3170.80 5230.20 L 3155.60 5220.80 L 3144.40 5213.40 L 3133.60 5205.80 L 3123.40 5198.10 L 3113.70 5190.20 L 3104.20 5181.90 L 3095.20 5173.30 L 3086.80 5164.30 L 3078.90 5155.10 L 3067.30 5139.40 L 3062.10 5131.00 L 3057.10 5122.40 L 3052.60 5113.30 L 3048.50 5104.20 L 3044.70 5094.60 L 3041.40 5084.50 L 3038.40 5073.90 L 3035.80 5062.80 L 3033.80 5051.90 L 3032.10 5040.30 L 3030.90 5028.00 L 3030.20 5015.10 L 3029.90 5001.30 L 3030.10 4988.10 L 3030.80 4974.10 L 3032.00 4959.30 L 3033.70 4943.50 L 3036.00 4926.80 L 3038.70 4910.90 L 3045.70 4876.10 L 3050.20 4857.10 L 3055.40 4837.10 L 3062.20 4813.00 L 3070.10 4787.20 L 3079.10 4759.60 L 3089.30 4730.00 L 3099.80 4701.40 L 3111.50 4670.80 L 3124.60 4638.20 L 3139.00 4603.50 L 3630.30 4603.50 Z" fill="#429cfd" fill-rule="nonzero" node-id="510" stroke="none" target-height="2857" target-width="1102.3003" target-x="3029.9" target-y="4603.5"></path><g node-id="604"><path d="M 3630.30 4603.50 L 3655.90 4637.40 L 3685.00 4678.10 L 3702.30 4703.10 L 3722.40 4733.10 L 3743.20 4764.80 L 3766.20 4801.10 L 3797.60 4852.60 L 3830.90 4909.80 L 3847.80 4940.10 L 3865.20 4972.00 L 3882.10 5004.00 L 3899.20 5037.50 L 3916.50 5072.40 L 3941.60 5125.50 L 3966.60 5181.30 L 3978.60 5209.60 L 4002.00 5267.70 L 4013.30 5297.60 L 4024.30 5327.50 L 4034.90 5357.90 L 4045.10 5388.80 L 4055.00 5420.00 L 4064.30 5451.40 L 4073.20 5483.10 L 4081.60 5515.10 L 4089.50 5547.50 L 4100.90 5599.50 L 4106.00 5625.80 L 4115.00 5678.80 L 4118.80 5705.40 L 4122.20 5732.10 L 4125.10 5758.90 L 4127.60 5785.80 L 4129.50 5812.80 L 4131.00 5839.80 L 4131.90 5866.80 L 4132.20 5893.90 L 4132.00 5921.10 L 4131.20 5948.30 L 4129.90 5975.50 L 4127.90 6002.60 L 4125.20 6029.80 L 4122.00 6057.00 L 4118.10 6084.30 L 4114.30 6107.50 L 4105.30 6154.90 L 4100.10 6179.10 L 4094.40 6203.60 L 4086.90 6233.90 L 4078.80 6264.50 L 4070.10 6295.50 L 4060.80 6326.80 L 4051.00 6357.80 L 4040.80 6389.00 L 4030.10 6420.30 L 4018.90 6451.90 L 4003.50 6493.70 L 3987.40 6535.40 L 3970.60 6577.20 L 3953.30 6618.70 L 3935.60 6660.00 L 3917.50 6700.90 L 3898.90 6741.60 L 3880.20 6781.80 L 3861.30 6821.30 L 3842.00 6860.60 L 3803.80 6936.50 L 3784.40 6973.80 L 3746.60 7044.80 L 3718.40 7096.40 L 3691.50 7144.50 L 3664.80 7191.10 L 3615.90 7274.50 L 3594.30 7310.60 L 3555.60 7373.50 L 3525.90 7420.70 L 3500.10 7460.50 L 3252.30 7337.50 L 3281.10 7279.00 L 3309.60 7220.30 L 3337.50 7161.40 L 3364.90 7102.30 L 3391.90 7043.00 L 3418.40 6983.50 L 3444.40 6923.70 L 3469.90 6863.80 L 3494.90 6803.60 L 3519.50 6743.20 L 3543.60 6682.70 L 3567.10 6622.00 L 3590.20 6561.00 L 3612.80 6499.90 L 3634.90 6438.60 L 3656.50 6377.20 L 3677.70 6315.50 L 3698.30 6253.60 L 3704.30 6234.30 L 3709.90 6214.80 L 3714.90 6195.30 L 3719.50 6175.70 L 3723.50 6156.00 L 3727.10 6136.20 L 3730.10 6116.30 L 3732.70 6096.40 L 3734.70 6076.40 L 3736.30 6056.40 L 3737.30 6036.30 L 3737.90 6016.20 L 3737.90 5996.10 L 3737.40 5976.00 L 3736.50 5955.90 L 3735.00 5935.80 L 3733.00 5915.70 L 3730.50 5895.60 L 3727.50 5875.50 L 3724.00 5855.60 L 3720.00 5835.90 L 3715.50 5816.30 L 3710.50 5796.80 L 3705.00 5777.40 L 3699.10 5758.20 L 3692.70 5739.10 L 3685.80 5720.30 L 3678.40 5701.60 L 3670.60 5683.10 L 3662.30 5664.80 L 3653.50 5646.60 L 3644.30 5628.80 L 3634.60 5611.10 L 3624.50 5593.70 L 3613.90 5576.50 L 3602.90 5559.50 L 3591.80 5543.80 L 3580.80 5528.90 L 3569.70 5514.60 L 3558.60 5501.00 L 3547.50 5488.10 L 3532.90 5472.00 L 3518.40 5456.90 L 3503.90 5442.70 L 3489.50 5429.20 L 3474.50 5416.10 L 3459.60 5403.70 L 3444.80 5391.90 L 3430.10 5380.90 L 3415.00 5370.00 L 3385.30 5350.00 L 3370.60 5340.80 L 3350.80 5328.80 L 3312.20 5306.90 L 3203.50 5249.00 L 3186.70 5239.60 L 3170.80 5230.20 L 3155.60 5220.80 L 3144.40 5213.40 L 3133.60 5205.80 L 3123.40 5198.10 L 3113.70 5190.20 L 3104.20 5181.90 L 3095.20 5173.30 L 3086.80 5164.30 L 3078.90 5155.10 L 3067.30 5139.40 L 3062.10 5131.00 L 3057.10 5122.40 L 3052.60 5113.30 L 3048.50 5104.20 L 3044.70 5094.60 L 3041.40 5084.50 L 3038.40 5073.90 L 3035.80 5062.80 L 3033.80 5051.90 L 3032.10 5040.30 L 3030.90 5028.00 L 3030.20 5015.10 L 3029.90 5001.30 L 3030.10 4988.10 L 3030.80 4974.10 L 3032.00 4959.30 L 3033.70 4943.50 L 3036.00 4926.80 L 3038.70 4910.90 L 3045.70 4876.10 L 3050.20 4857.10 L 3055.40 4837.10 L 3062.20 4813.00 L 3070.10 4787.20 L 3079.10 4759.60 L 3089.30 4730.00 L 3099.80 4701.40 L 3111.50 4670.80 L 3124.60 4638.20 L 3139.00 4603.50 L 3630.30 4603.50 Z" fill="#000000" fill-opacity="0.6" fill-rule="nonzero" group-id="17" node-id="514" stroke="none" target-height="2857" target-width="1102.3003" target-x="3029.9" target-y="4603.5"></path></g><g node-id="605"><path d="M 3924.90 5089.60 L 3837.60 5002.80 L 3844.10 5045.40 L 4054.20 6347.10 L 4062.40 6321.30 L 4077.60 6269.40 L 4084.60 6243.30 L 4091.30 6217.10 L 4097.50 6190.80 L 4108.70 6138.00 L 4113.70 6111.40 L 4118.30 6084.70 L 4122.20 6058.00 L 4125.40 6031.30 L 4128.10 6004.70 L 4130.10 5978.00 L 4131.50 5951.40 L 4132.30 5924.70 L 4132.60 5898.00 L 4132.40 5871.40 L 4131.60 5844.90 L 4130.30 5818.40 L 4128.50 5791.90 L 4126.20 5765.50 L 4123.50 5739.20 L 4120.30 5713.00 L 4116.60 5686.90 L 4111.40 5654.30 L 4105.70 5621.90 L 4099.30 5589.80 L 4092.30 5557.90 L 4084.70 5526.10 L 4076.60 5494.60 L 4068.10 5463.50 L 4059.10 5432.60 L 4049.60 5401.80 L 4039.70 5371.50 L 4029.50 5341.60 L 4018.90 5312.10 L 3996.70 5253.70 L 3985.20 5225.30 L 3961.50 5169.50 L 3949.40 5142.20 L 3924.90 5089.60 Z" fill="#000000" fill-opacity="0.4" fill-rule="nonzero" group-id="18" node-id="519" stroke="none" target-height="1344.3003" target-width="295" target-x="3837.6" target-y="5002.8"></path></g><path d="M 3898.40 4603.50 L 3926.10 4640.80 L 3977.30 4712.00 L 4023.50 4778.00 L 4049.80 4816.50 L 4106.00 4900.40 L 4135.60 4945.70 L 4166.70 4994.30 L 4198.00 5044.10 L 4230.30 5096.60 L 4273.20 5168.40 L 4316.30 5243.10 L 4337.70 5281.10 L 4358.90 5319.80 L 4379.80 5358.70 L 4400.30 5397.90 L 4420.50 5437.40 L 4440.20 5477.20 L 4459.40 5517.00 L 4478.00 5556.90 L 4491.50 5587.00 L 4504.70 5617.00 L 4529.50 5676.60 L 4541.40 5706.60 L 4552.70 5736.30 L 4563.40 5765.70 L 4573.50 5794.80 L 4583.20 5824.30 L 4592.20 5853.40 L 4600.40 5881.90 L 4608.00 5910.10 L 4613.80 5933.00 L 4619.00 5955.50 L 4627.80 5999.30 L 4631.50 6020.70 L 4634.80 6042.50 L 4637.50 6063.80 L 4639.60 6084.70 L 4641.10 6105.10 L 4642.10 6125.00 L 4642.40 6140.80 L 4642.10 6157.00 L 4641.20 6173.70 L 4639.70 6190.80 L 4637.60 6208.40 L 4635.10 6225.70 L 4632.00 6243.30 L 4628.50 6261.40 L 4624.30 6279.80 L 4619.60 6298.70 L 4613.30 6321.90 L 4606.20 6345.50 L 4598.30 6369.70 L 4589.70 6394.50 L 4580.70 6418.80 L 4571.00 6443.60 L 4560.70 6468.70 L 4549.60 6494.30 L 4526.40 6544.90 L 4501.00 6596.70 L 4483.30 6631.00 L 4464.80 6665.60 L 4445.60 6700.40 L 4425.90 6734.90 L 4405.70 6769.40 L 4385.00 6803.80 L 4363.90 6838.10 L 4320.90 6905.70 L 4298.90 6939.20 L 4254.90 7004.60 L 4232.70 7036.90 L 4188.80 7099.00 L 4156.00 7144.50 L 4124.20 7187.70 L 4092.70 7229.80 L 4033.50 7307.10 L 3979.90 7375.10 L 3933.70 7432.30 L 3882.60 7494.00 L 3855.00 7526.40 L 3621.30 7375.10 L 3647.70 7337.20 L 3678.00 7292.30 L 3717.30 7232.50 L 3739.30 7198.20 L 3763.90 7159.30 L 3788.90 7118.90 L 3815.80 7074.60 L 3843.00 7028.90 L 3871.40 6979.90 L 3909.30 6912.50 L 3928.80 6877.00 L 3967.00 6805.00 L 3986.20 6767.60 L 4004.90 6730.10 L 4023.50 6692.10 L 4041.80 6653.40 L 4059.60 6614.60 L 4076.90 6575.50 L 4093.80 6536.10 L 4106.10 6506.40 L 4117.90 6476.70 L 4140.40 6417.40 L 4151.10 6387.50 L 4161.30 6357.80 L 4170.90 6328.30 L 4179.90 6299.00 L 4188.50 6269.40 L 4196.50 6240.10 L 4203.80 6211.10 L 4210.40 6182.50 L 4208.70 6166.00 L 4206.40 6148.90 L 4203.50 6131.30 L 4200.00 6113.20 L 4195.80 6094.50 L 4191.30 6076.40 L 4186.20 6057.80 L 4180.50 6038.80 L 4174.30 6019.30 L 4167.50 5999.40 L 4158.60 5975.00 L 4148.90 5950.00 L 4138.50 5924.50 L 4127.20 5898.30 L 4115.50 5872.70 L 4103.20 5846.50 L 4090.20 5819.90 L 4076.50 5792.80 L 4047.90 5739.20 L 4017.00 5684.20 L 3995.70 5647.80 L 3973.50 5611.00 L 3950.60 5573.90 L 3927.20 5537.10 L 3903.20 5500.10 L 3878.70 5463.20 L 3853.80 5426.40 L 3803.00 5353.40 L 3777.20 5317.20 L 3725.30 5246.10 L 3699.00 5210.90 L 3647.10 5142.50 L 3608.10 5092.20 L 3570.10 5043.90 L 3532.30 4996.60 L 3496.00 4951.90 L 3460.10 4908.20 L 3393.30 4828.40 L 3362.90 4792.60 L 3307.30 4728.20 L 3261.20 4675.70 L 3226.20 4636.50 L 3196.20 4603.50 L 3898.40 4603.50 Z" fill="#429cfd" fill-rule="nonzero" node-id="522" stroke="none" target-height="2922.9" target-width="1446.2" target-x="3196.2" target-y="4603.5"></path><g node-id="606"><path d="M 3898.40 4603.50 L 3926.10 4640.80 L 3977.30 4712.00 L 4023.50 4778.00 L 4049.80 4816.50 L 4106.00 4900.40 L 4135.60 4945.70 L 4166.70 4994.30 L 4198.00 5044.10 L 4230.30 5096.60 L 4273.20 5168.40 L 4316.30 5243.10 L 4337.70 5281.10 L 4358.90 5319.80 L 4379.80 5358.70 L 4400.30 5397.90 L 4420.50 5437.40 L 4440.20 5477.20 L 4459.40 5517.00 L 4478.00 5556.90 L 4491.50 5587.00 L 4504.70 5617.00 L 4529.50 5676.60 L 4541.40 5706.60 L 4552.70 5736.30 L 4563.40 5765.70 L 4573.50 5794.80 L 4583.20 5824.30 L 4592.20 5853.40 L 4600.40 5881.90 L 4608.00 5910.10 L 4613.80 5933.00 L 4619.00 5955.50 L 4627.80 5999.30 L 4631.50 6020.70 L 4634.80 6042.50 L 4637.50 6063.80 L 4639.60 6084.70 L 4641.10 6105.10 L 4642.10 6125.00 L 4642.40 6140.80 L 4642.10 6157.00 L 4641.20 6173.70 L 4639.70 6190.80 L 4637.60 6208.40 L 4635.10 6225.70 L 4632.00 6243.30 L 4628.50 6261.40 L 4624.30 6279.80 L 4619.60 6298.70 L 4613.30 6321.90 L 4606.20 6345.50 L 4598.30 6369.70 L 4589.70 6394.50 L 4580.70 6418.80 L 4571.00 6443.60 L 4560.70 6468.70 L 4549.60 6494.30 L 4526.40 6544.90 L 4501.00 6596.70 L 4483.30 6631.00 L 4464.80 6665.60 L 4445.60 6700.40 L 4425.90 6734.90 L 4405.70 6769.40 L 4385.00 6803.80 L 4363.90 6838.10 L 4320.90 6905.70 L 4298.90 6939.20 L 4254.90 7004.60 L 4232.70 7036.90 L 4188.80 7099.00 L 4156.00 7144.50 L 4124.20 7187.70 L 4092.70 7229.80 L 4033.50 7307.10 L 3979.90 7375.10 L 3933.70 7432.30 L 3882.60 7494.00 L 3855.00 7526.40 L 3621.30 7375.10 L 3647.70 7337.20 L 3678.00 7292.30 L 3717.30 7232.50 L 3739.30 7198.20 L 3763.90 7159.30 L 3788.90 7118.90 L 3815.80 7074.60 L 3843.00 7028.90 L 3871.40 6979.90 L 3909.30 6912.50 L 3928.80 6877.00 L 3967.00 6805.00 L 3986.20 6767.60 L 4004.90 6730.10 L 4023.50 6692.10 L 4041.80 6653.40 L 4059.60 6614.60 L 4076.90 6575.50 L 4093.80 6536.10 L 4106.10 6506.40 L 4117.90 6476.70 L 4140.40 6417.40 L 4151.10 6387.50 L 4161.30 6357.80 L 4170.90 6328.30 L 4179.90 6299.00 L 4188.50 6269.40 L 4196.50 6240.10 L 4203.80 6211.10 L 4210.40 6182.50 L 4208.70 6166.00 L 4206.40 6148.90 L 4203.50 6131.30 L 4200.00 6113.20 L 4195.80 6094.50 L 4191.30 6076.40 L 4186.20 6057.80 L 4180.50 6038.80 L 4174.30 6019.30 L 4167.50 5999.40 L 4158.60 5975.00 L 4148.90 5950.00 L 4138.50 5924.50 L 4127.20 5898.30 L 4115.50 5872.70 L 4103.20 5846.50 L 4090.20 5819.90 L 4076.50 5792.80 L 4047.90 5739.20 L 4017.00 5684.20 L 3995.70 5647.80 L 3973.50 5611.00 L 3950.60 5573.90 L 3927.20 5537.10 L 3903.20 5500.10 L 3878.70 5463.20 L 3853.80 5426.40 L 3803.00 5353.40 L 3777.20 5317.20 L 3725.30 5246.10 L 3699.00 5210.90 L 3647.10 5142.50 L 3608.10 5092.20 L 3570.10 5043.90 L 3532.30 4996.60 L 3496.00 4951.90 L 3460.10 4908.20 L 3393.30 4828.40 L 3362.90 4792.60 L 3307.30 4728.20 L 3261.20 4675.70 L 3226.20 4636.50 L 3196.20 4603.50 L 3898.40 4603.50 Z" fill="#000000" fill-opacity="0.6" fill-rule="nonzero" group-id="19" node-id="526" stroke="none" target-height="2922.9" target-width="1446.2" target-x="3196.2" target-y="4603.5"></path></g><path d="M 3003.70 3468.40 L 2999.70 3469.60 L 2995.60 3471.30 L 2991.40 3473.40 L 2987.10 3476.20 L 2982.80 3479.50 L 2977.80 3483.80 L 2972.70 3488.80 L 2967.60 3494.60 L 2962.30 3501.20 L 2955.80 3510.10 L 2949.20 3520.00 L 2942.60 3531.00 L 2930.20 3553.90 L 2915.30 3585.50 L 2906.90 3605.10 L 2891.60 3644.60 L 2884.60 3664.10 L 2872.40 3700.20 L 2859.20 3743.10 L 2852.30 3767.70 L 3154.50 3969.50 L 3163.00 3949.50 L 3177.30 3913.50 L 3188.70 3881.90 L 3194.60 3864.30 L 3205.90 3827.40 L 3211.10 3808.30 L 3218.60 3775.90 L 3223.20 3750.30 L 3224.90 3737.60 L 3227.00 3713.30 L 3227.30 3701.30 L 3227.10 3690.00 L 3226.30 3679.30 L 3225.20 3671.20 L 3223.80 3663.70 L 3222.00 3656.60 L 3219.80 3649.90 L 3214.30 3635.40 L 3208.20 3621.10 L 3201.50 3606.70 L 3194.20 3592.60 L 3186.30 3578.90 L 3178.00 3565.50 L 3164.50 3546.30 L 3157.40 3537.30 L 3150.00 3528.60 L 3142.30 3520.20 L 3134.40 3512.30 L 3126.30 3505.00 L 3118.00 3498.20 L 3109.40 3491.80 L 3100.60 3486.00 L 3091.70 3480.90 L 3082.60 3476.50 L 3075.10 3473.40 L 3067.60 3470.80 L 3059.90 3468.60 L 3052.20 3467.00 L 3044.30 3465.80 L 3036.50 3465.20 L 3028.50 3465.10 L 3020.40 3465.60 L 3012.10 3466.70 L 3003.70 3468.40 Z" fill="#429cfd" fill-rule="nonzero" node-id="529" stroke="none" target-height="504.3999" target-width="375" target-x="2852.3" target-y="3465.1"></path><g node-id="607"><path d="M 3062.90 3649.40 L 3041.90 3894.40 L 3154.20 3969.70 L 3163.30 3948.70 L 3171.90 3927.40 L 3180.00 3906.00 L 3187.60 3884.40 L 3194.70 3862.70 L 3201.30 3840.80 L 3207.40 3818.70 L 3213.00 3796.50 L 3218.10 3774.20 L 3222.70 3751.70 L 3062.90 3649.40 Z" fill="#000000" fill-opacity="0.2" fill-rule="nonzero" group-id="20" node-id="533" stroke="none" target-height="320.30005" target-width="180.80005" target-x="3041.9" target-y="3649.4"></path></g><path d="M 3987.90 3473.90 L 3990.30 3476.00 L 3992.30 3478.80 L 3994.20 3482.40 L 3996.60 3488.30 L 3998.90 3495.40 L 4002.80 3513.00 L 4005.70 3533.00 L 4006.90 3544.00 L 4008.90 3572.60 L 4009.60 3588.10 L 4010.10 3626.70 L 4010.00 3647.50 L 4008.60 3697.70 L 4005.80 3754.30 L 4001.10 3821.20 L 3994.30 3899.10 L 3985.40 3984.90 L 3974.00 4083.30 L 3967.30 4136.80 L 3951.70 4253.10 L 3932.70 4383.90 L 3911.00 4524.90 L 3898.40 4603.30 L 3138.50 4603.30 L 3141.40 4527.90 L 3143.20 4455.60 L 3143.70 4419.90 L 3143.70 4384.30 L 3143.20 4348.50 L 3141.10 4287.80 L 3138.20 4237.10 L 3134.00 4184.90 L 3131.40 4157.30 L 3125.10 4100.90 L 3117.10 4040.60 L 3107.20 3975.50 L 3098.80 3924.70 L 3089.00 3869.60 L 3078.30 3812.80 L 3065.90 3751.00 L 3052.70 3687.40 L 3037.70 3618.00 L 3021.70 3546.40 L 3003.70 3468.40 L 3035.70 3462.00 L 3067.70 3456.10 L 3099.90 3450.70 L 3132.00 3445.80 L 3164.30 3441.30 L 3196.60 3437.30 L 3229.00 3433.70 L 3261.40 3430.70 L 3293.90 3428.00 L 3360.90 3423.70 L 3395.40 3422.00 L 3429.90 3420.70 L 3464.50 3420.00 L 3499.00 3419.70 L 3533.60 3420.00 L 3568.10 3420.70 L 3602.60 3422.00 L 3637.10 3423.70 L 3671.60 3425.90 L 3699.50 3428.70 L 3756.00 3435.40 L 3783.60 3439.10 L 3836.90 3447.10 L 3885.40 3455.00 L 3926.90 3462.30 L 3987.90 3473.90 Z" fill="#429cfd" fill-rule="nonzero" node-id="536" stroke="none" target-height="1183.5999" target-width="1006.40015" target-x="3003.7" target-y="3419.7"></path><path d="M 4065.20 3649.40 L 4100.80 3748.00 L 4133.70 3835.00 L 4161.80 3910.40 L 4176.10 3948.00 L 4234.50 4097.80 L 4256.90 4153.90 L 4260.50 4163.20 L 4260.50 4164.60 L 4258.20 4161.60 L 4253.00 4156.30 L 4247.00 4151.70 L 4243.80 4149.80 L 4234.00 4145.10 L 4228.90 4143.30 L 4223.60 4141.90 L 4218.20 4141.00 L 4212.80 4140.50 L 4208.20 4140.50 L 4203.70 4140.90 L 4194.90 4142.60 L 4190.60 4143.90 L 4186.30 4145.50 L 4181.40 4148.00 L 4178.60 4149.60 L 4176.60 4151.10 L 4175.80 4151.90 L 4175.50 4152.40 L 4178.40 4149.90 L 4180.80 4147.00 L 4188.60 4136.40 L 4195.80 4125.60 L 4202.60 4114.40 L 4209.00 4103.10 L 4223.20 4074.80 L 4231.50 4057.50 L 4247.60 4022.50 L 4268.10 3974.30 L 4292.70 3912.90 L 4307.30 3875.50 L 4335.60 3800.40 L 4417.20 3572.40 L 4509.00 3596.20 L 4502.80 3632.40 L 4496.20 3668.40 L 4481.80 3739.90 L 4465.90 3811.30 L 4448.60 3882.70 L 4432.40 3945.50 L 4417.20 3999.00 L 4400.60 4052.40 L 4384.70 4099.40 L 4377.30 4119.60 L 4361.20 4160.30 L 4355.00 4175.10 L 4348.30 4189.70 L 4341.20 4204.00 L 4333.70 4218.20 L 4325.70 4232.10 L 4317.30 4245.70 L 4310.70 4254.60 L 4303.70 4263.20 L 4296.30 4271.50 L 4290.00 4277.80 L 4283.30 4283.70 L 4276.20 4289.10 L 4268.70 4294.00 L 4261.00 4298.40 L 4252.90 4302.20 L 4245.50 4305.10 L 4237.90 4307.40 L 4230.20 4309.10 L 4222.40 4310.30 L 4214.50 4310.80 L 4206.60 4310.80 L 4199.90 4310.40 L 4193.40 4309.50 L 4186.90 4308.20 L 4180.50 4306.50 L 4174.20 4304.40 L 4168.10 4301.90 L 4162.10 4299.10 L 4156.30 4295.80 L 4149.60 4291.40 L 4137.40 4281.50 L 4126.50 4270.10 L 4121.60 4264.00 L 4117.00 4257.50 L 4112.90 4250.70 L 4107.90 4241.60 L 4099.50 4224.70 L 4072.30 4169.10 L 4054.50 4131.90 L 4025.50 4069.30 L 4003.90 4019.10 L 3956.30 3904.90 L 3930.60 3840.80 L 3911.40 3789.10 L 3902.10 3762.70 L 3884.40 3708.60 L 4065.20 3649.40 Z" fill="#e58a7b" fill-rule="nonzero" node-id="538" stroke="none" target-height="738.3999" target-width="624.6001" target-x="3884.4" target-y="3572.4"></path><path d="M 3987.90 3473.90 L 3992.70 3475.20 L 3997.60 3477.10 L 4007.70 3482.20 L 4013.00 3485.50 L 4019.10 3490.00 L 4031.70 3500.80 L 4038.10 3507.20 L 4046.30 3516.10 L 4054.50 3525.80 L 4062.90 3536.50 L 4078.70 3559.00 L 4086.60 3571.10 L 4097.90 3589.60 L 4108.80 3608.70 L 4119.30 3627.90 L 4129.00 3646.90 L 4138.30 3665.70 L 4154.60 3700.60 L 4172.50 3741.90 L 4182.00 3765.60 L 3910.40 3996.70 L 3897.50 3974.40 L 3875.30 3934.10 L 3856.70 3898.70 L 3846.80 3879.00 L 3826.90 3837.40 L 3817.20 3815.80 L 3807.70 3793.80 L 3798.80 3771.70 L 3790.70 3749.90 L 3781.10 3721.10 L 3777.00 3707.40 L 3773.40 3693.50 L 3770.40 3680.40 L 3768.10 3668.00 L 3766.80 3658.50 L 3765.90 3649.60 L 3765.40 3633.40 L 3765.80 3625.40 L 3766.80 3618.00 L 3768.20 3611.40 L 3770.10 3605.30 L 3774.90 3592.60 L 3780.00 3580.60 L 3785.30 3569.20 L 3790.80 3558.50 L 3796.80 3547.80 L 3803.10 3537.90 L 3809.70 3528.60 L 3816.50 3519.90 L 3822.40 3513.30 L 3828.50 3507.10 L 3834.80 3501.30 L 3841.30 3495.90 L 3848.10 3491.00 L 3855.20 3486.50 L 3862.60 3482.40 L 3870.20 3478.80 L 3878.30 3475.60 L 3886.60 3472.80 L 3895.00 3470.60 L 3903.80 3468.90 L 3913.00 3467.70 L 3922.70 3466.90 L 3932.90 3466.70 L 3942.70 3467.00 L 3953.10 3467.80 L 3964.10 3469.20 L 3975.70 3471.20 L 3987.90 3473.90 Z" fill="#429cfd" fill-rule="nonzero" node-id="540" stroke="none" target-height="530" target-width="416.6001" target-x="3765.4" target-y="3466.7"></path><path d="M 4460.40 3487.00 L 4555.80 3407.30 L 4622.10 3510.60 L 4573.20 3596.70 L 4565.90 3602.80 L 4553.30 3612.00 L 4545.00 3617.40 L 4532.70 3624.40 L 4518.60 3631.10 L 4511.10 3634.00 L 4503.30 3636.70 L 4495.30 3638.90 L 4487.10 3640.50 L 4478.60 3641.70 L 4472.20 3642.10 L 4465.80 3642.10 L 4459.30 3641.60 L 4452.80 3640.70 L 4446.30 3639.40 L 4439.90 3637.40 L 4433.30 3634.90 L 4426.80 3631.70 L 4423.40 3623.50 L 4420.70 3615.00 L 4418.60 3606.40 L 4417.20 3597.70 L 4416.30 3589.00 L 4416.10 3580.10 L 4416.50 3571.30 L 4417.60 3562.50 L 4419.30 3553.70 L 4421.60 3545.10 L 4424.60 3536.70 L 4428.10 3528.60 L 4432.20 3520.80 L 4436.80 3513.30 L 4441.90 3506.10 L 4447.60 3499.30 L 4453.70 3492.90 L 4460.40 3487.00 Z" fill="#e58a7b" fill-rule="nonzero" node-id="542" stroke="none" target-height="234.80005" target-width="206" target-x="4416.1" target-y="3407.3"></path><path d="M 4728.00 3407.50 L 4720.90 3433.50 L 4569.90 3458.80 L 4555.10 3407.30 L 4728.00 3407.50 Z" fill="#e58a7b" fill-rule="nonzero" node-id="544" stroke="none" target-height="51.5" target-width="172.8999" target-x="4555.1" target-y="3407.3"></path><path d="M 3873.60 7538.50 L 3601.20 7362.50 L 3650.10 7277.60 L 3956.10 7465.80 L 3873.60 7538.50 Z" fill="#429cfd" fill-rule="nonzero" node-id="546" stroke="none" target-height="260.8999" target-width="354.90015" target-x="3601.2" target-y="7277.6"></path><path d="M 3410.90 7678.80 L 3401.90 7677.40 L 3393.10 7675.60 L 3375.90 7670.50 L 3367.60 7667.30 L 3359.40 7663.60 L 3351.40 7659.50 L 3342.40 7654.30 L 3341.50 7653.40 L 3340.90 7652.20 L 3340.90 7649.60 L 3341.50 7648.40 L 3342.40 7647.40 L 3343.60 7646.80 L 3350.00 7644.90 L 3361.50 7641.70 L 3376.30 7638.30 L 3384.50 7636.70 L 3392.80 7635.50 L 3401.20 7634.60 L 3409.10 7634.20 L 3414.30 7634.20 L 3419.00 7634.60 L 3423.30 7635.30 L 3427.50 7636.50 L 3430.90 7638.00 L 3433.80 7639.90 L 3435.70 7641.80 L 3438.40 7646.50 L 3439.10 7649.10 L 3439.00 7654.40 L 3438.60 7657.70 L 3437.70 7660.70 L 3436.50 7663.70 L 3434.90 7666.50 L 3432.90 7669.00 L 3430.70 7671.30 L 3428.20 7673.30 L 3422.00 7676.60 L 3418.40 7677.80 L 3414.70 7678.50 L 3410.90 7678.80 Z M 3357.20 7652.50 L 3367.30 7657.40 L 3376.50 7661.40 L 3384.80 7664.50 L 3397.10 7668.00 L 3407.00 7669.60 L 3411.90 7669.80 L 3416.00 7669.40 L 3419.40 7668.70 L 3422.30 7667.60 L 3424.70 7666.00 L 3426.70 7664.10 L 3428.40 7661.80 L 3429.70 7659.20 L 3430.50 7656.60 L 3430.90 7653.70 L 3430.90 7651.10 L 3430.70 7649.40 L 3430.30 7648.50 L 3426.60 7645.90 L 3423.70 7644.80 L 3419.90 7644.00 L 3416.10 7643.60 L 3411.70 7643.50 L 3399.00 7644.20 L 3390.50 7645.30 L 3382.00 7646.70 L 3364.80 7650.50 L 3357.20 7652.50 Z" fill="#263238" fill-rule="nonzero" node-id="548" stroke="none" target-height="44.59961" target-width="98.200195" target-x="3340.9" target-y="7634.2"></path><path d="M 3345.30 7656.60 L 3342.00 7655.20 L 3341.50 7654.10 L 3341.40 7653.00 L 3342.00 7650.90 L 3342.90 7647.20 L 3345.60 7639.00 L 3350.00 7627.80 L 3355.90 7615.30 L 3359.40 7609.00 L 3366.00 7599.20 L 3371.90 7592.50 L 3375.10 7589.60 L 3381.80 7585.30 L 3385.00 7584.10 L 3391.70 7583.10 L 3395.10 7583.40 L 3401.60 7585.30 L 3404.00 7586.30 L 3406.20 7587.60 L 3408.20 7589.30 L 3411.10 7593.60 L 3412.50 7598.60 L 3411.90 7604.80 L 3410.60 7608.50 L 3408.50 7612.50 L 3403.00 7620.20 L 3399.30 7624.20 L 3393.40 7629.90 L 3386.80 7635.50 L 3379.80 7640.60 L 3372.70 7645.30 L 3365.50 7649.50 L 3354.40 7654.90 L 3347.20 7657.30 L 3345.30 7656.60 Z M 3391.60 7591.50 L 3389.20 7591.70 L 3387.00 7592.30 L 3382.00 7594.80 L 3376.40 7599.70 L 3372.40 7604.30 L 3368.60 7609.70 L 3361.90 7621.60 L 3356.30 7633.90 L 3352.00 7645.10 L 3358.30 7642.10 L 3365.20 7638.30 L 3372.00 7634.00 L 3378.60 7629.20 L 3385.00 7624.00 L 3390.50 7618.90 L 3394.00 7615.30 L 3396.80 7611.90 L 3401.10 7605.30 L 3402.30 7602.40 L 3402.80 7599.80 L 3402.60 7597.50 L 3401.10 7595.10 L 3399.70 7594.00 L 3397.30 7592.70 L 3394.50 7591.50 L 3391.60 7590.80 L 3391.60 7591.50 Z" fill="#263238" fill-rule="nonzero" node-id="550" stroke="none" target-height="74.19971" target-width="71.1001" target-x="3341.4" target-y="7583.1"></path><path d="M 3751.20 7781.80 L 3746.80 7781.50 L 3742.00 7780.60 L 3737.00 7779.10 L 3727.10 7774.90 L 3714.10 7767.30 L 3706.40 7761.90 L 3698.80 7756.00 L 3684.40 7743.60 L 3677.90 7737.50 L 3666.30 7726.00 L 3665.70 7724.80 L 3665.50 7723.50 L 3666.30 7721.00 L 3668.30 7719.20 L 3669.50 7718.70 L 3670.90 7718.60 L 3689.50 7720.90 L 3704.90 7723.80 L 3722.10 7727.70 L 3730.80 7730.10 L 3747.60 7735.80 L 3755.00 7738.90 L 3759.80 7741.30 L 3767.40 7746.10 L 3770.70 7748.90 L 3773.10 7751.60 L 3775.90 7756.60 L 3776.80 7761.30 L 3775.90 7766.00 L 3773.40 7770.90 L 3771.60 7773.30 L 3769.50 7775.40 L 3767.20 7777.30 L 3764.60 7778.90 L 3761.90 7780.20 L 3759.00 7781.00 L 3751.20 7781.80 Z M 3682.10 7729.30 L 3692.30 7738.90 L 3701.90 7747.10 L 3710.60 7754.00 L 3717.70 7758.90 L 3724.00 7763.00 L 3729.80 7766.30 L 3735.10 7768.80 L 3739.80 7770.70 L 3743.90 7771.90 L 3747.50 7772.60 L 3753.60 7772.80 L 3756.20 7772.20 L 3758.70 7771.20 L 3763.00 7768.20 L 3764.70 7766.20 L 3766.00 7763.90 L 3766.60 7762.60 L 3766.90 7761.20 L 3766.00 7758.40 L 3763.30 7754.90 L 3761.10 7753.00 L 3758.20 7751.00 L 3749.30 7746.50 L 3743.50 7744.10 L 3734.50 7741.10 L 3724.40 7738.10 L 3703.10 7733.10 L 3682.10 7729.30 Z" fill="#263238" fill-rule="nonzero" node-id="552" stroke="none" target-height="63.199707" target-width="111.30005" target-x="3665.5" target-y="7718.6"></path><path d="M 3684.70 7728.20 L 3674.00 7727.70 L 3667.70 7726.70 L 3665.00 7723.20 L 3664.80 7722.10 L 3665.00 7720.90 L 3665.40 7719.80 L 3675.90 7708.40 L 3680.70 7703.80 L 3692.80 7693.50 L 3699.70 7688.60 L 3704.40 7685.60 L 3709.30 7682.90 L 3714.30 7680.60 L 3719.40 7678.70 L 3724.50 7677.40 L 3729.60 7676.60 L 3738.10 7677.10 L 3742.20 7678.10 L 3746.20 7679.70 L 3749.90 7681.70 L 3753.40 7684.30 L 3756.30 7687.10 L 3759.70 7692.00 L 3760.70 7694.50 L 3761.20 7696.70 L 3760.80 7701.60 L 3758.70 7706.30 L 3757.20 7708.50 L 3752.40 7712.80 L 3744.10 7717.40 L 3738.90 7719.50 L 3733.50 7721.30 L 3727.70 7722.90 L 3712.10 7726.00 L 3702.70 7727.20 L 3693.30 7727.90 L 3684.70 7728.20 Z M 3678.50 7719.30 L 3685.40 7719.50 L 3700.70 7718.60 L 3716.40 7716.30 L 3731.10 7712.80 L 3737.50 7710.70 L 3741.50 7709.10 L 3745.00 7707.40 L 3747.80 7705.80 L 3752.20 7702.00 L 3753.70 7699.10 L 3753.60 7697.80 L 3752.30 7694.60 L 3749.10 7691.20 L 3746.50 7689.20 L 3743.70 7687.60 L 3740.60 7686.40 L 3734.20 7685.30 L 3731.00 7685.50 L 3723.30 7687.90 L 3716.00 7690.90 L 3708.90 7694.40 L 3702.10 7698.50 L 3695.60 7703.10 L 3689.60 7708.10 L 3678.50 7719.30 Z" fill="#263238" fill-rule="nonzero" node-id="554" stroke="none" target-height="51.600098" target-width="96.3999" target-x="3664.8" target-y="7676.6"></path><path d="M 3351.70 7630.80 L 3149.50 7573.60 L 3146.80 7573.00 L 3144.10 7573.00 L 3141.50 7573.40 L 3138.90 7574.30 L 3134.50 7577.50 L 3132.90 7579.50 L 3131.60 7581.90 L 3063.40 7735.30 L 3062.00 7739.30 L 3061.30 7743.50 L 3061.10 7747.70 L 3061.60 7751.90 L 3062.60 7756.00 L 3064.20 7759.90 L 3066.30 7763.60 L 3068.90 7766.90 L 3072.00 7769.80 L 3075.40 7772.30 L 3079.20 7774.20 L 3083.20 7775.60 L 3111.80 7782.90 L 3213.00 7807.30 L 3243.00 7815.20 L 3278.00 7825.00 L 3313.50 7835.60 L 3441.30 7876.60 L 3486.70 7890.10 L 3494.00 7891.90 L 3500.70 7893.10 L 3506.90 7893.80 L 3512.70 7894.00 L 3518.70 7893.70 L 3524.20 7893.10 L 3529.20 7892.10 L 3533.90 7890.80 L 3542.90 7887.00 L 3550.30 7882.20 L 3558.60 7874.30 L 3561.90 7870.10 L 3564.70 7865.50 L 3566.80 7860.90 L 3568.50 7856.20 L 3569.60 7851.40 L 3570.10 7846.80 L 3570.10 7842.40 L 3569.50 7838.00 L 3568.40 7834.10 L 3565.10 7827.90 L 3560.90 7823.80 L 3558.30 7822.10 L 3527.10 7804.70 L 3514.00 7796.80 L 3501.10 7788.40 L 3489.60 7780.50 L 3479.40 7773.10 L 3469.50 7765.30 L 3452.80 7750.80 L 3438.50 7736.40 L 3432.50 7729.60 L 3421.40 7715.90 L 3409.80 7699.30 L 3385.50 7660.20 L 3378.90 7650.40 L 3376.30 7646.90 L 3370.40 7640.90 L 3363.50 7635.90 L 3359.70 7633.90 L 3355.80 7632.20 L 3351.70 7630.80 Z" fill="#429cfd" fill-rule="nonzero" node-id="556" stroke="none" target-height="321" target-width="509" target-x="3061.1" target-y="7573"></path><path d="M 3671.80 7698.10 L 3499.10 7578.80 L 3496.80 7577.40 L 3494.30 7576.50 L 3491.70 7576.10 L 3489.00 7576.10 L 3483.90 7577.50 L 3479.60 7580.70 L 3365.10 7703.80 L 3362.50 7707.20 L 3360.50 7710.80 L 3359.00 7714.80 L 3358.00 7718.90 L 3357.70 7723.10 L 3357.90 7727.30 L 3358.80 7731.40 L 3360.20 7735.40 L 3362.10 7739.10 L 3364.60 7742.60 L 3367.50 7745.60 L 3370.80 7748.20 L 3395.30 7764.30 L 3483.20 7820.40 L 3509.10 7837.60 L 3549.80 7865.40 L 3589.30 7894.10 L 3683.30 7963.90 L 3714.50 7986.80 L 3743.60 8007.50 L 3749.90 8011.50 L 3755.80 8014.80 L 3761.50 8017.50 L 3766.90 8019.60 L 3772.60 8021.30 L 3778.00 8022.50 L 3783.10 8023.10 L 3788.00 8023.40 L 3793.00 8023.20 L 3797.60 8022.70 L 3806.30 8020.60 L 3811.70 8018.40 L 3816.60 8015.90 L 3821.10 8012.90 L 3825.20 8009.40 L 3828.80 8005.80 L 3834.40 7997.80 L 3836.40 7993.60 L 3837.80 7989.40 L 3838.70 7985.10 L 3838.90 7981.00 L 3837.70 7974.10 L 3835.00 7968.90 L 3833.10 7966.40 L 3816.20 7948.00 L 3801.30 7930.90 L 3788.10 7914.90 L 3775.40 7898.60 L 3764.20 7883.40 L 3754.50 7869.30 L 3745.10 7854.80 L 3737.00 7841.20 L 3730.00 7828.50 L 3723.40 7815.50 L 3712.70 7791.70 L 3704.00 7768.60 L 3695.50 7741.60 L 3689.80 7721.80 L 3688.20 7717.80 L 3686.30 7714.00 L 3681.30 7706.90 L 3678.50 7703.70 L 3675.30 7700.80 L 3671.80 7698.10 Z" fill="#429cfd" fill-rule="nonzero" node-id="558" stroke="none" target-height="447.2998" target-width="481.19995" target-x="3357.7" target-y="7576.1"></path><path d="M 3531.80 7476.30 L 3220.50 7321.50 L 3252.70 7237.00 L 3585.20 7400.20 L 3531.80 7476.30 Z" fill="#429cfd" fill-rule="nonzero" node-id="560" stroke="none" target-height="239.2998" target-width="364.69995" target-x="3220.5" target-y="7237"></path><path d="M 3127.00 4558.70 L 3100.20 4629.10 L 3100.90 4632.50 L 3103.40 4635.30 L 3107.70 4637.30 L 3110.30 4637.90 L 3113.20 4638.10 L 3902.20 4638.10 L 3905.60 4637.80 L 3908.60 4637.10 L 3911.30 4635.80 L 3913.30 4634.30 L 3914.30 4633.10 L 3915.00 4631.80 L 3915.40 4630.50 L 3922.00 4562.20 L 3921.80 4560.80 L 3921.30 4559.40 L 3919.10 4556.80 L 3915.80 4554.90 L 3911.60 4553.80 L 3137.10 4553.60 L 3132.60 4554.60 L 3130.50 4555.70 L 3128.70 4557.00 L 3127.00 4558.70 Z" fill="#429cfd" fill-rule="nonzero" node-id="562" stroke="none" target-height="84.5" target-width="821.80005" target-x="3100.2" target-y="4553.6"></path><g node-id="608"><path d="M 3127.00 4558.70 L 3100.20 4629.10 L 3100.90 4632.50 L 3103.40 4635.30 L 3107.70 4637.30 L 3110.30 4637.90 L 3113.20 4638.10 L 3902.20 4638.10 L 3905.60 4637.80 L 3908.60 4637.10 L 3911.30 4635.80 L 3913.30 4634.30 L 3914.30 4633.10 L 3915.00 4631.80 L 3915.40 4630.50 L 3922.00 4562.20 L 3921.80 4560.80 L 3921.30 4559.40 L 3919.10 4556.80 L 3915.80 4554.90 L 3911.60 4553.80 L 3137.10 4553.60 L 3132.60 4554.60 L 3130.50 4555.70 L 3128.70 4557.00 L 3127.00 4558.70 Z" fill="#ffffff" fill-opacity="0.4" fill-rule="nonzero" group-id="21" node-id="566" stroke="none" target-height="84.5" target-width="821.80005" target-x="3100.2" target-y="4553.6"></path></g><path d="M 3251.30 4644.80 L 3228.50 4644.60 L 3225.50 4643.40 L 3223.80 4641.20 L 3223.60 4640.00 L 3233.40 4551.50 L 3234.60 4549.30 L 3237.40 4547.60 L 3239.30 4547.10 L 3262.00 4547.00 L 3264.10 4547.20 L 3267.10 4548.30 L 3268.20 4549.30 L 3268.80 4550.40 L 3269.00 4551.50 L 3259.20 4640.00 L 3258.00 4642.30 L 3256.70 4643.40 L 3255.10 4644.10 L 3253.40 4644.60 L 3251.30 4644.80 Z" fill="#429cfd" fill-rule="nonzero" node-id="569" stroke="none" target-height="97.799805" target-width="45.399902" target-x="3223.6" target-y="4547"></path><path d="M 3737.90 4644.80 L 3715.30 4644.60 L 3711.20 4642.30 L 3710.60 4641.20 L 3710.50 4640.00 L 3720.00 4551.50 L 3720.30 4550.40 L 3721.20 4549.30 L 3722.50 4548.40 L 3724.20 4547.60 L 3726.10 4547.10 L 3750.60 4547.10 L 3753.80 4548.40 L 3755.50 4550.30 L 3746.00 4640.00 L 3745.30 4641.20 L 3744.30 4642.40 L 3743.10 4643.40 L 3741.70 4644.10 L 3740.00 4644.60 L 3737.90 4644.80 Z" fill="#429cfd" fill-rule="nonzero" node-id="571" stroke="none" target-height="97.69971" target-width="45" target-x="3710.5" target-y="4547.1"></path><g node-id="609"><path d="M 3233.20 8684.20 L 3348.00 8683.40 L 3456.10 8681.30 L 3507.70 8679.70 L 3555.70 8677.80 L 3602.40 8675.60 L 3644.90 8673.30 L 3685.80 8670.50 L 3721.90 8667.70 L 3756.20 8664.50 L 3784.90 8661.20 L 3819.10 8656.40 L 3832.20 8654.00 L 3845.20 8651.20 L 3854.90 8648.60 L 3861.90 8646.20 L 3866.30 8644.20 L 3869.20 8642.40 L 3871.00 8640.70 L 3871.90 8639.30 L 3872.20 8637.90 L 3871.90 8636.50 L 3871.00 8635.00 L 3869.20 8633.40 L 3866.30 8631.60 L 3861.90 8629.60 L 3854.90 8627.20 L 3845.20 8624.50 L 3832.20 8621.70 L 3803.50 8617.00 L 3784.90 8614.50 L 3756.20 8611.30 L 3721.90 8608.10 L 3685.80 8605.20 L 3644.90 8602.50 L 3602.40 8600.10 L 3507.70 8596.10 L 3403.50 8593.30 L 3291.70 8591.80 L 3174.70 8591.80 L 3062.80 8593.30 L 2958.70 8596.10 L 2910.70 8597.90 L 2864.00 8600.10 L 2821.50 8602.50 L 2780.50 8605.20 L 2744.50 8608.10 L 2710.20 8611.30 L 2681.40 8614.50 L 2662.90 8617.00 L 2647.20 8619.40 L 2634.20 8621.70 L 2621.10 8624.50 L 2611.50 8627.20 L 2604.50 8629.60 L 2600.00 8631.60 L 2597.10 8633.40 L 2595.40 8635.00 L 2594.50 8636.50 L 2594.20 8637.90 L 2594.50 8639.30 L 2597.10 8642.40 L 2600.00 8644.20 L 2604.50 8646.20 L 2611.50 8648.60 L 2621.10 8651.20 L 2634.20 8654.00 L 2647.20 8656.40 L 2662.90 8658.80 L 2681.40 8661.20 L 2710.20 8664.50 L 2744.50 8667.70 L 2780.50 8670.50 L 2821.50 8673.30 L 2864.00 8675.60 L 2910.70 8677.80 L 3010.20 8681.30 L 3062.80 8682.50 L 3118.30 8683.40 L 3233.20 8684.20 Z" fill="#f5f5f5" fill-rule="nonzero" group-id="22" node-id="575" stroke="none" target-height="92.40039" target-width="1278" target-x="2594.2" target-y="8591.8"></path></g><path d="M 3622.50 8572.50 L 3565.20 8555.30 L 3507.60 8536.80 L 3479.00 8527.00 L 3451.30 8517.10 L 3424.00 8506.80 L 3398.20 8496.50 L 3365.10 8482.20 L 3334.80 8467.70 L 3301.40 8449.90 L 3301.40 7938.10 L 3530.10 7938.10 L 3535.90 7937.50 L 3541.50 7936.40 L 3547.00 7934.90 L 3552.40 7932.90 L 3562.50 7927.70 L 3571.60 7920.80 L 3579.50 7912.40 L 3582.90 7907.70 L 3585.90 7902.80 L 3590.50 7892.50 L 3592.20 7887.00 L 3593.40 7881.40 L 3594.10 7875.70 L 3594.30 7870.00 L 3594.10 7864.20 L 3593.40 7858.50 L 3592.20 7852.90 L 3590.50 7847.40 L 3585.90 7837.10 L 3582.90 7832.20 L 3579.50 7827.50 L 3571.60 7819.10 L 3562.50 7812.20 L 3557.50 7809.40 L 3552.40 7807.00 L 3547.00 7805.00 L 3541.50 7803.50 L 3535.90 7802.40 L 3530.10 7801.90 L 3302.60 7801.90 L 3302.60 5468.60 L 3165.20 5468.60 L 3165.20 7801.90 L 2933.20 7801.70 L 2928.60 7801.90 L 2924.00 7802.40 L 2919.50 7803.30 L 2915.00 7804.40 L 2906.30 7807.60 L 2898.10 7811.90 L 2890.60 7817.20 L 2887.10 7820.30 L 2883.80 7823.60 L 2880.80 7827.10 L 2875.50 7834.70 L 2871.20 7842.90 L 2868.10 7851.60 L 2866.30 7860.70 L 2865.80 7865.30 L 2865.80 7874.60 L 2866.30 7879.20 L 2867.10 7883.80 L 2868.10 7888.20 L 2871.20 7897.00 L 2873.20 7901.20 L 2878.00 7909.10 L 2883.80 7916.30 L 2887.10 7919.60 L 2890.60 7922.70 L 2898.10 7928.00 L 2902.10 7930.30 L 2906.30 7932.30 L 2910.60 7934.10 L 2919.50 7936.60 L 2924.00 7937.50 L 2928.60 7938.00 L 2933.20 7938.20 L 3165.20 7938.10 L 3165.20 8449.20 L 3162.00 8450.30 L 3158.80 8452.50 L 3145.80 8459.80 L 3131.70 8467.20 L 3116.60 8474.70 L 3101.30 8481.90 L 3068.10 8496.20 L 3042.20 8506.60 L 3015.00 8516.90 L 2987.30 8526.80 L 2958.80 8536.50 L 2901.40 8554.90 L 2844.20 8571.80 L 2840.40 8573.20 L 2836.90 8575.00 L 2833.60 8577.10 L 2830.70 8579.70 L 2828.00 8582.60 L 2825.70 8585.80 L 2823.70 8589.30 L 2822.20 8592.90 L 2821.20 8596.70 L 2820.40 8604.50 L 2820.80 8608.50 L 2821.60 8612.40 L 2822.80 8616.10 L 2824.40 8619.70 L 2826.50 8623.00 L 2829.00 8626.10 L 2831.80 8628.90 L 2834.90 8631.30 L 2841.90 8635.00 L 2845.60 8636.10 L 2849.50 8636.90 L 2853.50 8637.20 L 3613.10 8637.20 L 3616.90 8636.70 L 3624.10 8634.50 L 3630.60 8630.80 L 3633.60 8628.30 L 3636.20 8625.60 L 3640.50 8619.30 L 3642.00 8615.90 L 3643.20 8612.30 L 3643.90 8608.60 L 3644.30 8604.80 L 3643.60 8597.30 L 3641.30 8590.20 L 3639.60 8586.80 L 3637.40 8583.60 L 3632.20 8578.20 L 3626.00 8574.00 L 3622.50 8572.50 Z" fill="#e0e0e0" fill-rule="nonzero" node-id="578" stroke="none" target-height="3168.6" target-width="823.90015" target-x="2820.4" target-y="5468.6"></path><path d="M 3657.50 5231.10 L 2809.30 5231.10 L 2800.80 5231.30 L 2792.50 5232.20 L 2784.40 5233.60 L 2776.50 5235.50 L 2768.70 5237.90 L 2761.20 5240.80 L 2754.00 5244.10 L 2747.00 5247.90 L 2740.20 5252.20 L 2733.80 5256.80 L 2727.70 5261.80 L 2722.00 5267.20 L 2716.50 5273.00 L 2711.50 5279.10 L 2706.90 5285.50 L 2702.60 5292.20 L 2698.80 5299.20 L 2695.50 5306.50 L 2692.60 5314.00 L 2690.20 5321.80 L 2688.30 5329.70 L 2686.90 5337.70 L 2686.10 5346.10 L 2685.80 5354.60 L 2685.80 5606.70 L 2686.10 5615.30 L 2686.90 5623.60 L 2688.30 5631.70 L 2690.20 5639.60 L 2692.60 5647.40 L 2695.50 5654.90 L 2698.80 5662.10 L 2702.60 5669.10 L 2706.90 5675.80 L 2711.50 5682.20 L 2716.50 5688.30 L 2722.00 5694.10 L 2727.70 5699.50 L 2733.80 5704.50 L 2740.20 5709.20 L 2747.00 5713.40 L 2754.00 5717.20 L 2761.20 5720.60 L 2768.70 5723.40 L 2776.50 5725.90 L 2784.40 5727.80 L 2792.50 5729.10 L 2800.80 5730.00 L 2809.30 5730.30 L 3657.50 5730.30 L 3666.10 5730.00 L 3674.40 5729.10 L 3682.50 5727.80 L 3690.40 5725.90 L 3698.10 5723.40 L 3705.60 5720.60 L 3719.90 5713.40 L 3726.60 5709.20 L 3739.10 5699.50 L 3744.90 5694.10 L 3750.30 5688.30 L 3755.30 5682.20 L 3759.90 5675.80 L 3764.20 5669.10 L 3768.00 5662.10 L 3771.30 5654.90 L 3774.20 5647.40 L 3776.70 5639.60 L 3778.60 5631.70 L 3779.90 5623.60 L 3780.80 5615.30 L 3781.10 5606.70 L 3781.10 5354.60 L 3780.80 5346.10 L 3779.90 5337.70 L 3778.60 5329.70 L 3776.70 5321.80 L 3774.20 5314.00 L 3771.30 5306.50 L 3768.00 5299.20 L 3764.20 5292.20 L 3759.90 5285.50 L 3755.30 5279.10 L 3750.30 5273.00 L 3744.90 5267.20 L 3739.10 5261.80 L 3733.00 5256.80 L 3726.60 5252.20 L 3719.90 5247.90 L 3712.90 5244.10 L 3705.60 5240.80 L 3698.10 5237.90 L 3690.40 5235.50 L 3682.50 5233.60 L 3674.40 5232.20 L 3666.10 5231.30 L 3657.50 5231.10 Z" fill="#429cfd" fill-rule="nonzero" node-id="580" stroke="none" target-height="499.1997" target-width="1095.3" target-x="2685.8" target-y="5231.1"></path><path d="M 2801.50 5730.50 L 2801.50 5773.90 L 2801.80 5779.30 L 2802.60 5784.50 L 2804.00 5789.50 L 2805.90 5794.40 L 2808.30 5799.10 L 2811.10 5803.70 L 2814.40 5808.00 L 2822.30 5816.10 L 2831.60 5823.10 L 2836.90 5826.30 L 2842.40 5829.00 L 2848.20 5831.50 L 2854.30 5833.70 L 2860.70 5835.50 L 2867.20 5836.90 L 2873.90 5837.90 L 2880.80 5838.60 L 2888.00 5838.80 L 3579.80 5838.80 L 3593.90 5837.90 L 3600.60 5836.90 L 3613.50 5833.70 L 3619.60 5831.50 L 3630.80 5826.30 L 3636.20 5823.10 L 3645.50 5816.10 L 3653.40 5808.00 L 3656.70 5803.70 L 3659.50 5799.10 L 3661.90 5794.40 L 3663.80 5789.50 L 3665.20 5784.50 L 3666.00 5779.30 L 3666.30 5773.90 L 3666.30 5730.50 L 2801.50 5730.50 Z" fill="#429cfd" fill-rule="nonzero" node-id="582" stroke="none" target-height="108.299805" target-width="864.80005" target-x="2801.5" target-y="5730.5"></path><g node-id="610"><path d="M 2801.50 5730.50 L 2801.50 5773.90 L 2801.80 5779.30 L 2802.60 5784.50 L 2804.00 5789.50 L 2805.90 5794.40 L 2808.30 5799.10 L 2811.10 5803.70 L 2814.40 5808.00 L 2822.30 5816.10 L 2831.60 5823.10 L 2836.90 5826.30 L 2842.40 5829.00 L 2848.20 5831.50 L 2854.30 5833.70 L 2860.70 5835.50 L 2867.20 5836.90 L 2873.90 5837.90 L 2880.80 5838.60 L 2888.00 5838.80 L 3579.80 5838.80 L 3593.90 5837.90 L 3600.60 5836.90 L 3613.50 5833.70 L 3619.60 5831.50 L 3630.80 5826.30 L 3636.20 5823.10 L 3645.50 5816.10 L 3653.40 5808.00 L 3656.70 5803.70 L 3659.50 5799.10 L 3661.90 5794.40 L 3663.80 5789.50 L 3665.20 5784.50 L 3666.00 5779.30 L 3666.30 5773.90 L 3666.30 5730.50 L 2801.50 5730.50 Z" fill="#ffffff" fill-opacity="0.3" fill-rule="nonzero" group-id="23" node-id="586" stroke="none" target-height="108.299805" target-width="864.80005" target-x="2801.5" target-y="5730.5"></path></g></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/logo.svg b/ruoyi-plus-soybean/src/assets/svg-icon/logo.svg
new file mode 100755
index 0000000..d3a984f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/logo.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 476.22 476.22">
+ <path d="M389.98,400.6c-64.58,76.59-176.66,93.98-261.1,38.54-57.75-37.91-92.29-103.74-87.69-173.54,1.28-19.35,9.54-58.77,33.33-61.44,20.49-2.29,43.2,14.41,62.99,21.14,64.93,22.1,150.88,21.39,214.51-4.81,17.24-7.1,39.23-23.9,58.05-13.31,25.61,14.41,27.13,67.07,24.38,92.75-3.94,36.78-20.9,72.7-44.47,100.65ZM150.91,269c-17.4-4.01-34.19-9.87-50.5-17.04-2.56-1.12-15.17-8.19-16.64-6.83-23.51,92.25,48.81,179.87,140.64,188.01,3.68.33,9,.72,12.64.79,3.76.07,7.18-.41,10.86-.78-.24-3.7-1.05-7.05-.77-10.85,1.3-17.71,28.61-26.36,26.88-43.25-.89-8.76-14.85-11.03-22.02-11.55-8.23-.6-17.94,1.77-25.24-3.11-5.39-3.61-5.08-10.32-7.37-15.76-4.74-11.26-15.86-17.54-27.79-18.47-22.55-1.77-68.52,7.5-81.87-16.63-11.77-21.27,12.24-35.1,29.25-40.42,3.38-1.06,7.52-1.31,10.88-2.55,1.03-.38,1.28.27,1.04-1.56ZM391.83,245.21c-1.03-.58-12.51,5.5-14.65,6.42-20.82,8.92-42.01,16.02-64.2,20.87-10.31,2.25-21.7,3.15-31.66,5.65-19.17,4.82-17.59,22.78-.42,29.11,10.27,3.78,25.34,3.94,36.56,5.97,23.56,4.26,56.74,11.28,53.56,41.93-.31,2.98-2.13,5.86-2.26,8.2-.08,1.48.18,1.21,1.5,1.13,5.18-9.87,11.46-19.09,15.7-29.46,7.55-18.44,12.19-42.75,11.17-62.68-.19-3.67-2.95-25.81-5.31-27.15Z"/>
+ <path d="M278.22,21.54c4.79,5.69,9.27,14.39,11.84,21.37.67,1.8,1.49,7.86,2.59,8.61,1.49,1.01,17.78-3.63,21.39-4.05,71.76-8.37,101.88,65.14,44.46,110.3-41.92,32.98-84.43,32.67-135.62,31.41-47.74-1.18-111.25-13.52-129.97-64.78-19.66-53.81,31.01-83.19,77.15-58.5,2,1.07,11.58,7.79,12.54,7.59,1.57-.32,1.05-4.62,1.34-6.14,3.75-19.76,11.37-39.47,27.8-52.04,20.82-15.93,49.31-14.16,66.47,6.23ZM256.71,71.1c1.07-24.31-18.03-42.71-30.8-12.52-5.88,13.9-7.77,32.56-3.36,47,1.73.46,1.24-.41,1.7-.95,10.15-11.8,19.02-25.11,32.46-33.52ZM340.18,123.96c10.12-10.37,17.92-27.48.94-35.85-29-14.31-83.74,17.89-87.91,49.24-1.6,11.97,2.2,14.36,13.71,14.53,21.84.32,57.86-12.13,73.26-27.92ZM173.64,117.92c-4.77-6.36-9.81-11.55-16.53-15.93-7.07-4.62-24.6-13.3-29.47-2.16-3.94,9.02,6.8,23.05,13.43,28.86,12.36,10.83,29.63,16.76,45.66,19.42-.28-11.02-6.62-21.58-13.08-30.19Z"/>
+</svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/maxkey.svg b/ruoyi-plus-soybean/src/assets/svg-icon/maxkey.svg
new file mode 100755
index 0000000..f8f8a7d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/maxkey.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256" viewBox="0 0 256 256">
+ <image id="鍥惧眰_1" data-name="鍥惧眰 1" x="32" y="25" width="192" height="206" xlink:href="data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADOCAYAAABo1g13AAAazklEQVR4nO2dS1NcR5bH/3kLYZl22OXdbCRdVtOoF4Zd9zxCsJudYNc7wScQRFvYO8Fmoo16AvgEwK53QquZHXjh3lJaWHinBH8AlxQdtiyZmxN5MqsooB6Z95V5b+UvQmGZKMGt4pw8jzwPhoA3xPFqPOxZON/h4beVL0EBCoaE+mJyFhBNMBYD4h4Ya0JACnuz548NbfrDhFIIAQ6wMwjB0ZBf+63F+U67np9ovgQFyImuoLMkBtgXAJuXX3b4SFJBWgBrQyTfIkIrKMZNggKkJI6/nEeCWbDoAQRmHQu7DS0wcCTiBRpocf6sVZHnLoSgAIbQCZ9MLGqBn4e92+Ir0n06JivR+HA8bnFGUIAhqFOeLQLsYYVO+GwwcQwhXiD67XAclCEowDV6hP5RjU75tLSA5KDOyhAUQLk3TSSTj8GwqP35wHWkZUhwgMaHwzoF0mOtAHTaC/YUgjI2ATPaADtE9OtmHazC2CmAOu0nlsGiR+G0z4i2CvzHZ/uVfQsePEMpdN0cYDX49rnDIcRmFRWh9goQBL9UKqcItVWAIPhOqYwi1FIB4vjJYyRsIwi+c7xXhFopgMrhR3tjc2lVFWSwzD6s+Jg1qoUCUJmCuLUX0pm+w/Z9S582PHiGTMR3159CNPYB9vsKv41xYRaisdj89N/etN/+w4sivMpagDh+MosLtg+GLzx4nIA1fliDSloAdeqzv4PhXzx4nEA6vLAGlbIAuunkMJz6dcOdNaiMBVCpzQl56ocMT/1Q1uDzP71st/9RqhJ4bwH0hdZTfaEVqD8b/Hxrs6x36bUCqC6syaOQ1x8zGA7B3q+V4RJ5qwDxnfVFMOyF29yxhSN6v1C0EkQ+frqU5WF4HoR/rJHW/4QOwgLxLgiO765vA/jag0cJuOc2GP7c/Ozf0X7z3bdFPI03CiCD3eZn//m/APuzB48T8Iv5opTACwXQ+f3/A9gfPXicgJ/MNz/7j7j95rsXeT6d8yA4ZHoClrTwdmqBtzdyacx3qgBB+AMpyU0JnClAEP5ARnJRAicKEIS/CwejSc+yc6rzi3xDA22vIGQ6+DOaKk1/vzJdepzJrASlK8AYCn+bhtEK8S0iObpc/rlo5XXBQ2XhSkFmIaIH9LmO17iXTEpQqgKMifC3AXGISLyUQ2ddTV+m9tCOUtRrmG8/UitBaQqgi9pOain8NFCWyRP+0Ndx492Zp4w9qKeFYPv8/JsV639VzMPcJL6z3qpVHT81eosXwCf7nOeTkiuLOP46RpIsg+GRjiVqAtvh59+sWf0ay3jjuryhDuXMUtB3fT7pbdGTNJYBPKrWkw/Eqpy6cAWgwjZgo+ifUygVPu1NIauACz0suOJWIcIy51sHJi8tVAF0SfPzIn9GoSjB3+T8b8eVfQ8piOMnyxVXhDai93MmmbbCFEBnfE4qmX0YU8G/TsUVgePt1NyozFAhClDZjI/M17NkZdwF/zqVVQTGjvnZNwtDX1LEz43vru9XLKii4JafbzmLVcRpV7gGCRmZczYDJ3N0dOZoo4LB8tCgOHcF0INpd/L+voVB7k5jhfO/FipY4jWa+BkxInk5hVlEuEf/FWiCWbqJgm6XW1RGIfASAi25LJv9AYVnpkgRRHJUKWsQJQuDrHquCqD9/td5fs8CaSPCCudbh0X8CBL4X+gGdh4Ryrl86iiGVAqGQzaDwly5+O66tAZPi/r+OTMwHshXAe6uv66E31/QqU9ujIDMfD0kwfcBOWFBQDaRHOftPlXKGjAc8rOtpZtfzokK5fvX+PlWbi6aPumXvRL6wUiLcMBmkOu8/gpZgxu/+1wUoBKuD2V4xFJeN7jiFPMQkDeoD619ePdwrQybeVkFyhQlbNvztPeN+4F8xqKoCk9/IZcnWshD+KXgi1PI93tEtTTVE35oN1Uq72txir2eDFRqOH+2jyiao4PGX5oQH+31Pl1mC+C/6yN2+fmzzHVIdOIrM1/PJRwqVljLahF0XPDc64pTgSX+o0p+ZFKACrg+m1lz+7UX/Jvs5+EaxXef7ADscUHPmJVuViibCyQmt314NwNYyyL8MrgVP2CbXJ3xEX70uEaZglptdUsbcmtJjE9/ofeX2gLEd54sg7E9g5eWTyRWyCdNifgBj5Fgo6L+fZ5IK7CS5T7B6wxRJObSWwDGfH1TqYVfBoMU4ArsBOEnZHB8JC0hpXtToK2wb5agTe4xf9ZKpQA68PXv8iOb8Mu05smYuTtmCKziHU7SZov8UgKxi2hquuMeW7tAHje2p77gIl9fhAUcRshM0X2k+pydukMDStztFcDPSs9U2R4y6++oYSec+jYw7LDfw6r3tkPp8kMXoFgbVPNlpQCepj3TCb8y52E4V1pU0d1SmnRpfG/9pIR7At2/PbUzrI11wupbJpN+XXgxcczPntkL//f04T8Pwp8BJcBH4hQL1krA3i8AkyfFFdHJrZNs06TY0dgCeHf6k2mT5Q12FZ0k/IzKGEKWJx/k52+tBLrBJt+W2RStrOZZIO9O/xTCf0q1O0H48yXWlsDqNKffXSRSxRF9aFMG8OzZwMaXQRhZAA99f+uMD538EaU5A8WQzhJkK5kw8vOHYRYDXNya92efJI3Asxd+Br8rVqtPnComiD5sQEw+tI4HcmpqMnOBfLn1lX5/xKwuVMg0R3ge3J5SUEpgcWvM+U5b9mkYPxzDS+rxJXcne0ffSAWgmh9fsiUU4Ji/6ZDqdEKs71aM0X0aow62Nrm+Z1uzeY6tGW0BIl8uvdh+ijKHwyD8TpjXlbTG0F0OGzTVolu+kPu0kaExgAp+mftbUkp5Wrs+0m2rzzTqqiGwKl7hzKpsgiVrENFlrEZ+vrzFLW4Q8dDQ1puyB8siN3FKNe1+lmqPE2pu0ZxNUExZIcYeljWhb5QL9KDoBxgJA7cU/rhC82rqjaBg2CoopqwQm5orazzlQBdIzY33wH9mie3Wj73g93tFjF/pQDK69KKsUIkMtgBqaYJjZOBrfhJQJ1eo7PQPGQ+c+vl7GeYCPSzxOfpjkfMn1yep+CKOerOXtqusSPoqQHznL4vuBxzJ09/iokOEHl7PkfcD3jUd9Y8BWGOx9Ce5TtTYNX2peEXzOKu844pTDjzBWXdxdofL6dH3ujuAq6roEb0HrxgUBLvN/sg6f/7f5rlfZnfp4gGcBtbKYVS30WLTsAr89BBeqQiL+nfle9CvxjD+a3HTqtNyQwG8yP4wGC04w2XO3/+sjyAhP8hjbLnOq3N9090Z3uXfpkf1njfT9hCXwY2LMOcTvWTe/2xr2vTl4hR+j2RXM/t3cRs7tie99Y9SdyC+bHHZLOM9Z+VmEOx6mTUTNpkf30//TXyMaTaDjTIEQVoGNkOfyTRZGzdI61bae87KFQugl9v95PSJomjaNPvj8en/EsCiq31eHXRyYLukzyjzFDkXXLUAFw3HlxXihbHwv6IA0D/hF9hlM5h1LfwSdp9ihIVCrYGgeh85VXq6asKPGwrAIrcKEMF8XxeDX5OHVcC3wu77levucYvyn8wmsEsunsdB7iiuKYBj/7/9OyMF0MGeT1frMq25kPfqoTyRPrlU0Jy+5TH1/97HahX8/GFcTYMKl7X/4sWord6XL8WqPz3Kuhn8D15vRiGkgopT+mvaUvFK+vnD6CqAzv+7w879cV+nhK7/u5SX8FMDEiZiXLCe2Ia10UjawG+tPColUylBiancsum1AI5X2jSMThV96eNL8LuSZTk1Zd0ubslMzQOALSLRJQ7XrVsiPdVJ2aDEAXYMIV50VvykQSuBWd9EhH0k+S3T841LBUiYO6FiaBkXvqk9vD6wqbMs1qhW01urSJjtkr0YEHIl67JSBuwjen/Qu/XQ+COfwYb4AV/Q59kfb8sX8uQyCHYZAAvxrfFr/XB/jnVQaYU88Wm3QjJ5om/bsxS1qVvfZPIovrueLvP0CwXFV5Wnk82aoaC+1sKPKwogmDsXKGKm7k/siftjnU2J4yezSvBJcfKs5pSfx7bc0q9iCHPYnBJ2+ge6bkffXHubzcobUgDyRV3W/7enTE8aH1Kf1v5wHD95jISdFKy8tLiEFM0CfcpvglHzeiXKF/JEW4AJd6e/9P/N05+u+xS4reujXB5W1kWRHGNzEsfrVsVwJPg1DXJHoRTgwmEALMSZ8WtdX9RZ3qbSye9iiXiCHVtLMK4oBWAOFQDMKI3ogf/PbXxj5fOXdvJfp4mEHdnGBONIJwh290FFhnl096v3jQvK9EQ9q/mYBdCE+CgMBxuBtgDCYa/mhJnvyZwrgHlmRC0TcX/6CjGfOkU6JigFEMxZBsi499etBTg2DRK12+FTa+LTuLkRpmUMwLULZLFIweFEAdnAbopvq6SkK/Tpz8EKDKCjAG5OCCbMFcBlo74wuxH18PTv4FfvhEdE+hLMDQJvTH6uOOnOxikfhrZxwZtcJeUnTar2DdwgAm479A8js/7jSaenv3m1pzfLRPqQMPfDzjxkAnjXlBLmBsNLsMhhmYagBveRqIECHiwTGQh7GMdfpi6hLvC52kUuwBjFBDARMgTDMYxTHJaTmBEjibzclEml3ZFYcqEI5ouyi0AYB8EuexXM6pRclpNUn1jdXJdfvuFWAaqB4UVdUICMNCGi0me8BgUI+IMQwQIExprS49GgAAF/kGtRSyYowGjMfHvzgD4wCIvByHnhVgFMA0fhsE1PGJrlxgdnuewa0EaEpbJWo/YyocqRXU7EMsA0FVkMptkdvy2A3LaflH/CjqQhLecnLc4N22JzZuCe4HJgphWe7oTLsA1TTm2L730lh1b5eRss2DH/cWtspj2YEgHv/J8C8M6hBbBpxLGZb1Q2kTAv6R4jorI3c1+BJUbuBc2vcRUHCDR1P/Joovd+jgmXa6d4+lGKdaYTBDsSLovbU5dxgOE4FjpMmNmQr1IRLAj/ANwqgF2Njzv3wmYcI7vwK9CUwW/EjHcujxudpnhnQWYcf+1/KhSYN13zT6k8lm5obiEIHFht3B8zOk3x5sOpcicxCzJZ+jHkuWCz5p+9X3FoVXueA5yfb/nWo+wVHRfI3Qlh3uvr1re22ElGsUB0kdc6orS0waIFx8/gPdoCuLzGZ0Z5dhpL4vhGWO8lNoLz/zksZDGdKRFWguszGm0BEofm2uriyHWeffRGlR60+1G+EkRiJaQ9zVAK0Ljl0r+OLQY3uU4xxuKV3RrUkpWgrYT/mdWNr7RsxncdNYMUQJtKd1ag+U/TQNj9qRbhqa2wkBJEF0uUkiwKSneKhRTCH+uFeUe2yl0HeqtBHQbCZiM79HhCt36tqg61HjpLMYEKSvPe2q42u7CpOdumcp3a7TTKx2DYFqd4bRPrVJ1eBTAa/1EILDKf+28zprA45tOcltLS8vOtZUTRdA6K0AbELqJoTlqYVNWUv1JMc92akUUQp/Sn9m5Rd99iHH+5iqT8puQub6c+N9kUo9ek+jLeI9MiOXUJeDEPgUeGS8rbYKIFJgvbPtnPUkIsfsBjCIyuXWLYgcBuXTfI9CrAvNO5MUKs8B/N/FfxCj85G5XYC6Mivbm8hEONLxRNgMkZPur9yRQ1Lcq+aKVZh9oP8YpWzdrsL+B6N1rtyql7FEBONps0G1VYzKPs8/NvjC6PxCmtHbJKSRYI15agEiek+B6zYDhKeYBwJLQZvzbdb90YQJdFO3xjYtE4HZp4VGujfOajKvjL5D6mF37Qe41wUqf44FpPsNOGjqZpOlSfQD6VHSsl+N75FpuBkM+PTMLfi8wSyWyRL1Y4NVcVQCRuhUo0bD5Q3/pbO6ejV0IhU53iB2wbBbz2bFQ9bcp6/8d9HIA23k5Nm+4Nlh++J5vjryIv7ATWXMcFZJEiClyLXy8rq3UFlqqWLbpiAdzHAbTOx+Y0yftSKR8EZVlOXN2sdk/9CCelCD+6O9xeVy0+6DMXyHljt/mSiXeUo/azqV/QVptSb1ZJ8KUL9gteQzgra1jWSYFKxAfs+hec3wdIomTBdEgSnbIM7i7wzOF61epB3m4CZXdU3/IjL+5HLvH+/uCGAkAtLPjJ2eI80MS4Y372jXEzh7exwGCOqaRD4DhNTp1qeN6Ry/EADIseLBEfjicxUT8GKcC+822HNlbAr/IIO6QLp9o9pXCc9RT7ce1GdQ4iqeD3aE6R7wI/mH02A9edclfoPxlOiGMw5lYBksYj01y/rMcRp/Ta6m1CVALe/7n7HU+i8CcqEoe95/3pPxy38eHQfVO3WNZ7d01Z8TYgDoAs2u1C7iIy0VcB9IAn9/Ue4iPjunvyLxnC/Bt/2WTT/h1Qg8ej+zDgSYh5mwXPbAYbzsenBG4Ske/vZSZoyH6A31pezLaxK4+QPvJScIW8gmMSa6YPRNUIJTJQAdStsHB/0yqtwJ0nxhdJOtXm3xz88WXByvVJJh/Hd9dfW8Z/qRm+ISYSfpQdM7ZtMTkC7L7qYir2oQIGbNrk/rXQb6i9wZOv47tf7RWtCEMVQM259GLacROf/mLnCv0a4gGnqDZKu7GMYvJal5pYVoqwXlhZxegdYezCk8UKYtUqIFY7BZa8X11UTzg+thP+OH7yeMgF3wa5RRausCl9b4KvPhiVSL92WhpxCcfbqTnTcmlkbwEM2GPdIkpuTjJ5YiRjcpUq+7CSV3/0SAugS6R98adjW1eIam0YWYJA8aTrj04mj4wPWDk9I8f4wGxNqlr940lqUbpC61ZlGnp0iVc1KLVDpZ6thV/79ykEmeKDo/jueqay75EuUIf47pMdgBmPCC+YNqL3c7ZmUNflW091C4z6YCneWrCtbI3vrNuOZxkEhxCbpmN1ejFXADnEKUle5/CweWEdDyAoQRGkGpWi/f6jfMvY2T6iXzdtDkbjTfF6gK5PLYgyHrBuhKEr+XBbnBfK57cX/mb+wo+etOlX26bxgbECqFdHnq3bEctpcsTsPg7BMBdSpJl4mXogmJjcK7aBSaxSfGCQNjV2gTp40SxzkzV+vmVdaqubtws4iWrPAW5jNU11pz6wyjxIOaJkZVBzlb0C+HUvcEmEZc63Urlo4hV2bHaAjTmb1je8GgfC30P/+MBaAaDeiE+zOTu09YKIVOUPeoTJ03BhNhApOCtpp2G7Ff4rbODt1G4neZJOAXy1AlmVILhE/ZFN7R9hJW1Di0fC36GbNk2lAPBhn8BgMikB/Js+7Q6VKVuhpEFKPBT+S6Jo2i4L1APnf9vxpFL0Ok0k7MT2trgX7ePmscWlushy8o8xXVvhl+P4+V95agVQ38ODtslBJNjPUkYr03tshi7NVsYsXXpM6c376bI8HbwWflooyDaRNgboxbMSiX5s8POtzIqqb5BT1q1UgmOd4clk1VV8+NE2XUr5Ss8q2ewKIN+wmDyB8FgwZBD3ZmrFtmyiHzVUhFwEH53yhotJeclYzkDeNDBxzM+edacOZnOBuiNUEr8rLeXczE9/PsmjfFaWUrAZTOtmGx9joNGoaXTSt5c3uZkW/XVQM2Unj7wWfloy2Lgiq5ktQIcKuEKgD0CItTRVg4Og1KmgAb0PK2AV1EzSj7Gf54wev4PdK9yoGMhPAargCnVh+3j78VoeLlEv3SnNfimDPN2/1XM5cw3myaKKW3uGK17dwnDIz7ZuNEblpgDwZbS6OUNrRLKiL9XmSRlUr2tZCsHplJcDAW7jsKhpbNTDm7ANDy9DbyKzPixa0BXNV8hVAeD3BdkA7GvI09Az0nye/GQ5+VlQv3JaAeJ6T7FcTfRSC3yr6PGDlTr1O0RY4nyr731G7gogie99dSQHWhXxvQsidUdRHmhr0WshrloLFbS2kchbbrRdzNnX5S8yxlutxKl/ySY/3xoYnxSjAJWKB65QqFtUVXTr4nblUr8ML/nZ1tBdCoUoAJSPOIuEmXf7e0U5bpHvUEwn2NNKuTsdhvj9V19WINWLB64znopQacHvEIk5k4LIQhUA1bkfGI4cxpSwXf5j/0CqDpDbenFrEREeVVrwFcYdgoUrACgoXn+utxhWHQqW0fhwXBeroF3VhxUMbgcxNOi9TjkKoILiowovd7uJLCVIxAu5TkpPz6sMKqMzsQzGHtbgtO9B7PLzZ1aDskpRAHTmConkqIKZodFcKoO3lkHN4ZlYrJ/QawwyPv3/WYnUWgkuadG2fcGO0XjfcqUQqjLz1jytVQWrQp1Sepi8CJya59y+tKVUBcD4KEEvnG5p5W2tvLVtCA584Hm5TWql0K0YF3SrPAvG7kHQ2tXxaO43THcO/ucOqPYdQW60lXIIWc4gf3lvAHapFEKoXyhjPQeFuKe+JitQ6evNsf4MMwo/XCkAghIEspKD8COPhpi00CVFFM3RGwkEbMhJ+OFSAdAZuMuihaAEAWMo4M1H+OHSBeplDAPjQBoyZHsG4YUC4FIJntfqsiyQJwf8fCv3SRNOXaBelDv0fkE3awcCvWwWIfzwyQL04unw3YAbUo2+N6Xh4y+1/ea74+bnf3oDwf7Lg8cJuICmtyVL/OzZ34v86V5agA4hOB5TVKZnMa9MzzC8VgBcKsF2TcqpAyOxr+jMgvcK0CHEBbVHNvyvDJreUBSVUQAEl6i+yI471lgpw+W5TqUUAN1mjlsblW+zDEAXBG4WmeUZReUUoINq3I72gjWoKA5P/V4qqwC4HNa0GmKDSuH81O+l0grQIcQGFUHe8rNozfWp30stFKBDHD9ZVvNsgiJ4BeX1k1UfJ+7VSgEQ3CLf8Mrd6UftFKADuUVJIu8OUm+LDKRGCv4uoqmdPEuXi6C2CtAhKEKpVEbwO9ReAToERSiUygl+h7FRgA6kCPhtESJ6HILlzLQQJQfAJ/tVE/wOY6cAvaisUS2GwZYLXWKJzTrsURhrBejQdY8YHgSrMBCZuz+oopszjKAA19CL/mT73cMws0j69uIAkTis69acoABDiOO/LCJpLI6ZZeCAeFFnoe8lKIAh2jLMg4kHNYsZ2mCiBSZeAL8djts2nKAAKYjjjSbwTznacVGtPGWzFXKXegReTrL+pFUnn96WoAA5QbNOgVkkNKXZF6XQk6nFmZpMXZ/NNnkRFKBAupYCoomkof4rlUOipjtniSva6jSXy7JFi6ZLR4LjgrXRkCf77/g4n+ymBAXwAFpm0WWij1LIsekftDA320GwcwLA/wMQTIdh+nm6rAAAAABJRU5ErkJggg=="/>
+</svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/404.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/404.svg
new file mode 100755
index 0000000..6df5019
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/404.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M121.718 73.272v9.953c3.957-7.584 6.199-16.05 6.199-24.995C127.917 26.079 99.273 0 63.958 0 28.644 0 0 26.079 0 58.23c0 .403.028.806.028 1.21l22.97-25.953h13.34l-19.76 27.187h6.42V53.77l13.728-19.477v49.361H22.998V73.272H2.158c5.951 20.284 23.608 36.208 45.998 41.399-1.44 3.3-5.618 11.263-12.565 12.674-8.607 1.764 23.358.428 46.163-13.178 17.519-4.611 31.938-15.849 39.77-30.513h-13.506V73.272H85.02V59.464l22.998-25.977h13.008l-19.429 27.187h6.421v-7.433l13.727-19.402v39.433h-.027zm-78.24 2.822a10.516 10.516 0 0 1-.996-4.535V44.548c0-1.613.332-3.124.996-4.535a11.66 11.66 0 0 1 2.713-3.68c1.134-1.032 2.49-1.864 4.04-2.468 1.55-.605 3.21-.908 4.982-.908h11.292c1.77 0 3.431.303 4.981.908 1.522.604 2.85 1.41 3.986 2.418l-12.26 16.303v-2.898a1.96 1.96 0 0 0-.665-1.512c-.443-.403-.996-.604-1.66-.604-.665 0-1.218.201-1.661.604a1.96 1.96 0 0 0-.664 1.512v9.071L44.364 77.606a10.556 10.556 0 0 1-.886-1.512zm35.73-4.535c0 1.613-.332 3.124-.997 4.535a11.66 11.66 0 0 1-2.712 3.68c-1.134 1.032-2.49 1.864-4.04 2.469-1.55.604-3.21.907-4.982.907H55.185c-1.77 0-3.431-.303-4.981-.907-1.55-.605-2.906-1.437-4.041-2.47a12.49 12.49 0 0 1-1.384-1.512l13.727-18.217v6.375c0 .605.222 1.109.665 1.512.442.403.996.604 1.66.604.664 0 1.218-.201 1.66-.604a1.96 1.96 0 0 0 .665-1.512V53.87L75.97 36.838c.913.932 1.66 1.99 2.214 3.175.664 1.41.996 2.922.996 4.535v27.011h.028z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/bug.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/bug.svg
new file mode 100755
index 0000000..05a150d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/bug.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M127.88 73.143c0 1.412-.506 2.635-1.518 3.669-1.011 1.033-2.209 1.55-3.592 1.55h-17.887c0 9.296-1.783 17.178-5.35 23.645l16.609 17.044c1.011 1.034 1.517 2.257 1.517 3.67 0 1.412-.506 2.635-1.517 3.668-.958 1.033-2.155 1.55-3.593 1.55-1.438 0-2.635-.517-3.593-1.55l-15.811-16.063a15.49 15.49 0 0 1-1.196 1.06c-.532.434-1.65 1.208-3.353 2.322a50.104 50.104 0 0 1-5.192 2.974c-1.758.87-3.94 1.658-6.546 2.364-2.607.706-5.189 1.06-7.748 1.06V47.044H58.89v73.062c-2.716 0-5.417-.367-8.106-1.102-2.688-.734-5.003-1.631-6.945-2.692a66.769 66.769 0 0 1-5.268-3.179c-1.571-1.057-2.73-1.94-3.476-2.65L33.9 109.34l-14.611 16.877c-1.066 1.14-2.344 1.711-3.833 1.711-1.277 0-2.422-.434-3.434-1.304-1.012-.978-1.557-2.187-1.635-3.627-.079-1.44.333-2.705 1.236-3.794l16.129-18.51c-3.087-6.197-4.63-13.644-4.63-22.342H5.235c-1.383 0-2.58-.517-3.592-1.55S.125 74.545.125 73.132c0-1.412.506-2.635 1.518-3.668 1.012-1.034 2.21-1.55 3.592-1.55h17.887V43.939L9.308 29.833c-1.012-1.033-1.517-2.256-1.517-3.669 0-1.412.505-2.635 1.517-3.668 1.012-1.034 2.21-1.55 3.593-1.55s2.58.516 3.593 1.55l13.813 14.106h67.396l13.814-14.106c1.012-1.034 2.21-1.55 3.592-1.55 1.384 0 2.581.516 3.593 1.55 1.012 1.033 1.518 2.256 1.518 3.668 0 1.413-.506 2.636-1.518 3.67l-13.814 14.105v23.975h17.887c1.383 0 2.58.516 3.593 1.55 1.011 1.033 1.517 2.256 1.517 3.668l-.005.01zM89.552 26.175H38.448c0-7.23 2.489-13.386 7.466-18.469C50.892 2.623 56.92.082 64 .082c7.08 0 13.108 2.541 18.086 7.624 4.977 5.083 7.466 11.24 7.466 18.469z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/build.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/build.svg
new file mode 100755
index 0000000..57fe4a0
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/build.svg
@@ -0,0 +1 @@
+<svg t="1568899741379" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2054" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M960 591.424V368.96c0-0.288 0.16-0.512 0.16-0.768S960 367.68 960 367.424V192a32 32 0 0 0-32-32H96a32 32 0 0 0-32 32v175.424c0 0.288-0.16 0.512-0.16 0.768s0.16 0.48 0.16 0.768v222.464c0 0.288-0.16 0.512-0.16 0.768s0.16 0.48 0.16 0.768V864a32 32 0 0 0 32 32h832a32 32 0 0 0 32-32v-271.04c0-0.288 0.16-0.512 0.16-0.768S960 591.68 960 591.424z m-560-31.232v-160H608v160h-208z m208 64V832h-208v-207.808H608z m-480-224h208v160H128v-160z m544 0h224v160h-224v-160zM896 224v112.192H128V224h768zM128 624.192h208V832H128v-207.808zM672 832v-207.808h224V832h-224z" p-id="2055"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/button.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/button.svg
new file mode 100755
index 0000000..8b97436
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/button.svg
@@ -0,0 +1,10 @@
+<svg t="1588670460195" class="icon" viewBox="0 0 1024 1024" version="1.1"
+ xmlns="http://www.w3.org/2000/svg" p-id="1314" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="200" height="200">
+ <defs>
+ <style type="text/css"></style>
+ </defs>
+ <path
+ d="M230.4 307.712c13.824 0 25.088-11.264 25.088-25.088 0-100.352 81.92-182.272 182.272-182.272s182.272 81.408 182.272 182.272c0 13.824 11.264 25.088 25.088 25.088s25.088-11.264 24.576-25.088c0-127.488-103.936-231.936-231.936-231.936S205.824 154.624 205.824 282.624c-0.512 14.336 10.752 25.088 24.576 25.088z m564.736 234.496c-11.264 0-21.504 2.048-31.232 6.144 0-44.544-40.448-81.92-88.064-81.92-14.848 0-28.16 3.584-39.936 10.24-13.824-28.16-44.544-48.128-78.848-48.128-12.288 0-24.576 2.56-35.328 7.68V284.16c0-45.568-37.888-81.92-84.48-81.92s-84.48 36.864-84.48 81.92v348.672l-69.12-112.64c-18.432-28.16-58.368-36.864-91.136-19.968-26.624 14.336-46.592 47.104-30.208 88.064 3.072 8.192 76.8 205.312 171.52 311.296 0 0 28.16 24.576 43.008 58.88 4.096 9.728 13.312 15.36 22.528 15.36 3.072 0 6.656-0.512 9.728-2.048 12.288-5.12 18.432-19.968 12.8-32.256-19.456-44.544-53.76-74.752-53.76-74.752C281.6 768 209.408 573.44 208.384 570.88c-5.12-12.8-2.56-20.992 7.168-26.112 9.216-4.608 21.504-4.608 26.112 2.56l113.152 184.32c4.096 8.704 12.8 14.336 22.528 14.336 13.824 0 25.088-10.752 25.088-25.088V284.16c0-17.92 15.36-32.256 34.816-32.256s34.816 14.336 34.816 32.256v284.16c0 13.824 10.24 25.088 24.576 25.088 13.824 0 25.088-11.264 25.088-25.088v-57.344c0-17.92 15.36-32.768 34.816-32.768 19.968 0 37.376 15.36 37.376 32.768v95.232c0 7.168 3.072 13.312 7.68 17.92 4.608 4.608 10.752 7.168 17.92 7.168 13.824 0 24.576-11.264 24.576-25.088V547.84c0-18.432 13.824-32.256 32.256-32.256 20.48 0 38.912 15.36 38.912 32.256v95.232c0 13.824 11.264 25.088 25.088 25.088s24.576-11.264 25.088-25.088v-18.944c0-18.944 12.8-32.256 30.72-32.256 18.432 0 22.528 18.944 22.528 31.744 0 1.024-11.776 99.84-50.688 173.056-30.72 58.368-45.056 112.128-51.2 146.944-2.56 13.312 6.656 26.112 19.968 28.672 1.536 0 3.072 0.512 4.608 0.512 11.776 0 22.016-8.192 24.064-20.48 5.632-31.232 18.432-79.36 46.08-132.608 43.52-81.92 55.808-186.88 56.32-193.536-0.512-50.688-29.696-83.968-72.704-83.968z">
+ </path>
+</svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/caret-back.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/caret-back.svg
new file mode 100755
index 0000000..9bae722
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/caret-back.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path d="M321.94 98L158.82 237.78a24 24 0 000 36.44L321.94 414c15.57 13.34 39.62 2.28 39.62-18.22v-279.6c0-20.5-24.05-31.56-39.62-18.18z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/caret-forward.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/caret-forward.svg
new file mode 100755
index 0000000..1ec3f7d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/caret-forward.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path d="M190.06 414l163.12-139.78a24 24 0 000-36.44L190.06 98c-15.57-13.34-39.62-2.28-39.62 18.22v279.6c0 20.5 24.05 31.56 39.62 18.18z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/cascader.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/cascader.svg
new file mode 100755
index 0000000..b372ff0
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/cascader.svg
@@ -0,0 +1 @@
+<svg t="1576153230908" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="971" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M772.87036133 734.06115723c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714843H475.90991211c-56.60705567 0-102.66723633-46.06018067-102.66723633-102.66723633V600.82446289h305.859375c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012S827.9942627 467.50537109 772.87036133 467.50537109c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714844H373.24267578V401.01062011h321.92687989c55.12390137 0 99.94812012-44.82421875 99.94812011-99.94812011V190.07312011C795.11767578 134.94921875 750.29345703 90.125 695.16955567 90.125H251.12963867C196.0057373 90.125 151.18151855 134.94921875 151.18151855 190.07312011V301.0625c0 55.12390137 44.82421875 99.94812012 99.94812012 99.94812012h55.53588867v296.96044921c0 93.35632325 75.97045898 169.32678223 169.32678224 169.32678223h203.19213866c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012s-44.90661622-99.86572266-100.03051758-99.86572265z m0-199.89624024c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857423s-14.91394043 33.28857422-33.28857422 33.28857421-33.28857422-14.91394043-33.28857422-33.28857421 14.91394043-33.28857422 33.28857422-33.28857422zM217.75866699 301.0625V190.07312011c0-18.37463379 14.91394043-33.28857422 33.28857423-33.28857421h444.03991698c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857422V301.0625c0 18.37463379-14.91394043 33.28857422-33.28857422 33.28857422H251.12963867c-18.37463379 0-33.37097168-14.91394043-33.37097168-33.28857422z m555.11169434 566.23535156c-18.37463379 0-33.28857422-14.91394043-33.28857422-33.28857422 0-18.37463379 14.91394043-33.28857422 33.28857422-33.28857422s33.28857422 14.91394043 33.28857422 33.28857422c0.08239747 18.29223633-14.91394043 33.28857422-33.28857422 33.28857422z" p-id="972"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/category.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/category.svg
new file mode 100755
index 0000000..df92526
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/category.svg
@@ -0,0 +1 @@
+<svg t="1715954426124" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3305" width="200" height="200"><path d="M664.081597 1023.943114a78.246037 78.246037 0 0 1-78.985549-76.795456v-284.996471a78.27448 78.27448 0 0 1 78.985549-76.93767h280.843828A78.189152 78.189152 0 0 1 1023.939417 662.151187v284.996471a78.246037 78.246037 0 0 1-79.013992 76.795456z m-585.067605 0a78.246037 78.246037 0 0 1-78.985549-76.795456v-284.996471a78.160709 78.160709 0 0 1 78.985549-76.93767h280.786942a78.302923 78.302923 0 0 1 79.042434 76.93767v284.996471h-0.170656a78.246037 78.246037 0 0 1-78.985549 76.795456z m0-585.096048a78.217594 78.217594 0 0 1-78.985549-76.93767V76.912925a78.189152 78.189152 0 0 1 78.957106-76.795456h280.786942a78.27448 78.27448 0 0 1 79.042435 76.93767v284.996471a78.27448 78.27448 0 0 1-79.013992 76.795456z m589.675333-5.688552a77.193655 77.193655 0 0 1-77.990052-75.885288V75.888985a77.25054 77.25054 0 0 1 77.990052-75.942173h277.26004a77.25054 77.25054 0 0 1 77.961609 75.942173v281.384241a77.421197 77.421197 0 0 1-78.132266 75.885288z" p-id="3306" fill="currentColor"></path></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/chart.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/chart.svg
new file mode 100755
index 0000000..27728fb
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/chart.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h36.571V128H0V54.857zM91.429 27.43H128V128H91.429V27.429zM45.714 0h36.572v128H45.714V0z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/checkbox.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/checkbox.svg
new file mode 100755
index 0000000..eae8be6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/checkbox.svg
@@ -0,0 +1 @@
+<svg t="1575982282951" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="902" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M828.40625 90.125H195.59375C137.375 90.125 90.125 137.375 90.125 195.59375v632.8125c0 58.21875 47.25 105.46875 105.46875 105.46875h632.8125c58.21875 0 105.46875-47.25 105.46875-105.46875V195.59375c0-58.21875-47.25-105.46875-105.46875-105.46875z m52.734375 738.28125c0 29.16-23.57015625 52.734375-52.734375 52.734375H195.59375c-29.109375 0-52.734375-23.574375-52.734375-52.734375V195.59375c0-29.109375 23.625-52.734375 52.734375-52.734375h632.8125c29.16 0 52.734375 23.625 52.734375 52.734375v632.8125z" p-id="903"></path><path d="M421.52890625 709.55984375a36.28125 36.28125 0 0 1-27.55265625-12.66890625L205.17453125 476.613125a36.28546875 36.28546875 0 0 1 55.10109375-47.22890625l164.986875 192.4846875 342.16171875-298.48078125a36.2896875 36.2896875 0 0 1 47.70984375 54.68765625L445.3859375 700.6203125a36.3234375 36.3234375 0 0 1-23.85703125 8.93953125z" p-id="904"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/clipboard.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/clipboard.svg
new file mode 100755
index 0000000..90923ff
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/clipboard.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.857 118.857h64V73.143H89.143c-1.902 0-3.52-.668-4.855-2.002-1.335-1.335-2.002-2.954-2.002-4.855V36.57H54.857v82.286zM73.143 16v-4.571a2.2 2.2 0 0 0-.677-1.61 2.198 2.198 0 0 0-1.609-.676H20.571c-.621 0-1.158.225-1.609.676a2.198 2.198 0 0 0-.676 1.61V16a2.2 2.2 0 0 0 .676 1.61c.451.45.988.676 1.61.676h50.285c.622 0 1.158-.226 1.61-.677.45-.45.676-.987.676-1.609zm18.286 48h21.357L91.43 42.642V64zM128 73.143v48c0 1.902-.667 3.52-2.002 4.855-1.335 1.335-2.953 2.002-4.855 2.002H52.57c-1.901 0-3.52-.667-4.854-2.002-1.335-1.335-2.003-2.953-2.003-4.855v-11.429H6.857c-1.902 0-3.52-.667-4.855-2.002C.667 106.377 0 104.759 0 102.857v-96c0-1.902.667-3.52 2.002-4.855C3.337.667 4.955 0 6.857 0h77.714c1.902 0 3.52.667 4.855 2.002 1.335 1.335 2.003 2.953 2.003 4.855V30.29c1 .622 1.856 1.29 2.569 2.003l29.147 29.147c1.335 1.335 2.478 3.145 3.429 5.43.95 2.287 1.426 4.383 1.426 6.291v-.018z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/code.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/code.svg
new file mode 100755
index 0000000..ddbfc48
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/code.svg
@@ -0,0 +1 @@
+<svg t="1546567861908" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2422" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M318.577778 819.2L17.066667 512l301.511111-307.2 45.511111 45.511111L96.711111 512l267.377778 261.688889zM705.422222 819.2l-45.511111-45.511111L927.288889 512l-267.377778-261.688889 45.511111-45.511111L1006.933333 512zM540.785778 221.866667l55.751111 11.150222L483.157333 802.133333l-55.751111-11.093333z" p-id="2423"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/color.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/color.svg
new file mode 100755
index 0000000..a3c1e14
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/color.svg
@@ -0,0 +1 @@
+<svg t="1577252187056" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2508" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M747.59340925 691.12859384c11.51396329 0.25305413 22.43746719-0.21087818 40.74171707-1.51832482 29.35428085-2.10878421 35.84933734-2.36183835 46.47761114-0.8856895 24.71495444 3.37405491 41.12129828 21.76265671 32.47528161 47.95376084-85.57447632 258.19957947-442.00123984 249.76444099-628.67084683 50.73735554-153.47733892-159.33976008-153.09775772-414.41833795 0.92786545-573.42069196 159.71934128-162.67163983 424.03439521-166.59397897 565.78689185 0.63263534 80.38686649 94.81095318 108.34934958 169.16669549 89.11723508 230.57450162-15.01454608 47.99593598-50.61082928 77.68762207-119.77896259 114.63352789-4.89237973 2.65706845-29.35428085 15.52065436-35.84933652 19.02123633-46.94154346 25.30541465-63.51659033 41.20565021-62.20914449 58.45550757 2.95229856 39.13904114 24.16667102 52.7196135 70.98168823 53.81618115z m44.41100207 50.10472101c-19.82257471 1.43397372-32.05352527 1.940082-45.63409763 1.6448519-70.34905207-1.60267593-115.98314969-30.91478165-121.38163769-101.64341492-3.45840683-46.05585397 24.7571304-73.13264758 89.24376132-107.96976837 6.7902866-3.66928501 31.37871396-16.57504688 36.06021551-19.06341229 57.69634516-30.83042972 85.15271997-53.73183005 94.76877722-84.47790866 12.77923398-40.78389304-9.10994898-98.94417051-79.24812286-181.6507002-121.17075953-142.97559219-350.14258521-139.60153647-489.2380134 2.06660824-134.49827774 138.84237405-134.79350784 362.12048163-0.42175717 501.637667 158.53842169 168.99799328 451.9968783 181.18676788 534.57688175-11.80919339-4.68150156 0.2952301-10.71262573 0.67481131-18.72600705 1.26527069z" p-id="2509"></path><path d="M346.03865637 637.18588562a78.82636652 78.82636652 0 0 0 78.32025825-79.29029883c0-43.69401562-35.005823-79.29029883-78.32025825-79.29029882a78.82636652 78.82636652 0 0 0-78.36243338 79.29029882c0 43.69401562 35.005823 79.29029883 78.36243338 79.29029883z m0-51.7495729a27.07679361 27.07679361 0 0 1-26.5706845-27.54072593c0-15.30977536 11.97789643-27.54072593 26.5706845-27.54072592 14.55061295 0 26.57068533 12.23095057 26.57068533 27.54072592a27.07679361 27.07679361 0 0 1-26.57068533 27.54072593zM475.7289063 807.11174353a78.82636652 78.82636652 0 0 0 78.3624334-79.29029882c0-43.69401562-34.96364785-79.29029883-78.32025825-79.29029883a78.82636652 78.82636652 0 0 0-78.32025742 79.29029883c0 43.69401562 34.96364785 79.29029883 78.32025742 79.29029882z m0-51.74957208a27.07679361 27.07679361 0 0 1-26.57068532-27.54072674c0-15.30977536 12.06224753-27.54072593 26.57068532-27.54072593 14.59278892 0 26.57068533 12.23095057 26.57068453 27.54072593a27.07679361 27.07679361 0 0 1-26.57068453 27.54072674zM601.24376214 377.21492718a78.82636652 78.82636652 0 0 0 78.32025742-79.29029883c0-43.69401562-34.96364785-79.29029883-78.32025742-79.29029882a78.82636652 78.82636652 0 0 0-78.32025823 79.29029883c0 43.69401562 34.96364785 79.29029883 78.32025824 79.29029883z m1e-8-51.74957208a27.07679361 27.07679361 0 0 1-26.57068534-27.54072675c0-15.30977536 11.97789643-27.54072593 26.57068534-27.54072591 14.55061295 0 26.57068533 12.23095057 26.57068451 27.54072592a27.07679361 27.07679361 0 0 1-26.57068451 27.54072674zM378.80916809 433.85687983a78.82636652 78.82636652 0 0 0 78.32025824-79.29029883c0-43.69401562-34.96364785-79.29029883-78.32025824-79.29029802a78.82636652 78.82636652 0 0 0-78.32025742 79.29029802c0 43.69401562 34.96364785 79.29029883 78.32025742 79.29029883z m0-51.74957209a27.07679361 27.07679361 0 0 1-26.57068451-27.54072674c0-15.30977536 11.97789643-27.54072593 26.57068451-27.54072593 14.55061295 0 26.57068533 12.23095057 26.57068533 27.54072593a27.07679361 27.07679361 0 0 1-26.57068533 27.54072674z" p-id="2510"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/company.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/company.svg
new file mode 100755
index 0000000..7af5327
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/company.svg
@@ -0,0 +1 @@
+<svg t="1675914273096" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2417" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1001.7 969.6H890.4V399.4c0-27.7-17.4-52.8-43.3-62.5L580 236.7c-14.9-5.6-31.1-5.5-45.7-0.4V76.6c0-21.9-10.7-42.4-28.7-54.9s-41-15.3-61.5-7.6L176.9 114.3c-25.9 9.7-43.3 34.9-43.3 62.5v792.8H22.3C10 969.6 0 979.6 0 991.9s10 22.3 22.3 22.3H1001.8c12.3 0 22.3-10 22.3-22.3s-10.1-22.3-22.4-22.3zM178.1 176.8c0-9.2 5.8-17.6 14.4-20.8L459.7 55.8c7-2.6 14.4-1.7 20.5 2.5s9.6 10.9 9.6 18.3v893H178.1V176.8z m356.2 792.8V299.3c0-7.4 3.5-14.1 9.6-18.3 6.1-4.2 13.6-5.2 20.5-2.5l267.1 100.2c8.6 3.2 14.4 11.6 14.4 20.8v570.2H534.3z" p-id="2418"></path><path d="M391.8 346.3H258.2c-12.3 0-22.3 10-22.3 22.3s10 22.3 22.3 22.3h133.6c12.3 0 22.3-10 22.3-22.3s-10-22.3-22.3-22.3zM748 479.9H614.4c-12.3 0-22.3 10-22.3 22.3s10 22.3 22.3 22.3H748c12.3 0 22.3-10 22.3-22.3s-10-22.3-22.3-22.3zM748 613.4H614.4c-12.3 0-22.3 10-22.3 22.3s10 22.3 22.3 22.3H748c12.3 0 22.3-10 22.3-22.3s-10-22.3-22.3-22.3zM391.8 613.4H258.2c-12.3 0-22.3 10-22.3 22.3s10 22.3 22.3 22.3h133.6c12.3 0 22.3-10 22.3-22.3s-10-22.3-22.3-22.3z" p-id="2419"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/component.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/component.svg
new file mode 100755
index 0000000..d5b6b30
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/component.svg
@@ -0,0 +1 @@
+<svg t="1575804206892" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3145" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M826.56 470.016c-32.896 0-64.384 12.288-89.984 35.52l0-104.96c0-62.208-50.496-112.832-112.64-113.088L623.936 287.04 519.552 287.104C541.824 262.72 554.56 230.72 554.56 197.12c0-73.536-59.904-133.44-133.504-133.44-73.472 0-133.376 59.904-133.376 133.44 0 32.896 12.224 64.256 35.52 89.984L175.232 287.104l0 0.576C113.728 288.704 64 338.88 64 400.576l0.32 0 0.32 116.48C60.864 544.896 70.592 577.728 100.8 588.48c12.736 4.608 37.632 7.488 60.864-25.28 12.992-18.368 34.24-29.248 56.64-29.248 38.336 0 69.504 31.104 69.504 69.312 0 38.4-31.168 69.504-69.504 69.504-22.656 0-44.032-11.264-57.344-30.4C138.688 610.112 112.576 615.36 102.464 619.136c-29.824 10.752-39.104 43.776-38.144 67.392l0 160.384L64 846.912C64 909.248 114.752 960 177.216 960l446.272 0c62.4 0 113.152-50.752 113.152-113.152l0-145.024c24.384 22.272 56.384 35.008 89.984 35.008 73.536 0 133.44-59.904 133.44-133.504C960 529.92 900.096 470.016 826.56 470.016zM826.56 672.896c-22.72 0-44.032-11.264-57.344-30.4-22.272-32.384-48.448-27.136-58.56-23.36-29.824 10.752-39.04 43.776-38.08 67.392l0 160.384c0 27.136-22.016 49.152-49.152 49.152L177.216 896.064C150.08 896 128 873.984 128 846.848l0.32 0 0-145.024c24.384 22.272 56.384 35.008 89.984 35.008 73.6 0 133.504-59.904 133.504-133.504 0-73.472-59.904-133.376-133.504-133.376-32.896 0-64.32 12.288-89.984 35.52l0-104.96L128 400.512c0-27.072 22.08-49.152 49.216-49.152L177.216 351.04 334.656 350.72c3.776 0.512 7.616 0.832 11.52 0.832 24.896 0 50.752-10.816 60.032-37.056 4.544-12.736 7.424-37.568-25.344-60.736C362.624 240.768 351.68 219.52 351.68 197.12c0-38.272 31.104-69.44 69.376-69.44 38.336 0 69.504 31.168 69.504 69.44 0 22.72-11.264 44.032-30.528 57.472C427.968 276.736 433.088 302.784 436.8 313.024c10.752 29.888 43.072 39.232 67.392 38.08l119.232 0 0 0.384c27.136 0 49.152 22.08 49.152 49.152l0.256 116.48c-3.776 27.84 6.016 60.736 36.224 71.488 12.736 4.608 37.632 7.488 60.8-25.28 13.056-18.368 34.24-29.248 56.704-29.248C864.832 534.016 896 565.12 896 603.392 896 641.728 864.832 672.896 826.56 672.896z" p-id="3146"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/dashboard.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/dashboard.svg
new file mode 100755
index 0000000..5317d37
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/dashboard.svg
@@ -0,0 +1 @@
+<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/date-range.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/date-range.svg
new file mode 100755
index 0000000..27e9e9e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/date-range.svg
@@ -0,0 +1 @@
+<svg t="1579774833889" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1376" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M887.466667 192.853333h-100.693334V119.466667c0-10.24-6.826667-17.066667-17.066666-17.066667s-17.066667 6.826667-17.066667 17.066667v73.386666H303.786667V119.466667c0-10.24-6.826667-17.066667-17.066667-17.066667s-17.066667 6.826667-17.066667 17.066667v73.386666H168.96c-46.08 0-85.333333 37.546667-85.333333 85.333334V836.266667c0 46.08 37.546667 85.333333 85.333333 85.333333H887.466667c46.08 0 85.333333-37.546667 85.333333-85.333333V278.186667c0-47.786667-37.546667-85.333333-85.333333-85.333334z m-718.506667 34.133334h100.693333v66.56c0 10.24 6.826667 17.066667 17.066667 17.066666s17.066667-6.826667 17.066667-17.066666v-66.56h450.56v66.56c0 10.24 6.826667 17.066667 17.066666 17.066666s17.066667-6.826667 17.066667-17.066666v-66.56H887.466667c27.306667 0 51.2 22.186667 51.2 51.2v88.746666H117.76v-88.746666c0-29.013333 22.186667-51.2 51.2-51.2zM887.466667 887.466667H168.96c-27.306667 0-51.2-22.186667-51.2-51.2V401.066667H938.666667V836.266667c0 27.306667-22.186667 51.2-51.2 51.2z" p-id="1377"></path><path d="M858.453333 493.226667H327.68c-10.24 0-17.066667 6.826667-17.066667 17.066666v114.346667h-116.053333c-10.24 0-17.066667 6.826667-17.066667 17.066667v133.12c0 10.24 6.826667 17.066667 17.066667 17.066666H460.8c10.24 0 17.066667-6.826667 17.066667-17.066666v-114.346667h380.586666c10.24 0 17.066667-6.826667 17.066667-17.066667v-133.12c0-10.24-6.826667-17.066667-17.066667-17.066666z m-413.013333 34.133333v97.28h-98.986667v-97.28h98.986667z m-230.4 131.413333h98.986667v98.986667h-98.986667v-98.986667z m131.413333 97.28v-97.28h98.986667v97.28h-98.986667z m133.12-228.693333h97.28v98.986667h-97.28v-98.986667z m131.413334 0h98.986666v98.986667h-98.986666v-98.986667z m230.4 97.28h-98.986667v-98.986667h98.986667v98.986667z" p-id="1378"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/date.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/date.svg
new file mode 100755
index 0000000..fc1051b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/date.svg
@@ -0,0 +1 @@
+<svg t="1577186573535" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1068" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M479.85714249 608.42857168h64.28571502c19.28571417 0 32.14285751-12.85714249 32.14285664-32.14285751s-12.85714249-32.14285751-32.14285664-32.14285664h-64.28571504c-19.28571417 0-32.14285751 12.85714249-32.14285664 32.14285662s12.85714249 32.14285751 32.14285664 32.14285753z m-2e-8 122.14285665h64.28571504c19.28571417 0 32.14285751-12.85714249 32.14285664-32.14285665s-12.85714249-32.14285751-32.14285664-32.14285751h-64.28571504c-19.28571417 0-32.14285751 12.85714249-32.14285664 32.14285751s12.85714249 32.14285751 32.14285664 32.14285664z m353.57142921-559.28571416h-128.57142921v-32.14285664c0-19.28571417-12.85714249-32.14285751-32.14285664-32.14285753s-32.14285751 12.85714249-32.14285751 32.14285753v32.14285664h-257.14285665v-32.14285664c0-19.28571417-12.85714249-32.14285751-32.14285752-32.14285753s-32.14285751 12.85714249-32.14285664 32.14285753v32.14285664h-128.57142919c-70.71428585 0-128.57142832 57.85714249-128.57142832 122.14285751v501.42857081c0 70.71428585 57.85714249 128.57142832 128.57142832 122.14285751h642.85714335c70.71428585 0 128.57142832-57.85714249 128.57142833-122.14285751v-501.42857081c0-70.71428585-57.85714249-122.14285753-128.57142833-122.14285751z m64.28571415 623.57142832c0 32.14285751-32.14285751 64.28571415-64.28571416 64.28571504h-642.85714335c-32.14285751 0-64.28571415-25.71428583-64.28571417-64.28571504v-372.85714249h771.42857168v372.85714249z m0-437.14285664h-771.42857168v-64.28571417c0-32.14285751 32.14285751-64.28571415 64.28571417-64.28571415h128.57142919v32.14285664c0 19.28571417 12.85714249 32.14285751 32.14285664 32.14285751s32.14285751-12.85714249 32.14285753-32.14285751v-32.14285664h257.14285665v32.14285664c0 19.28571417 12.85714249 32.14285751 32.1428575 32.14285751s32.14285751-12.85714249 32.14285664-32.14285751v-32.14285664h128.57142921c32.14285751 0 64.28571415 25.71428583 64.28571415 64.28571415v64.28571417z m-610.71428583 372.85714247h64.28571415c19.28571417 0 32.14285751-12.85714249 32.14285753-32.14285664s-12.85714249-32.14285751-32.14285753-32.14285751h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285751 32.14285751s12.85714249 32.14285751 32.14285751 32.14285665z m385.71428583-122.14285664h64.28571417c19.28571417 0 32.14285751-12.85714249 32.14285751-32.14285751s-12.85714249-32.14285751-32.14285751-32.14285664h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285753 32.14285664s12.85714249 32.14285751 32.14285753 32.14285751z m-385.71428583 0h64.28571415c19.28571417 0 32.14285751-12.85714249 32.14285753-32.14285751s-12.85714249-32.14285751-32.14285753-32.14285664h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285751 32.14285664s12.85714249 32.14285751 32.14285751 32.14285751z m385.71428583 122.14285665h64.28571417c19.28571417 0 32.14285751-12.85714249 32.14285751-32.14285665s-12.85714249-32.14285751-32.14285751-32.14285751h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285753 32.14285751s12.85714249 32.14285751 32.14285753 32.14285665z" p-id="1069"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/dict.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/dict.svg
new file mode 100755
index 0000000..eacaa5e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/dict.svg
@@ -0,0 +1 @@
+<svg t="1566035680909" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3601" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1002.0848 744.672l-33.568 10.368c0.96 7.264 2.144 14.304 2.144 21.76 0 7.328-1.184 14.432-2.368 21.568l33.792 10.56c7.936 2.24 14.496 7.616 18.336 14.752 3.84 7.328 4.672 15.808 1.952 23.552-5.376 16-23.168 24.672-39.936 19.68l-34.176-10.624c-7.136 12.8-15.776 24.672-26.208 35.2l20.8 27.488a28.96 28.96 0 0 1 5.824 22.816 29.696 29.696 0 0 1-12.704 19.616 32.544 32.544 0 0 1-44.416-6.752l-20.8-27.552c-13.696 6.56-28.192 11.2-43.008 13.888v33.632c0 16.736-14.112 30.432-31.648 30.432-17.6 0-31.872-13.696-31.872-30.432v-33.632a167.616 167.616 0 0 1-42.88-13.888l-20.928 27.552c-10.72 13.76-30.08 16.64-44.288 6.752a29.632 29.632 0 0 1-12.704-19.616 29.28 29.28 0 0 1 5.696-22.816l20.896-27.808a166.72 166.72 0 0 1-27.008-34.688l-33.376 10.432c-16.8 5.184-34.56-3.552-39.936-19.616a29.824 29.824 0 0 1 20.224-38.24l33.472-10.432c-0.8-7.264-2.016-14.304-2.016-21.824 0-7.36 1.184-14.496 2.304-21.632l-33.792-10.368c-16.672-5.376-25.632-22.496-20.224-38.432 5.376-16 23.136-24.672 39.936-19.68l34.016 10.752c7.328-12.672 15.84-24.8 26.336-35.328l-20.8-27.552a29.44 29.44 0 0 1 6.944-42.432 32.704 32.704 0 0 1 44.384 6.752l20.832 27.616c13.696-6.432 28.224-11.2 43.104-13.952v-33.568c0-16.736 14.048-30.432 31.648-30.432 17.536 0 31.808 13.568 31.808 30.432v33.504c15.072 2.688 29.344 7.808 42.848 14.016l20.992-27.616a32.48 32.48 0 0 1 44.224-6.752 29.568 29.568 0 0 1 7.136 42.432l-21.024 27.808c10.432 10.432 19.872 21.888 27.04 34.752l33.376-10.432c16.768-5.12 34.56 3.68 39.936 19.68 5.536 15.936-3.712 33.056-20.32 38.304z m-206.016-74.432c-61.344 0-111.136 47.808-111.136 106.56 0 58.88 49.792 106.496 111.136 106.496 61.312 0 111.104-47.616 111.104-106.496 0-58.752-49.792-106.56-111.104-106.56z" p-id="3602"></path><path d="M802.7888 57.152h-76.448c0-22.08-21.024-38.24-42.848-38.24H39.3968a39.68 39.68 0 0 0-39.36 40.032v795.616s41.888 120.192 110.752 120.192H673.2848a227.488 227.488 0 0 1-107.04-97.44H117.6368s-40.608-13.696-40.608-41.248l470.304-0.256 1.664 3.36a227.68 227.68 0 0 1-12.64-73.632c0-60.576 24-118.624 66.88-161.44a228.352 228.352 0 0 1 123.552-63.392l-3.2 0.288 2.144-424.672h38.208l0.576 421.024c27.04 0 52.672 4.8 76.64 13.344V101.536c0.032 0-6.304-44.384-38.368-44.384zM149.7648 514.336H72.3888v-77.408H149.7648v77.408z m0-144.32H72.3888v-77.44H149.7648v77.44z m0-137.248H72.3888v-77.44H149.7648v77.44z m501.856 281.568H206.0848v-77.408h445.536v77.408z m0-144.32H206.0848v-77.44h445.536v77.44z m0-137.248H206.0848v-77.44h445.536v77.44z" p-id="3603"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/documentation.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/documentation.svg
new file mode 100755
index 0000000..7043122
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/documentation.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M71.984 44.815H115.9L71.984 9.642v35.173zM16.094.05h63.875l47.906 38.37v76.74c0 3.392-1.682 6.645-4.677 9.044-2.995 2.399-7.056 3.746-11.292 3.746H16.094c-4.236 0-8.297-1.347-11.292-3.746-2.995-2.399-4.677-5.652-4.677-9.044V12.84C.125 5.742 7.23.05 16.094.05zm71.86 102.32V89.58h-71.86v12.79h71.86zm23.952-25.58V64H16.094v12.79h95.812z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/download.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/download.svg
new file mode 100755
index 0000000..f58aa25
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/download.svg
@@ -0,0 +1 @@
+<svg t="1569915748289" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3062" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M768.35456 416a256 256 0 1 0-512 0 192 192 0 1 0 0 384v64a256 256 0 0 1-58.88-505.216 320.128 320.128 0 0 1 629.76 0A256.128 256.128 0 0 1 768.35456 864v-64a192 192 0 0 0 0-384z m-512 384h64v64H256.35456v-64z m448 0h64v64h-64v-64z" fill="#333333" p-id="3063"></path><path d="M539.04256 845.248V512.192a32.448 32.448 0 0 0-32-32.192c-17.664 0-32 14.912-32 32.192v333.056l-36.096-36.096a32.192 32.192 0 0 0-45.056 0.192 31.616 31.616 0 0 0-0.192 45.056l90.88 90.944a31.36 31.36 0 0 0 22.528 9.088 30.08 30.08 0 0 0 22.4-9.088l90.88-90.88a32.192 32.192 0 0 0-0.192-45.12 31.616 31.616 0 0 0-45.056-0.192l-36.096 36.096z" fill="#333333" p-id="3064"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/drag.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/drag.svg
new file mode 100755
index 0000000..4185d3c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/drag.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M73.137 29.08h-9.209 29.7L63.886.093 34.373 29.08h20.49v27.035H27.238v17.948h27.625v27.133h18.274V74.063h27.41V56.115h-27.41V29.08zm-9.245 98.827l27.518-26.711H36.59l27.302 26.71zM.042 64.982l27.196 27.029V38.167L.042 64.982zm100.505-26.815V92.01l27.41-27.029-27.41-26.815z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/druid.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/druid.svg
new file mode 100755
index 0000000..d3d9a6a
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/druid.svg
@@ -0,0 +1 @@
+<svg t="1566036347051" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5853" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M832 128H192a64.19 64.19 0 0 0-64 64v640a64.19 64.19 0 0 0 64 64h640a64.19 64.19 0 0 0 64-64V192a64.19 64.19 0 0 0-64-64z m0 703.89l-0.11 0.11H192.11l-0.11-0.11V768h640zM832 544H720L605.6 696.54 442.18 435.07 333.25 544H192v-64h114.75l147.07-147.07L610.4 583.46 688 480h144z m0-288H192v-63.89l0.11-0.11h639.78l0.11 0.11z" p-id="5854"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/edit.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/edit.svg
new file mode 100755
index 0000000..d26101f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/edit.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M106.133 67.2a4.797 4.797 0 0 0-4.8 4.8c0 .187.014.36.027.533h-.027V118.4H9.6V26.667h50.133c2.654 0 4.8-2.147 4.8-4.8 0-2.654-2.146-4.8-4.8-4.8H9.6a9.594 9.594 0 0 0-9.6 9.6V118.4c0 5.307 4.293 9.6 9.6 9.6h91.733c5.307 0 9.6-4.293 9.6-9.6V72.533h-.026c.013-.173.026-.346.026-.533 0-2.653-2.146-4.8-4.8-4.8z"/><path d="M125.16 13.373L114.587 2.8c-3.747-3.747-9.854-3.72-13.6.027l-52.96 52.96a4.264 4.264 0 0 0-.907 1.36L33.813 88.533c-.746 1.76-.226 3.534.907 4.68 1.133 1.147 2.92 1.667 4.693.92l31.4-13.293c.507-.213.96-.52 1.36-.907l52.96-52.96c3.747-3.746 3.774-9.853.027-13.6zM66.107 72.4l-18.32 7.76 7.76-18.32L92.72 24.667l10.56 10.56L66.107 72.4zm52.226-52.227l-8.266 8.267-10.56-10.56 8.266-8.267.027-.026 10.56 10.56-.027.026z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/education.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/education.svg
new file mode 100755
index 0000000..7bfb01d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/education.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M88.883 119.565c-7.284 0-19.434 2.495-21.333 8.25v.127c-4.232.13-5.222 0-7.108 0-1.895-5.76-14.045-8.256-21.333-8.256H0V0h42.523c9.179 0 17.109 5.47 21.47 13.551C68.352 5.475 76.295 0 85.478 0H128v119.57l-39.113-.005h-.004zM60.442 24.763c0-9.651-8.978-16.507-17.777-16.507H7.108V111.43H39.11c7.054-.14 18.177.082 21.333 6.12v-4.628c-.134-5.722-.004-13.522 0-13.832V27.413l.004-2.655-.004.005zm60.442-16.517h-35.55c-8.802 0-17.78 6.856-17.78 16.493v74.259c.004.32.138 8.115 0 13.813v4.627c3.155-6.022 14.279-6.26 21.333-6.114h32V8.25l-.003-.005z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/email.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/email.svg
new file mode 100755
index 0000000..74d25e2
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/email.svg
@@ -0,0 +1 @@
+<svg width="128" height="96" xmlns="http://www.w3.org/2000/svg"><path d="M64.125 56.975L120.188.912A12.476 12.476 0 0 0 115.5 0h-103c-1.588 0-3.113.3-4.513.838l56.138 56.137z"/><path d="M64.125 68.287l-62.3-62.3A12.42 12.42 0 0 0 0 12.5v71C0 90.4 5.6 96 12.5 96h103c6.9 0 12.5-5.6 12.5-12.5v-71a12.47 12.47 0 0 0-1.737-6.35L64.125 68.287z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/example.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/example.svg
new file mode 100755
index 0000000..46f42b5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/example.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/excel.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/excel.svg
new file mode 100755
index 0000000..74d97b8
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/excel.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.208 16.576v8.384h38.72v5.376h-38.72v8.704h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.512h38.72v5.376h-38.72v11.136H128v-94.72H78.208zM0 114.368L72.128 128V0L0 13.632v100.736z"/><path d="M28.672 82.56h-11.2l14.784-23.488-14.08-22.592h11.52l8.192 14.976 8.448-14.976h11.136l-14.08 22.208L58.368 82.56H46.656l-8.768-15.68z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/exit-fullscreen.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/exit-fullscreen.svg
new file mode 100755
index 0000000..485c128
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/exit-fullscreen.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M49.217 41.329l-.136-35.24c-.06-2.715-2.302-4.345-5.022-4.405h-3.65c-2.712-.06-4.866 2.303-4.806 5.016l.152 19.164-24.151-23.79a6.698 6.698 0 0 0-9.499 0 6.76 6.76 0 0 0 0 9.526l23.93 23.713-18.345.074c-2.712-.069-5.228 1.813-5.64 5.02v3.462c.069 2.721 2.31 4.97 5.022 5.03l35.028-.207c.052.005.087.025.133.025l2.457.054a4.626 4.626 0 0 0 3.436-1.38c.88-.874 1.205-2.096 1.169-3.462l-.262-2.465c0-.048.182-.081.182-.136h.002zm52.523 51.212l18.32-.073c2.713.06 5.224-1.609 5.64-4.815v-3.462c-.068-2.722-2.317-4.97-5.021-5.04l-34.58.21c-.053 0-.086-.021-.138-.021l-2.451-.06a4.64 4.64 0 0 0-3.445 1.381c-.885.868-1.201 2.094-1.174 3.46l.27 2.46c.005.06-.177.095-.177.141l.141 34.697c.069 2.713 2.31 4.338 5.022 4.397l3.45.006c2.705.062 4.867-2.31 4.8-5.026l-.153-18.752 24.151 23.946a6.69 6.69 0 0 0 9.494 0 6.747 6.747 0 0 0 0-9.523L101.74 92.54v.001zM48.125 80.662a4.636 4.636 0 0 0-3.437-1.382l-2.457.06c-.05 0-.082.022-.137.022l-35.025-.21c-2.712.07-4.957 2.318-5.022 5.04v3.462c.409 3.206 2.925 4.874 5.633 4.814l18.554.06-24.132 23.928c-2.62 2.626-2.62 6.89 0 9.524a6.694 6.694 0 0 0 9.496 0l24.155-23.79-.155 18.866c-.06 2.722 2.094 5.093 4.801 5.025h3.65c2.72-.069 4.962-1.685 5.022-4.406l.141-34.956c0-.05-.182-.082-.182-.136l.262-2.46c.03-1.366-.286-2.592-1.166-3.46h-.001zM80.08 47.397a4.62 4.62 0 0 0 3.443 1.374l2.45-.054c.055 0 .088-.02.143-.028l35.08.21c2.712-.062 4.953-2.312 5.021-5.033l.009-3.463c-.417-3.211-2.937-5.084-5.64-5.025l-18.615-.073 23.917-23.715c2.63-2.623 2.63-6.879.008-9.513a6.691 6.691 0 0 0-9.494 0L92.251 26.016l.155-19.312c.065-2.713-2.097-5.085-4.802-5.025h-3.45c-2.713.069-4.954 1.693-5.022 4.406l-.139 35.247c0 .054.18.088.18.136l-.267 2.465c-.028 1.366.288 2.588 1.174 3.463v.001z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/eye-open.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/eye-open.svg
new file mode 100755
index 0000000..88dcc98
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/eye-open.svg
@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/eye.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/eye.svg
new file mode 100755
index 0000000..16ed2d8
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/eye.svg
@@ -0,0 +1 @@
+<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/finish.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/finish.svg
new file mode 100755
index 0000000..4685c23
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/finish.svg
@@ -0,0 +1 @@
+<svg t="1716006237008" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12400" width="200" height="200"><path d="M738.826039 1005.166431c-150.226824 0-272.00251-121.916235-272.00251-272.303686 0-150.407529 121.775686-272.323765 272.00251-272.323765 150.206745 0 271.982431 121.916235 271.982432 272.323765 0 150.387451-121.775686 272.303686-271.982432 272.303686z m-0.040157-508.225255c-128.582275 0-232.789333 104.347608-232.789333 233.09051s104.207059 233.110588 232.789333 233.110589c128.562196 0 232.769255-104.367686 232.769255-233.110589 0-128.742902-104.207059-233.09051-232.769255-233.09051z m10.561255 318.243138s-3.694431 3.674353-7.408941 3.674353a18.010353 18.010353 0 0 1-25.941333 0l-74.10949-80.916079a17.66902 17.66902 0 0 1 0-25.740549c7.408941-7.368784 22.246902-7.368784 25.941333 0l63.006118 69.872941 129.686588-117.699764a18.010353 18.010353 0 0 1 25.941333 0 17.709176 17.709176 0 0 1 0 25.760627L749.347137 815.184314zM391.529412 682.666667H190.745098a20.078431 20.078431 0 0 1 0-40.156863h200.784314a20.078431 20.078431 0 1 1 0 40.156863zM170.666667 261.019608a20.078431 20.078431 0 0 1 20.078431-20.078432h481.882353a20.078431 20.078431 0 0 1 0 40.156863H190.745098a20.078431 20.078431 0 0 1-20.078431-20.078431z m341.333333 200.784314H190.745098a20.078431 20.078431 0 0 1 0-40.156863h321.254902a20.078431 20.078431 0 0 1 0 40.156863zM813.176471 120.470588a80.313725 80.313725 0 0 0-80.313726-80.313725H130.509804a80.313725 80.313725 0 0 0-80.313726 80.313725v762.980392a80.313725 80.313725 0 0 0 80.313726 80.313726h366.832941a346.112 346.112 0 0 0 40.417882 40.779294H130.509804a120.470588 120.470588 0 0 1-120.470588-120.470588V120.470588a120.470588 120.470588 0 0 1 120.470588-120.470588h602.352941a120.470588 120.470588 0 0 1 120.470588 120.470588v293.667137a340.188863 340.188863 0 0 0-40.156862-8.533333V120.470588z" fill="currentColor" p-id="12401"></path></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/form.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/form.svg
new file mode 100755
index 0000000..dcbaa18
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/form.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.068 23.784c-1.02 0-1.877-.32-2.572-.96a8.588 8.588 0 0 1-1.738-2.237 11.524 11.524 0 0 1-1.042-2.621c-.232-.895-.348-1.641-.348-2.238V0h.278c.834 0 1.622.085 2.363.256.742.17 1.645.575 2.711 1.214 1.066.64 2.363 1.535 3.892 2.686 1.53 1.15 3.453 2.664 5.77 4.54 2.502 2.045 4.494 3.771 5.977 5.178 1.483 1.406 2.618 2.6 3.406 3.58.787.98 1.274 1.812 1.46 2.494.185.682.277 1.278.277 1.79v2.046H84.068zM127.3 84.01c.278.682.464 1.535.556 2.558.093 1.023-.37 2.003-1.39 2.94-.463.427-.88.832-1.25 1.215-.372.384-.696.704-.974.96a6.69 6.69 0 0 1-.973.767l-11.816-10.741a44.331 44.331 0 0 0 1.877-1.535 31.028 31.028 0 0 1 1.737-1.406c1.112-.938 2.317-1.343 3.615-1.215 1.297.128 2.363.405 3.197.83.927.427 1.923 1.173 2.989 2.239 1.065 1.065 1.876 2.195 2.432 3.388zM78.23 95.902c2.038 0 3.752-.511 5.143-1.534l-26.969 25.83H18.037c-1.761 0-3.684-.47-5.77-1.407a24.549 24.549 0 0 1-5.838-3.709 21.373 21.373 0 0 1-4.518-5.306c-1.204-2.003-1.807-4.07-1.807-6.202V16.495c0-1.79.44-3.665 1.32-5.626A18.41 18.41 0 0 1 5.04 5.562a21.798 21.798 0 0 1 5.213-3.964C12.198.533 14.237 0 16.37 0h53.24v15.984c0 1.62.278 3.367.834 5.242a16.704 16.704 0 0 0 2.572 5.179c1.159 1.577 2.665 2.898 4.518 3.964 1.853 1.066 4.078 1.598 6.673 1.598h20.295v42.325L85.458 92.45c1.02-1.364 1.529-2.856 1.529-4.476 0-2.216-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1c-2.409 0-4.448.789-6.116 2.366-1.668 1.577-2.502 3.474-2.502 5.69 0 2.217.834 4.092 2.502 5.626 1.668 1.535 3.707 2.302 6.117 2.302h52.13zM26.1 47.951c-2.41 0-4.449.789-6.117 2.366-1.668 1.577-2.502 3.473-2.502 5.69 0 2.216.834 4.092 2.502 5.626 1.668 1.534 3.707 2.302 6.117 2.302h52.13c2.409 0 4.47-.768 6.185-2.302 1.715-1.534 2.572-3.41 2.572-5.626 0-2.217-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1zm52.407 64.063l1.807-1.663 3.476-3.196a479.75 479.75 0 0 0 4.587-4.284 500.757 500.757 0 0 1 5.004-4.667c3.985-3.666 8.48-7.758 13.485-12.276l11.677 10.741-13.485 12.404-5.004 4.603-4.587 4.22a179.46 179.46 0 0 0-3.267 3.068c-.88.853-1.367 1.322-1.46 1.407-.463.341-.973.703-1.529 1.087-.556.383-1.112.703-1.668.959-.556.256-1.413.575-2.572.959a83.5 83.5 0 0 1-3.545 1.087 72.2 72.2 0 0 1-3.475.895c-1.112.256-1.946.426-2.502.511-1.112.17-1.854.043-2.224-.383-.371-.426-.464-1.151-.278-2.174.092-.511.278-1.279.556-2.302.278-1.023.602-2.067.973-3.132l1.042-3.005c.325-.938.58-1.577.765-1.918a10.157 10.157 0 0 1 2.224-2.941z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/fullscreen.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/fullscreen.svg
new file mode 100755
index 0000000..0e86b6f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/fullscreen.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M38.47 52L52 38.462l-23.648-23.67L43.209 0H.035L0 43.137l14.757-14.865L38.47 52zm74.773 47.726L89.526 76 76 89.536l23.648 23.672L84.795 128h43.174L128 84.863l-14.757 14.863zM89.538 52l23.668-23.648L128 43.207V.038L84.866 0 99.73 14.76 76 38.472 89.538 52zM38.46 76L14.792 99.651 0 84.794v43.173l43.137.033-14.865-14.757L52 89.53 38.46 76z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/gitee.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/gitee.svg
new file mode 100755
index 0000000..b4c8bfb
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/gitee.svg
@@ -0,0 +1 @@
+<svg t="1686919908144" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2521" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M512 992C246.895625 992 32 777.104375 32 512S246.895625 32 512 32s480 214.895625 480 480-214.895625 480-480 480z m242.9521875-533.3278125h-272.56875a23.7121875 23.7121875 0 0 0-23.71125 23.7121875l-0.024375 59.255625c0 13.08 10.6078125 23.7121875 23.6878125 23.7121875h165.96c13.104375 0 23.7121875 10.6078125 23.7121875 23.6878125v11.855625a71.1121875 71.1121875 0 0 1-71.1121875 71.1121875h-225.215625a23.7121875 23.7121875 0 0 1-23.6878125-23.7121875V423.1278125a71.1121875 71.1121875 0 0 1 71.0878125-71.1121875h331.824375a23.7121875 23.7121875 0 0 0 23.6878125-23.71125l0.0721875-59.2565625a23.7121875 23.7121875 0 0 0-23.68875-23.7121875H423.08a177.76875 177.76875 0 0 0-177.76875 177.7921875V754.953125c0 13.1034375 10.60875 23.7121875 23.713125 23.7121875h349.63125a159.984375 159.984375 0 0 0 159.984375-159.984375V482.36a23.7121875 23.7121875 0 0 0-23.7121875-23.6878125z" fill="#515151" p-id="2522"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/github.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/github.svg
new file mode 100755
index 0000000..7573111
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/github.svg
@@ -0,0 +1 @@
+<svg t="1581238998885" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4187" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M511.542857 14.057143C228.914286 13.942857 0 242.742857 0 525.142857 0 748.457143 143.2 938.285714 342.628571 1008c26.857143 6.742857 22.742857-12.342857 22.742858-25.371429v-88.571428c-155.085714 18.171429-161.371429-84.457143-171.771429-101.6C172.571429 756.571429 122.857143 747.428571 137.714286 730.285714c35.314286-18.171429 71.314286 4.571429 113.028571 66.171429 30.171429 44.685714 89.028571 37.142857 118.857143 29.714286 6.514286-26.857143 20.457143-50.857143 39.657143-69.485715-160.685714-28.8-227.657143-126.857143-227.657143-243.428571 0-56.571429 18.628571-108.571429 55.2-150.514286-23.314286-69.142857 2.171429-128.342857 5.6-137.142857 66.4-5.942857 135.428571 47.542857 140.8 51.771429 37.714286-10.171429 80.8-15.542857 129.028571-15.542858 48.457143 0 91.657143 5.6 129.714286 15.885715 12.914286-9.828571 76.914286-55.771429 138.628572-50.171429 3.314286 8.8 28.228571 66.628571 6.285714 134.857143 37.028571 42.057143 55.885714 94.514286 55.885714 151.2 0 116.8-67.428571 214.971429-228.571428 243.314286a145.714286 145.714286 0 0 1 43.542857 104v128.571428c0.914286 10.285714 0 20.457143 17.142857 20.457143 202.4-68.228571 348.114286-259.428571 348.114286-484.685714 0-282.514286-229.028571-511.2-511.428572-511.2z" p-id="4188"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/guide.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/guide.svg
new file mode 100755
index 0000000..b271001
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/guide.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M1.482 70.131l36.204 16.18 69.932-65.485-61.38 70.594 46.435 18.735c1.119.425 2.397-.17 2.797-1.363v-.085L127.998.047 1.322 65.874c-1.12.597-1.519 1.959-1.04 3.151.32.511.72.937 1.2 1.107zm44.676 57.821L64.22 107.26l-18.062-7.834v28.527z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/icon.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/icon.svg
new file mode 100755
index 0000000..82be8ee
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/icon.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.147.062a13 13 0 0 1 4.94.945c1.55.63 2.907 1.526 4.069 2.688a13.148 13.148 0 0 1 2.761 4.069c.678 1.55 1.017 3.245 1.017 5.086v102.3c0 3.681-1.187 6.733-3.56 9.155-2.373 2.422-5.352 3.633-8.937 3.633H12.992c-3.875 0-7-1.26-9.373-3.779-2.373-2.518-3.56-5.667-3.56-9.445V12.704c0-3.39 1.163-6.345 3.488-8.863C5.872 1.32 8.972.062 12.847.062h102.3zM81.434 109.047c1.744 0 3.003-.412 3.778-1.235.775-.824 1.163-1.914 1.163-3.27 0-1.26-.388-2.325-1.163-3.197-.775-.872-2.034-1.307-3.778-1.307H72.57c.097-.194.145-.485.145-.872V27.09h9.01c1.743 0 2.954-.436 3.633-1.308.678-.872 1.017-1.938 1.017-3.197 0-1.26-.34-2.325-1.017-3.197-.679-.872-1.89-1.308-3.633-1.308H46.268c-1.743 0-2.954.436-3.632 1.308-.678.872-1.018 1.938-1.018 3.197 0 1.26.34 2.325 1.018 3.197.678.872 1.889 1.308 3.632 1.308h8.138v72.075c0 .193.024.339.073.436.048.096.072.242.072.436H46.56c-1.744 0-3.003.435-3.778 1.307-.775.872-1.163 1.938-1.163 3.197 0 1.356.388 2.446 1.163 3.27.775.823 2.034 1.235 3.778 1.235h34.875z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/input.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/input.svg
new file mode 100755
index 0000000..1e49c89
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/input.svg
@@ -0,0 +1 @@
+<svg t="1575802859706" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3102" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 224H128c-35.2 0-64 28.8-64 64v448c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64z m0 480c0 19.2-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V320c0-19.2 12.8-32 32-32h704c19.2 0 32 12.8 32 32v384z" p-id="3103"></path><path d="M224 352c-19.2 0-32 12.8-32 32v256c0 16 12.8 32 32 32s32-12.8 32-32V384c0-16-12.8-32-32-32z" p-id="3104"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/international.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/international.svg
new file mode 100755
index 0000000..e9b56ee
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/international.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M83.287 103.01c-1.57-3.84-6.778-10.414-15.447-19.548-2.327-2.444-2.182-4.306-1.338-9.862v-.64c.553-3.81 1.513-6.05 14.313-8.087 6.516-1.018 8.203 1.57 10.589 5.178l.785 1.193a12.625 12.625 0 0 0 6.43 5.207c1.134.524 2.53 1.164 4.421 2.24 4.596 2.53 4.596 5.41 4.596 11.753v.727a26.91 26.91 0 0 1-5.178 17.454 59.055 59.055 0 0 1-19.025 11.026c3.49-6.546.814-14.313 0-16.553l-.146-.087zM64 5.12a58.502 58.502 0 0 1 25.484 5.818 54.313 54.313 0 0 0-12.859 10.327c-.93 1.28-1.716 2.473-2.472 3.579-2.444 3.694-3.637 5.352-5.818 5.614a25.105 25.105 0 0 1-4.219 0c-4.276-.29-10.094-.64-11.956 4.422-1.193 3.23-1.396 11.956 2.444 16.495.66 1.077.778 2.4.32 3.578a7.01 7.01 0 0 1-2.066 3.229 18.938 18.938 0 0 1-2.909-2.91 18.91 18.91 0 0 0-8.32-6.603c-1.25-.349-2.647-.64-3.985-.93-3.782-.786-8.03-1.688-9.019-3.812a14.895 14.895 0 0 1-.727-5.818 21.935 21.935 0 0 0-1.396-9.25 8.873 8.873 0 0 0-5.557-4.946A58.705 58.705 0 0 1 64 5.12zM0 64c0 35.346 28.654 64 64 64 35.346 0 64-28.654 64-64 0-35.346-28.654-64-64-64C28.654 0 0 28.654 0 64z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/job.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/job.svg
new file mode 100755
index 0000000..9225ffd
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/job.svg
@@ -0,0 +1 @@
+<svg t="1566036191400" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5472" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M934.912 1016.832H192c-14.336 0-25.6-11.264-25.6-25.6v-189.44c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v163.84h691.712V64H217.6v148.48c0 14.336-11.264 25.6-25.6 25.6s-25.6-11.264-25.6-25.6v-174.08c0-14.336 11.264-25.6 25.6-25.6h742.912c14.336 0 25.6 11.264 25.6 25.6v952.832c0 14.336-11.264 25.6-25.6 25.6z" p-id="5473"></path><path d="M232.96 371.2h-117.76c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h117.76c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM232.96 540.16h-117.76c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h117.76c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM232.96 698.88h-117.76c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h117.76c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM574.464 762.88c-134.144 0-243.2-109.056-243.2-243.2S440.32 276.48 574.464 276.48s243.2 109.056 243.2 243.2-109.056 243.2-243.2 243.2z m0-435.2c-105.984 0-192 86.016-192 192S468.48 711.68 574.464 711.68s192-86.016 192-192S680.448 327.68 574.464 327.68z" p-id="5474"></path><path d="M663.04 545.28h-87.04c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h87.04c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6z" p-id="5475"></path><path d="M576 545.28c-14.336 0-25.6-11.264-25.6-25.6v-87.04c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v87.04c0 14.336-11.264 25.6-25.6 25.6z" p-id="5476"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/language.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/language.svg
new file mode 100755
index 0000000..0082b57
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/language.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.742 36.8c2.398 7.2 5.595 12.8 11.19 18.4 4.795-4.8 7.992-11.2 10.39-18.4h-21.58zm-52.748 40h20.78l-10.39-28-10.39 28z"/><path d="M111.916 0H16.009C7.218 0 .025 7.2.025 16v96c0 8.8 7.193 16 15.984 16h95.907c8.791 0 15.984-7.2 15.984-16V16c0-8.8-6.394-16-15.984-16zM72.754 103.2c-1.598 1.6-3.197 1.6-4.795 1.6-.8 0-2.398 0-3.197-.8-.8-.8-1.599 0-1.599-.8s-.799-1.6-1.598-3.2c-.8-1.6-.8-2.4-1.599-4l-3.196-8.8H28.797L25.6 96c-1.598 3.2-2.398 5.6-3.197 7.2-.8 1.6-2.398 1.6-4.795 1.6-1.599 0-3.197-.8-4.796-1.6-1.598-1.6-2.397-2.4-2.397-4 0-.8 0-1.6.799-3.2.8-1.6.8-2.4 1.598-4l17.583-44.8c.8-1.6.8-3.2 1.599-4.8.799-1.6 1.598-3.2 2.397-4 .8-.8 1.599-2.4 3.197-3.2 1.599-.8 3.197-.8 4.796-.8 1.598 0 3.196 0 4.795.8 1.598.8 2.398 1.6 3.197 3.2.799.8 1.598 2.4 2.397 4 .8 1.6 1.599 3.2 2.398 5.6l17.583 44c1.598 3.2 2.398 5.6 2.398 7.2-.8.8-1.599 2.4-2.398 4zM116.711 72c-8.791-3.2-15.185-7.2-20.78-12-5.594 5.6-12.787 9.6-21.579 12l-2.397-4c8.791-2.4 15.984-5.6 21.579-11.2C87.939 51.2 83.144 44 81.545 36h-7.992v-3.2h21.58c-1.6-2.4-3.198-5.6-4.796-8l2.397-.8c1.599 2.4 3.997 5.6 5.595 8.8h19.98v4h-7.992c-2.397 8-6.393 15.2-11.189 20 5.595 4.8 11.988 8.8 20.78 11.2l-3.197 4z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/link.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/link.svg
new file mode 100755
index 0000000..48197ba
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/link.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/list.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/list.svg
new file mode 100755
index 0000000..20259ed
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/list.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M1.585 12.087c0 6.616 3.974 11.98 8.877 11.98 4.902 0 8.877-5.364 8.877-11.98 0-6.616-3.975-11.98-8.877-11.98-4.903 0-8.877 5.364-8.877 11.98zM125.86.107H35.613c-1.268 0-2.114 1.426-2.114 2.852v18.255c0 1.712 1.057 2.853 2.114 2.853h90.247c1.268 0 2.114-1.426 2.114-2.853V2.96c0-1.711-1.057-2.852-2.114-2.852zM.106 62.86c0 6.615 3.974 11.979 8.876 11.979 4.903 0 8.877-5.364 8.877-11.98 0-6.616-3.974-11.98-8.877-11.98-4.902 0-8.876 5.364-8.876 11.98zM124.17 50.88H33.921c-1.268 0-2.114 1.425-2.114 2.851v18.256c0 1.711 1.057 2.852 2.114 2.852h90.247c1.268 0 2.114-1.426 2.114-2.852V53.73c0-1.426-.846-2.852-2.114-2.852zM.106 115.913c0 6.616 3.974 11.98 8.876 11.98 4.903 0 8.877-5.364 8.877-11.98 0-6.616-3.974-11.98-8.877-11.98-4.902 0-8.876 5.364-8.876 11.98zm124.064-11.98H33.921c-1.268 0-2.114 1.426-2.114 2.853v18.255c0 1.711 1.057 2.852 2.114 2.852h90.247c1.268 0 2.114-1.426 2.114-2.852v-18.255c0-1.427-.846-2.853-2.114-2.853z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/lock.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/lock.svg
new file mode 100755
index 0000000..74fee54
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/lock.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M119.88 49.674h-7.987V39.52C111.893 17.738 90.45.08 63.996.08 37.543.08 16.1 17.738 16.1 39.52v10.154H8.113c-4.408 0-7.987 2.94-7.987 6.577v65.13c0 3.637 3.57 6.577 7.987 6.577H119.88c4.407 0 7.987-2.94 7.987-6.577v-65.13c-.008-3.636-3.58-6.577-7.987-6.577zm-23.953 0H32.065V39.52c0-14.524 14.301-26.295 31.931-26.295 17.63 0 31.932 11.777 31.932 26.295v10.153z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/log.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/log.svg
new file mode 100755
index 0000000..f4ef0e0
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/log.svg
@@ -0,0 +1 @@
+<svg t="1566035943711" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4805" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M208.736 566.336H64.384v59.328h144.352v-59.328z m0-336.096H165.44V74.592c0-7.968 4.896-14.848 10.464-14.848h502.016V0.448H175.936c-38.72 1.248-69.248 34.368-68.192 74.144v155.648H64.384V289.6h144.352V230.24z m0 168.096H64.384v59.328h144.352v-59.328z m714.656 76.576h-57.76v474.496c0 7.936-4.896 14.848-10.464 14.848H175.936c-5.568 0-10.464-6.912-10.464-14.848v-155.68h43.296v-59.296H64.384v59.296h43.328v155.68c-1.024 39.776 29.472 72.896 68.192 74.144h679.232c38.72-1.184 69.248-34.368 68.256-74.144V474.912z m14.944-290.336l-83.072-85.312a71.264 71.264 0 0 0-52.544-21.728 71.52 71.52 0 0 0-51.616 23.872L386.528 507.264a30.496 30.496 0 0 0-6.176 10.72L308.16 740.512a30.016 30.016 0 0 0 6.976 30.24c7.712 7.968 19.2 10.752 29.568 7.2l216.544-74.112a28.736 28.736 0 0 0 12.128-7.936L940.448 287.456a75.552 75.552 0 0 0-2.112-102.88z m-557.12 518.272l39.104-120.64 78.336 80.416-117.44 40.224z m170.048-70.016l-103.552-106.016 200.16-222.4 103.52 106.304-200.128 222.112zM897.952 247.072l-0.256 0.224-107.136 119.168-103.52-106.528 106.432-118.624a14.144 14.144 0 0 1 10.304-4.736 13.44 13.44 0 0 1 10.464 4.288l83.264 85.696c5.472 5.6 5.664 14.72 0.448 20.512z" p-id="4806"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/logininfor.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/logininfor.svg
new file mode 100755
index 0000000..3e863d3
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/logininfor.svg
@@ -0,0 +1 @@
+<svg t="1566036016814" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5261" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 128h-85.333333a42.666667 42.666667 0 0 0 0 85.333333h42.666666v640H170.666667V213.333333h42.666666a42.666667 42.666667 0 0 0 0-85.333333H128a42.666667 42.666667 0 0 0-42.666667 42.666667v725.333333a42.666667 42.666667 0 0 0 42.666667 42.666667h768a42.666667 42.666667 0 0 0 42.666667-42.666667V170.666667a42.666667 42.666667 0 0 0-42.666667-42.666667z" p-id="5262"></path><path d="M341.333333 298.666667a42.666667 42.666667 0 0 0 42.666667-42.666667V128a42.666667 42.666667 0 0 0-85.333333 0v128a42.666667 42.666667 0 0 0 42.666666 42.666667zM512 298.666667a42.666667 42.666667 0 0 0 42.666667-42.666667V128a42.666667 42.666667 0 0 0-85.333334 0v128a42.666667 42.666667 0 0 0 42.666667 42.666667zM682.666667 298.666667a42.666667 42.666667 0 0 0 42.666666-42.666667V128a42.666667 42.666667 0 0 0-85.333333 0v128a42.666667 42.666667 0 0 0 42.666667 42.666667zM341.333333 768a42.666667 42.666667 0 0 0 42.666667-42.666667 128 128 0 0 1 256 0 42.666667 42.666667 0 0 0 85.333333 0 213.333333 213.333333 0 0 0-107.52-184.32A128 128 0 0 0 640 469.333333a128 128 0 0 0-256 0 128 128 0 0 0 22.186667 71.68A213.333333 213.333333 0 0 0 298.666667 725.333333a42.666667 42.666667 0 0 0 42.666666 42.666667z m128-298.666667a42.666667 42.666667 0 1 1 42.666667 42.666667 42.666667 42.666667 0 0 1-42.666667-42.666667z" p-id="5263"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/message.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/message.svg
new file mode 100755
index 0000000..14ca817
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/message.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 20.967v59.59c0 11.59 8.537 20.966 19.075 20.966h28.613l1 26.477L76.8 101.523h32.125c10.538 0 19.075-9.377 19.075-20.966v-59.59C128 9.377 119.463 0 108.925 0h-89.85C8.538 0 0 9.377 0 20.967zm82.325 33.1c0-5.524 4.013-9.935 9.037-9.935 5.026 0 9.038 4.41 9.038 9.934 0 5.524-4.025 9.934-9.038 9.934-5.024 0-9.037-4.41-9.037-9.934zm-27.613 0c0-5.524 4.013-9.935 9.038-9.935s9.037 4.41 9.037 9.934c0 5.524-4.025 9.934-9.037 9.934-5.025 0-9.038-4.41-9.038-9.934zm-27.1 0c0-5.524 4.013-9.935 9.038-9.935s9.038 4.41 9.038 9.934c0 5.524-4.026 9.934-9.05 9.934-5.013 0-9.025-4.41-9.025-9.934z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/model.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/model.svg
new file mode 100755
index 0000000..0c57d70
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/model.svg
@@ -0,0 +1 @@
+<svg t="1715953291934" class="icon" viewBox="0 0 1061 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1715" id="mx_n_1715953291935" width="200" height="200"><path d="M447.122465 467.332105L49.240301 268.161564A33.501036 33.501036 0 0 0 0.136043 300.744763v441.020484a33.042117 33.042117 0 0 0 16.06214 27.994016L413.162511 1018.034062a33.959954 33.959954 0 0 0 17.438895 5.50702 33.042117 33.042117 0 0 0 33.042117-33.042118V497.161795a33.042117 33.042117 0 0 0-17.438895-29.82969zM398.018207 931.298504l-331.339011-208.348907v-367.134638l331.339011 162.915996zM1046.010843 263.572381a33.042117 33.042117 0 0 0-31.665363 0L550.838 467.332105a33.042117 33.042117 0 0 0-19.733487 30.288608v493.33717a33.042117 33.042117 0 0 0 49.563176 28.452934l463.048562-265.254776a33.042117 33.042117 0 0 0 16.521059-28.452934V291.566398a33.042117 33.042117 0 0 0-14.685386-27.994017z m-50.939931 441.020484L596.72983 931.298504v-413.026468l397.882163-176.224626zM991.399565 178.672496a33.042117 33.042117 0 0 0-22.486996-29.829689L550.838 1.530034a32.583199 32.583199 0 0 0-19.733487 0L83.659173 158.021173a33.042117 33.042117 0 0 0-4.130264 61.036134l397.882163 199.170541a33.042117 33.042117 0 0 0 14.685386 3.212428 33.959954 33.959954 0 0 0 13.30863 0l463.966399-205.595398a33.042117 33.042117 0 0 0 22.028078-37.172382zM494.391049 349.849021L180.490934 195.193555l358.874108-125.743613 328.126583 112.434982z m0 0" fill="currentColor" p-id="1716"></path></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/money.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/money.svg
new file mode 100755
index 0000000..c1580de
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/money.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.122 127.892v-28.68H7.513V87.274h46.609v-12.4H7.513v-12.86h38.003L.099 0h22.6l32.556 45.07c3.617 5.144 6.44 9.611 8.487 13.385 1.788-3.05 4.89-7.779 9.301-14.186L103.93 0h24.01L82.385 62.013h38.34v12.862h-46.41v12.4h46.41v11.937h-46.41v28.68H54.123z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/monitor.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/monitor.svg
new file mode 100755
index 0000000..c617440
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/monitor.svg
@@ -0,0 +1,2 @@
+<svg t="1543827393750" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4695" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
+</style></defs><path d="M64 64V640H896V64H64zM0 0h960v704H0V0z" p-id="4696"></path><path d="M192 896H768v64H192zM448 640H512v256h-64z" p-id="4697"></path><path d="M479.232 561.604267l309.9904-348.330667-47.803733-42.5472-259.566934 291.669333L303.957333 240.008533 163.208533 438.6048l52.224 37.009067 91.6224-129.28z" p-id="4698"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/my-copy.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/my-copy.svg
new file mode 100755
index 0000000..49f69fa
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/my-copy.svg
@@ -0,0 +1 @@
+<svg t="1716006583362" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="38505" width="200" height="200"><path d="M733.696 666.624l56.32-65.536-15.36-12.8c-41.472-34.816-87.552-61.44-136.192-79.36 75.264-49.152 124.928-134.144 124.928-230.912 0-152.576-123.904-276.48-276.48-276.48-74.24 0-143.872 28.672-195.584 80.384-52.224 51.712-80.896 121.344-80.896 195.584 0 92.16 45.568 174.08 115.2 224.256-81.408 26.624-156.672 74.752-215.552 144.896C34.304 736.768-4.096 850.944 0.512 968.192l1.024 20.48 86.528-4.608-1.024-19.968c-4.096-96.256 27.136-188.928 88.576-261.12 136.704-162.816 380.416-184.32 543.232-48.64l14.848 12.288zM296.96 278.016c0-106.496 83.456-189.952 189.952-189.952 104.96 0 189.952 84.992 189.952 189.952 0 106.496-83.456 189.952-189.952 189.952S296.96 384.512 296.96 278.016z m690.688 522.24H802.304c13.824-16.896 32.256-38.4 55.808-67.072 7.68-8.192 11.776-19.456 10.752-31.744-1.024-11.776-6.144-22.528-15.36-29.696-8.192-7.68-19.456-11.264-31.232-10.752-12.288 1.024-23.04 6.656-30.208 15.872-38.4 45.568-96.256 114.176-101.376 119.808-7.68 7.68-10.752 15.36-13.312 22.528-4.096 8.704-4.096 16.384-4.096 24.064 0 5.632 0 12.8 3.584 23.04 2.56 7.68 6.144 15.872 13.824 23.552l104.96 124.416 4.096 2.048c9.216 4.096 18.432 6.144 26.624 6.144 8.704 0 21.504-4.096 28.672-11.776 8.704-8.704 13.824-19.968 14.336-31.744 0-10.752-3.584-20.48-11.264-28.16l-54.272-63.488h183.296c19.456 0 35.84-18.944 36.352-43.008v-0.512c0.512-25.088-14.848-43.52-35.84-43.52z" fill="currentColor" p-id="38506"></path></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/my-task.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/my-task.svg
new file mode 100755
index 0000000..1f1ea44
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/my-task.svg
@@ -0,0 +1 @@
+<svg t="1715953932254" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11266" width="200" height="200"><path d="M955.59576334 565.84816928C921.71190561 470.40769794 828.48968821 401.08719229 717.79871055 401.08719229c-111.30100029 0-204.91141024 70.09689085-238.29616047 166.36920831h-253.43576475c-26.23088067 0-47.5261393 24.84446618-47.5261329 55.45640711s21.2952586 55.4564006 47.5261329 55.45640054h242.73267482c6.54385326 41.3150179 23.95716606 79.02537397 49.30074631 110.91280755h-292.03342113c-26.23088067 0-47.5261393 24.84446618-47.5261329 55.45640061s21.2952586 55.4564006 47.5261329 55.45640059h214.0617167a376.93717152 376.93717152 0 0 0-33.44021086 110.91280754h-292.47706801a46.36155474 46.36155474 0 0 1-45.91790151-46.8052016V169.1685161c0-25.06629616 21.23980441-45.36333956 47.35977009-45.3633331h63.38666838v55.45640059c0 50.07713168 67.6013528 55.4564006 110.91280122 55.45640061 43.31144833 0 88.50841878 5.76746809 88.5084187-55.45640061v-55.45640059h44.53149358v55.45640059c0 61.22386864 38.15400297 55.4564006 88.84115713 55.45640061 50.74260854 0 88.56387296 5.76746809 88.56387308-55.45640061v-55.45640059h44.19875511v55.45640059c0 61.22386864 38.43128719 55.4564006 89.11844137 55.45640061s110.91280771-2.82827659 110.91280118-55.45640061v-55.45640059h63.44212904a47.69250853 47.69250853 0 0 1 47.74796275 47.63704778l-0.22182994 394.4059385zM407.96379074 345.63079175h-181.95245955c-26.23088067 0-47.5261393 24.84446618-47.52613946 55.45640054s21.2952586 55.4564006 47.52613946 55.45640711h181.95245955c26.23088067 0 47.5261393-24.84446618 47.52613268-55.45640711s-21.2952586-55.4564006-47.52613268-55.45640054z m325.75090957-166.36920816c-30.61193437 0-55.4564006-18.63335139-55.45640712-41.59230208v-83.18460405c0-22.95895071 24.84446618-41.59230206 55.45640712-41.592302s55.4564006 18.63335139 55.4564006 41.592302v83.18460405c0 22.95895071-24.84446618 41.59230206-55.4564006 41.59230208z m-222.71291552 0c-30.61193437 0-55.4009463-18.63335139-55.4009464-41.59230208v-83.18460405c0-22.95895071 24.84446618-41.59230206 55.4009464-41.592302 30.61193437 0 55.4564006 18.63335139 55.45640054 41.592302v83.18460405c-0.0554542 22.95895071-24.84446618 41.59230206-55.45640054 41.59230208z m-220.6610244 0c-30.61193437 0-55.4009463-18.63335139-55.40094634-41.59230208v-83.18460405c0-22.95895071 24.84446618-41.59230206 55.40094634-41.592302 30.61193437 0 55.4564006 18.63335139 55.45640063 41.592302v83.18460405c0 22.95895071-24.84446618 41.59230206-55.45640063 41.59230208z m443.31847922 665.92048622c-91.28124099 0-165.53736215-74.69977454-165.53736218-166.59103817 0-91.83580289 74.2561211-166.59103164 165.53736218-166.59103164s165.53736215 74.75522879 165.53736218 166.59103164-74.2561211 166.59103164-165.53736218 166.59103817z m-115.73750824-29.33644132c32.1647114 24.45627358 71.98241282 39.59587158 115.68205395 39.59587144 44.14329437 0 84.34918863-15.47233629 116.68026924-40.3722568a235.13514545 235.13514545 0 0 1 105.14533952 195.98292729h-443.6512175c0-80.35632762 41.92504052-153.94697186 106.14355479-195.20654193z" fill="currentColor" p-id="11267"></path></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/nested.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/nested.svg
new file mode 100755
index 0000000..06713a8
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/nested.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.002 9.2c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-5.043-3.58-9.132-7.997-9.132S.002 4.157.002 9.2zM31.997.066h95.981V18.33H31.997V.066zm0 45.669c0 5.044 3.58 9.132 7.998 9.132 4.417 0 7.997-4.088 7.997-9.132 0-3.263-1.524-6.278-3.998-7.91-2.475-1.63-5.524-1.63-7.998 0-2.475 1.632-4 4.647-4 7.91zM63.992 36.6h63.986v18.265H63.992V36.6zm-31.995 82.2c0 5.043 3.58 9.132 7.998 9.132 4.417 0 7.997-4.089 7.997-9.132 0-5.044-3.58-9.133-7.997-9.133s-7.998 4.089-7.998 9.133zm31.995-9.131h63.986v18.265H63.992V109.67zm0-27.404c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-3.263-1.524-6.277-3.998-7.909-2.475-1.631-5.524-1.631-7.998 0-2.475 1.632-4 4.646-4 7.91zm31.995-9.13h31.991V91.4H95.987V73.135z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/number.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/number.svg
new file mode 100755
index 0000000..a3f7f79
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/number.svg
@@ -0,0 +1 @@
+<svg t="1575802851180" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2867" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M279.272727 791.272727h512a46.545455 46.545455 0 0 1 0 93.090909H279.272727a46.545455 46.545455 0 0 1 0-93.090909z m33.838546-617.984V651.636364H193.722182V395.170909c0-37.003636-0.884364-59.298909-2.653091-66.746182a24.948364 24.948364 0 0 0-14.615273-16.989091c-8.005818-3.863273-25.786182-5.771636-53.341091-5.771636h-11.822545v-55.854545c57.716364-12.381091 101.562182-37.888 131.490909-76.520728h70.283636z m303.709091 396.8V651.636364H354.164364v-68.235637c77.777455-127.255273 124.043636-206.010182 138.705454-236.218182 14.661818-30.254545 22.016-53.853091 22.016-70.74909 0-13.032727-2.234182-22.714182-6.656-29.137455-4.421818-6.376727-11.170909-9.588364-20.247273-9.588364a22.248727 22.248727 0 0 0-20.200727 10.612364c-4.468364 7.121455-6.656 21.178182-6.656 42.263273v45.521454H354.164364v-17.454545c0-26.763636 1.396364-47.941818 4.142545-63.348364 2.746182-15.499636 9.541818-30.72 20.386909-45.661091 10.798545-14.987636 24.901818-26.298182 42.216727-33.978182 17.361455-7.68 38.167273-11.543273 62.37091-11.543272 47.476364 0 83.316364 11.776 107.706181 35.328 24.296727 23.552 36.445091 53.341091 36.445091 89.367272 0 27.368727-6.842182 56.32-20.48 86.853819-13.730909 30.533818-54.039273 95.325091-121.018182 194.420363h130.885819z m270.615272-189.393454c18.152727 6.097455 31.650909 16.104727 40.494546 29.975272 8.843636 13.917091 13.312 46.452364 13.312 97.652364 0 38.027636-4.328727 67.490909-13.032727 88.529455-8.657455 20.945455-23.598545 36.910545-44.869819 47.848727-21.271273 10.938182-48.593455 16.384-81.873454 16.384-37.794909 0-67.490909-6.330182-89.088-19.083636-21.550545-12.660364-35.746909-28.253091-42.542546-46.638546-6.795636-18.432-10.193455-50.362182-10.193454-95.883636v-37.841455h119.389091v77.730909c0 20.666182 1.210182 33.838545 3.723636 39.424 2.420364 5.585455 7.912727 8.424727 16.337455 8.424728 9.309091 0 15.36-3.537455 18.338909-10.612364 2.932364-7.121455 4.421818-25.6 4.421818-55.575273v-33.047273c0-18.338909-2.048-31.744-6.190546-40.215272a30.72 30.72 0 0 0-18.338909-16.709818c-8.052364-2.653091-23.738182-4.189091-46.964363-4.561455V357.050182c28.392727 0 45.893818-1.070545 52.596363-3.258182a22.946909 22.946909 0 0 0 14.475637-14.149818c2.932364-7.307636 4.421818-18.711273 4.421818-34.257455v-26.624c0-16.756364-1.722182-27.741091-5.12-33.047272-3.490909-5.352727-8.843636-8.005818-16.151273-8.005819-8.285091 0-13.963636 2.792727-16.989091 8.378182-3.025455 5.632-4.561455 17.640727-4.561454 35.933091v39.284364h-119.389091v-40.773818c0-45.661091 10.472727-76.567273 31.325091-92.625455 20.898909-16.058182 54.085818-24.064 99.607272-24.064 56.878545 0 95.511273 11.170909 115.805091 33.373091 20.293818 22.248727 30.394182 53.201455 30.394182 92.765091 0 26.810182-3.630545 46.173091-10.891636 58.088727-7.307636 11.915636-20.107636 22.807273-38.446546 32.628364z" p-id="2868"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/online.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/online.svg
new file mode 100755
index 0000000..f419cc4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/online.svg
@@ -0,0 +1 @@
+<svg t="1568899557259" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="535" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M356.246145 681.56286c-68.156286-41.949414-107.246583-103.84102-107.246583-169.805384 0-65.966411 39.090297-127.860063 107.246583-169.809477 12.046361-7.414877 15.800871-23.190165 8.385994-35.236526-7.413853-12.046361-23.191188-15.801894-35.236526-8.387018-39.640836 24.399713-72.539106 56.044434-95.137801 91.515297-23.86657 37.461193-36.481889 79.620385-36.481889 121.917724 0 42.297338 12.615319 84.454484 36.481889 121.914654 22.598694 35.469839 55.496965 67.11456 95.137801 91.51325 4.185322 2.576685 8.821923 3.804652 13.400195 3.804652 8.598842 0 16.998139-4.329609 21.836331-12.190647C372.047016 704.752002 368.291482 688.976714 356.246145 681.56286zM263.943926 754.580874c-92.603071-61.111846-145.713686-149.623739-145.713686-242.840794 0-93.195565 53.094242-181.682899 145.667637-242.774279 11.805884-7.79043 15.061021-23.677259 7.269567-35.483142-7.79043-11.805884-23.677259-15.062044-35.483142-7.269567C128.487861 296.954249 67.006602 401.024489 67.006602 511.74008c0 110.73708 61.496609 214.830857 168.721703 285.593504 4.343935 2.867304 9.240455 4.238534 14.08274 4.238534 8.317433 0 16.476253-4.046153 21.400403-11.507078C279.003923 778.258133 275.748786 762.372328 263.943926 754.580874zM788.660552 226.213092c-11.80486-7.791453-27.692712-4.536316-35.483142 7.269567-7.79043 11.805884-4.536316 27.692712 7.269567 35.483142 92.575442 61.092403 145.670707 149.579737 145.670707 242.774279 0 93.216032-53.111638 181.727924-145.715733 242.840794-11.805884 7.79043-15.059997 23.678282-7.269567 35.484166 4.925173 7.461949 13.081946 11.507078 21.400403 11.507078 4.841262 0 9.739828-1.37123 14.083763-4.238534 107.22714-70.761624 168.724773-174.857447 168.724773-285.593504C957.341323 401.025513 895.860063 296.955272 788.660552 226.213092zM790.090111 633.67213c23.865547-37.459147 36.480866-79.617315 36.480866-121.914654 0-42.298362-12.615319-84.45653-36.480866-121.917724-22.598694-35.470863-55.496965-67.115584-95.139847-91.515297-12.047384-7.413853-27.821649-3.659343-35.236526 8.387018-7.414877 12.045337-3.659343 27.821649 8.385994 35.236526 68.156286 41.949414 107.247606 103.842043 107.247606 169.809477 0 65.964364-39.090297 127.85597-107.247606 169.804361-12.045337 7.414877-15.800871 23.190165-8.385994 35.237549 4.838192 7.861038 13.236466 12.190647 21.835308 12.190647 4.579295 0 9.215896-1.227967 13.400195-3.804652C734.591099 700.786691 767.490394 669.142993 790.090111 633.67213zM567.129086 518.274914c24.12342-17.150612 39.887452-45.305859 39.887452-77.07133 0-52.128241-42.452881-94.538143-94.634334-94.538143-52.18043 0-94.633311 42.408879-94.633311 94.538143 0 31.695886 15.696494 59.797921 39.730886 76.958766-49.875944 21.128203-84.917018 70.234621-84.917018 127.301338 0 2.366907 0.061398 4.762467 0.182149 7.119141l1.249457 24.296359 276.373515 0 1.238201-24.308639c0.119727-2.358721 0.181125-4.750187 0.181125-7.106862C651.786185 588.497255 616.865861 539.465538 567.129086 518.274914zM512.381182 397.889079c23.937179 0 43.411719 19.430538 43.411719 43.314505 0 23.882943-19.47454 43.313481-43.411719 43.313481-23.936155 0-43.409672-19.430538-43.409672-43.313481C468.971509 417.320641 488.445026 397.889079 512.381182 397.889079zM426.08884 625.656573c9.119705-38.542828 44.254923-67.337641 86.085634-67.337641s76.966952 28.794813 86.085634 67.337641L426.08884 625.656573z" p-id="536"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/password.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/password.svg
new file mode 100755
index 0000000..48785d5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/password.svg
@@ -0,0 +1 @@
+<svg t="1575802846045" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2750" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M868.593046 403.832442c-30.081109-28.844955-70.037123-44.753273-112.624057-44.753273L265.949606 359.079168c-42.554188 0-82.510202 15.908318-112.469538 44.690852-30.236652 28.782533-46.857191 67.222007-46.857191 108.198258l0 294.079782c0 40.977273 16.619516 79.414701 46.702672 108.136859 29.959336 28.844955 70.069869 44.814672 112.624057 44.814672l490.019383 0c42.585911 0 82.696444-15.969717 112.624057-44.814672 30.082132-28.844955 46.579875-67.222007 46.579875-108.136859L915.172921 511.968278C915.171897 471.053426 898.675178 432.677397 868.593046 403.832442zM841.821309 806.049083c0 22.098297-8.882298 42.772152-25.099654 58.306964-16.154935 15.661701-37.81935 24.203238-60.752666 24.203238L265.949606 888.559285c-22.934339 0-44.567032-8.54256-60.877509-24.264637-16.186657-15.474436-25.067932-36.148291-25.067932-58.246589L180.004165 511.968278c0-22.035876 8.881274-42.772152 25.192775-58.307987 16.186657-15.536858 37.81935-24.139793 60.753689-24.139793l490.019383 0c22.933315 0 44.597731 8.602935 60.752666 24.139793 16.21838 15.535835 25.099654 36.272112 25.099654 58.307987L841.822332 806.049083zM510.974136 135.440715c114.914216 0 208.318536 89.75214 208.318536 200.055338l73.350588 0c0-149.113109-126.366036-270.496667-281.669124-270.496667-155.333788 0-281.699824 121.383558-281.699824 270.496667l73.350588 0C302.623877 225.193879 396.059919 135.440715 510.974136 135.440715zM474.299865 747.244792l73.350588 0L547.650453 629.576859l-73.350588 0L474.299865 747.244792z" p-id="2751"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/pdf.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/pdf.svg
new file mode 100755
index 0000000..957aa0c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/pdf.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><path d="M869.073 277.307H657.111V65.344l211.962 211.963zm-238.232 26.27V65.344l-476.498-.054v416.957h714.73v-178.67H630.841zm-335.836 360.57c-5.07-3.064-10.944-5.133-17.61-6.201-6.67-1.064-13.603-1.6-20.81-1.6h-48.821v85.641h48.822c7.206 0 14.14-.532 20.81-1.6 6.665-1.065 12.54-3.133 17.609-6.202 5.064-3.063 9.134-7.406 12.208-13.007 3.065-5.602 4.6-12.937 4.6-22.011 0-9.07-1.535-16.408-4.6-22.01-3.074-5.603-7.144-9.94-12.208-13.01zM35.82 541.805v416.904h952.358V541.805H35.821zm331.421 191.179c-3.6 11.071-9.343 20.879-17.209 29.413-7.874 8.542-18.078 15.408-30.617 20.61-12.544 5.206-27.747 7.807-45.621 7.807h-66.036v102.45h-62.831V607.517h128.867c17.874 0 33.077 2.6 45.62 7.802 12.541 5.207 22.745 12.076 30.618 20.615 7.866 8.538 13.604 18.277 17.21 29.212 3.6 10.943 5.401 22.278 5.401 34.018 0 11.477-1.8 22.752-5.402 33.819zM644.9 806.417c-5.343 17.61-13.408 32.818-24.212 45.627-10.807 12.803-24.283 22.879-40.423 30.213-16.146 7.343-35.155 11.007-57.03 11.007h-123.26V607.518h123.26c18.41 0 35.552 2.941 51.428 8.808 15.873 5.869 29.618 14.671 41.22 26.412 11.608 11.744 20.674 26.411 27.217 44.02 6.535 17.61 9.803 38.288 9.803 62.035 0 20.81-2.67 40.02-8.003 57.624zm245.362-146.07h-138.07v66.03h119.66v48.829h-119.66v118.058h-62.83V607.518h200.9v52.829h-.001zm-318.2 25.611c-6.402-8.266-14.877-14.604-25.412-19.01-10.544-4.402-23.551-6.602-39.019-6.602h-44.825v180.088h56.029c9.07 0 17.872-1.463 26.415-4.401 8.535-2.932 16.14-7.802 22.812-14.609 6.665-6.8 12.007-15.667 16.007-26.61 4.003-10.94 6.003-24.275 6.003-40.021 0-14.408-1.4-27.416-4.202-39.019-2.8-11.607-7.406-21.542-13.808-29.816zm0 0"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/people.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/people.svg
new file mode 100755
index 0000000..2bd54ae
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/people.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M104.185 95.254c8.161 7.574 13.145 17.441 13.145 28.28 0 1.508-.098 2.998-.285 4.466h-10.784c.238-1.465.403-2.948.403-4.465 0-8.983-4.36-17.115-11.419-23.216C86 104.66 75.355 107.162 64 107.162c-11.344 0-21.98-2.495-31.22-6.83-7.064 6.099-11.444 14.218-11.444 23.203 0 1.517.165 3 .403 4.465H10.955a35.444 35.444 0 0 1-.285-4.465c0-10.838 4.974-20.713 13.127-28.291C9.294 85.42.003 70.417.003 53.58.003 23.99 28.656.001 64 .001s63.997 23.988 63.997 53.58c0 16.842-9.299 31.85-23.812 41.673zM64 36.867c-29.454 0-53.33-10.077-53.33 15.342 0 25.418 23.876 46.023 53.33 46.023 29.454 0 53.33-20.605 53.33-46.023 0-25.419-23.876-15.342-53.33-15.342zm24.888 25.644c-3.927 0-7.111-2.665-7.111-5.953 0-3.288 3.184-5.954 7.11-5.954 3.928 0 7.111 2.666 7.111 5.954s-3.183 5.953-7.11 5.953zm-3.556 16.372c0 4.11-9.55 7.442-21.332 7.442-11.781 0-21.332-3.332-21.332-7.442 0-1.06.656-2.064 1.8-2.976 3.295 2.626 10.79 4.465 19.532 4.465 8.743 0 16.237-1.84 19.531-4.465 1.145.912 1.801 1.916 1.801 2.976zm-46.22-16.372c-3.927 0-7.11-2.665-7.11-5.953 0-3.288 3.183-5.954 7.11-5.954 3.927 0 7.111 2.666 7.111 5.954s-3.184 5.953-7.11 5.953z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/peoples.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/peoples.svg
new file mode 100755
index 0000000..739e953
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/peoples.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M95.648 118.762c0 5.035-3.563 9.121-7.979 9.121H7.98c-4.416 0-7.979-4.086-7.979-9.121C0 100.519 15.408 83.47 31.152 76.75c-9.099-6.43-15.216-17.863-15.216-30.987v-9.128c0-20.16 14.293-36.518 31.893-36.518s31.894 16.358 31.894 36.518v9.122c0 13.137-6.123 24.556-15.216 30.993 15.738 6.726 31.141 23.769 31.141 42.012z"/><path d="M106.032 118.252h15.867c3.376 0 6.101-3.125 6.101-6.972 0-13.957-11.787-26.984-23.819-32.123 6.955-4.919 11.638-13.66 11.638-23.704v-6.985c0-15.416-10.928-27.926-24.39-27.926-1.674 0-3.306.193-4.89.561 1.936 4.713 3.018 9.974 3.018 15.526v9.121c0 13.137-3.056 23.111-11.066 30.993 14.842 4.41 27.312 23.42 27.541 41.509z"/></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/phone.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/phone.svg
new file mode 100755
index 0000000..5396034
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/phone.svg
@@ -0,0 +1 @@
+<svg t="1567417214476" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2266" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M761.503029 2.90619 242.121921 2.90619c-32.405037 0-58.932204 26.060539-58.932204 58.527998l0 902.302287c0 32.156374 26.217105 58.216913 58.932204 58.216913l519.381108 0c32.344662 0 58.591443-26.060539 58.591443-58.216913L820.094472 61.123103C820.094472 28.966729 793.847691 2.90619 761.503029 2.90619M452.878996 61.123103l98.147344 0c6.780427 0 12.31549 5.536087 12.31549 12.253068 0 6.748704-5.535063 12.253068-12.31549 12.253068l-98.147344 0c-6.779404 0-12.345166-5.504364-12.345166-12.253068C440.532807 66.659189 446.099592 61.123103 452.878996 61.123103M501.641583 980.593398c-29.636994 0-53.987588-23.946388-53.987588-53.677527 0-29.356608 24.039509-53.614082 53.987588-53.614082 29.91738 0 53.987588 23.883967 53.987588 53.614082C555.629171 956.647009 531.559986 980.593398 501.641583 980.593398M766.35657 803.142893c0 16.23373-13.186324 29.107945-29.233811 29.107945l-470.618521 0c-16.35755 0-29.325909-13.186324-29.325909-29.107945L237.178329 163.500794c0-16.232706 13.279445-29.138644 29.325909-29.138644l470.246037 0c16.420995 0 29.357632 13.1853 29.357632 29.138644l0 639.642099L766.35657 803.142893zM766.35657 803.142893" p-id="2267"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/post.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/post.svg
new file mode 100755
index 0000000..69db31b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/post.svg
@@ -0,0 +1 @@
+<svg t="1566035724641" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3998" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M136.4 434.3h77.7c21.5 0 38.9-17.4 38.9-38.9s-17.4-38.9-38.9-38.9h-77.7c-21.5 0-38.9 17.4-38.9 38.9s17.4 38.9 38.9 38.9zM252.9 628.6c0-21.5-17.4-38.9-38.9-38.9h-77.7c-21.5 0-38.9 17.4-38.9 38.9s17.4 38.9 38.9 38.9H214c21.5-0.1 38.9-17.5 38.9-38.9z" p-id="3999"></path><path d="M874.7 97.5H227c-28.6 0-51.8 23.2-51.8 51.8v194.3h38.9c28.6 0 51.8 23.2 51.8 51.8 0 28.6-23.2 51.8-51.8 51.8h-38.9v129.5h38.9c28.6 0 51.8 23.2 51.8 51.8 0 28.6-23.2 51.8-51.8 51.8h-38.9v194.3c0 28.6 23.2 51.8 51.8 51.8h647.7c28.6 0 51.8-23.2 51.8-51.8V149.3c0-28.6-23.2-51.8-51.8-51.8z m-311.3 723c-15.6 0-146.7-71.6-146.7-91 0-19.4 102-368.6 102-368.6l-83.6-104s-12.3-23.1 24.6-23.1h208.9c36.9 0 18.4 23.1 18.4 23.1l-79 104s102 351.3 102 368.6c0.1 17.3-131 91-146.6 91z m169.2-253.6l-27.9 40.2-74.5-240 103.4 171.7c4.6 7.9 4.2 20.6-1 28.1z" p-id="4000"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/process-definition.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/process-definition.svg
new file mode 100755
index 0000000..202d200
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/process-definition.svg
@@ -0,0 +1 @@
+<svg t="1716005059256" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3268" width="200" height="200"><path d="M497.798958 952.225272A345.95419 345.95419 0 0 1 359.273733 892.652247a171.541601 171.541601 0 0 0 7.177473-47.37132A179.436821 179.436821 0 0 0 211.417793 665.126359a345.95419 345.95419 0 0 1 185.896546-380.40606 179.436821 179.436821 0 0 0 317.244299 0 351.696169 351.696169 0 0 1 143.549456 128.476763 35.169617 35.169617 0 0 0 30.145386 16.508188 37.322859 37.322859 0 0 0 19.379177-5.024231 36.605111 36.605111 0 0 0 10.766209-46.653574 424.188644 424.188644 0 0 0-183.743304-160.775391v-13.637198a180.872315 180.872315 0 1 0-358.873642 0 129.194511 129.194511 0 0 0 0 15.79044A423.470897 423.470897 0 0 0 132.465591 600.529103a467.253481 467.253481 0 0 0 6.459726 71.774728A180.154568 180.154568 0 0 0 187.014385 1024a178.001326 178.001326 0 0 0 139.96072-68.185992 430.64837 430.64837 0 0 0 158.62215 62.444014h6.459725a35.887364 35.887364 0 0 0 5.741978-71.774729z m57.419783-861.29674a109.097587 109.097587 0 1 1-108.37984 110.533082A109.097587 109.097587 0 0 1 555.218741 90.928532zM187.014385 952.225272a109.097587 109.097587 0 1 1 108.37984-108.37984A109.097587 109.097587 0 0 1 187.014385 952.225272zM933.471559 617.755038l-104.791103-71.774728a35.887364 35.887364 0 0 0-48.089068 8.612967L560.242972 858.918125a37.322859 37.322859 0 0 0-6.459725 24.403408l9.330714 104.073356a35.169617 35.169617 0 0 0 14.354946 25.838902 38.758353 38.758353 0 0 0 21.532418 7.177473h7.177473l98.331378-21.532419a38.758353 38.758353 0 0 0 22.250166-14.354946L945.673263 665.126359a36.605111 36.605111 0 0 0 5.741978-27.274397 37.322859 37.322859 0 0 0-17.943682-20.096924zM675.800285 930.692853l-45.218079 9.330715-4.306484-49.524563 193.791766-262.695505 45.218079 29.427638z m311.50232-399.067489l-103.355608-66.750497a35.887364 35.887364 0 0 0-49.524563 10.048462 35.169617 35.169617 0 0 0 10.766209 49.524562L947.826505 593.35163a34.45187 34.45187 0 0 0 20.096924 5.741979 37.322859 37.322859 0 0 0 30.145386-15.790441 36.605111 36.605111 0 0 0-10.76621-51.677804z" fill="currentColor" p-id="3269"></path></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/qq.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/qq.svg
new file mode 100755
index 0000000..ee13d4e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/qq.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M18.448 57.545l-.244-.744-.198-.968-.132-.53v-2.181l.236-.859.24-.908.317-.953.428-1.06.561-1.103.794-1.104v-.773l.077-.724.123-.984.34-1.106.313-1.194.25-.548.289-.511.371-.569.405-.423v-2.73l.234-1.407.236-1.633.42-1.955.577-2.035.43-1.118.426-1.217.468-1.135.559-1.216.57-1.332.655-1.247.737-1.331.929-1.33.43-.762.457-.624.995-1.406 1.025-1.403 1.163-1.444 1.246-1.405 1.352-1.384 1.41-1.423 1.708-1.536 1.083-.934 1.322-1.008 1.34-.89 1.448-.855 1.392-.76 1.57-.63 1.667-.775 1.657-.532 1.653-.552 1.787-.548 1.785-.417 1.876-.347L59.128.68l1.879-.245 1.876-.252 2.002-.106h5.912l1.97.243 1.981.231 2.019.207 1.874.441 1.979.413 1.857.475 2.035.53 1.862.646 1.782.738 1.904.78 1.736.853 1.689.95 1.655 1.044 1.425.971.662.548.693.401 1.323 1.1 1.115 1.064 1.112 1.1 1.083 1.214.894 1.178 1.064 1.217.74 1.306.752 1.162.798 1.352.661 1.175 1.113 2.489.546 1.286.428 1.192.428 1.294.384 1.217.267 1.047.347 1.231.607 2.198.388 1.924.253 1.861.217 1.497.342 2.28.077.362.274.41.737 1.18.473.8.42.832.534.892.472 1.07.307 1.093.334 1.2.252 1.232.115.605.106.746v.648l-.106.643v.8l-.192.774-.35 1.5-.403.76-.299.852v.213l.142.264.4.623 1.746 2.53 1.377 1.9.66 1.267.889 1.389.774 1.52.893 1.627.894 1.828 1.006 2.069.567 1.268.518 1.239.447 1.307.44 1.175.336 1.235.342 1.16.432 2.261.343 2.31.235 2.05v2.891l-.158 1.025-.226 1.768-.308 1.59-.48 1.44-.18.588-.336.707-.28.493-.375.607-.33.383-.42.494-.375.4-.401.34-.48.207-.432.207-.355.114h-.543l-.346-.114-.66-.32-.302-.212-.317-.223-.347-.304-.35-.342-.579-.63-.684-.89-.539-.917-.538-.734-.526-.855-.741-1.517-.833-1.579-.098-.055h-.138l-.338.247-.196.415-.326.516-.567 1.533-.856 2.182-1.096 2.626-.824 1.308-.864 1.366-1.027 1.536-1.09 1.503-.557.68-.676.743-1.555 1.497.136.135.21.214.777.446 3.235 1.524 1.41.779 1.347.756 1.332.953 1.187.982.574.443.432.511.445.593.367.643.198.533.242.64.105.554.115.647-.115.433v.44l-.105.454-.242.415-.092.325-.22.394-.587.784-.543.627-.42.47-.35.348-.893.638-1.01.556-1.077.532-1.155.511-1.287.495-.693.207-.608.167-1.496.342-1.545.325-1.552.323-1.689.27-1.74.072-1.785.21h-5.539l-1.998-.114-1.86-.168-2.005-.27-1.99-.209-2.095-.286-2.03-.495-1.981-.374-1.968-.552-2.019-.707-1.98-.585-1.044-.342-.927-.323-.586-.223-.582-.12h-1.647l-1.904-.131-.962-.096-1.24-.135-.795.705-1.085.665-1.471.701-1.628.875-.99.475-1.033.376-2.281.914-1.24.305-1.3.343-1.803.344-1.13.086-1.193.1-1.246.135-1.45.053h-5.926l-3.346-.053-3.25-.321-1.644-.23-1.589-.23-1.546-.227-1.547-.305-1.442-.456-1.434-.325-1.294-.51-1.223-.474-1.142-.533-.99-.583-.984-.71-.336-.343-.44-.415-.334-.362-.3-.417-.278-.415-.215-.42-.311-.89-.109-.46-.138-.51v-.473l.138-.533v-.53l.109-.53v-1.069l.052-.564.259-.647.215-.646.39-.779.286-.3.236-.348.615-.738.49-.38.464-.266.428-.338.676-.21.543-.324.676-.341.77-.227.775-.231.897-.192.85-.11 1.008-.13 1.093-.081.284-.092h.063l.137-.115v-.13l-.2-.266-.58-.27-1.45-1.231-.975-.761-1.127-.967-1.136-1.082-1.181-1.382-1.36-1.558-.508-.843-.672-.87-.58-1.007-.522-1.1-.704-1.047-.459-1.194-.547-1.192-.546-1.33-.397-1.273-.378-1.575-.112-.057h-.115l-.059-.113h-.14l-.23.113-.114.057-.158.264-.057.321-.119.286-.206.477-.664 1.157-.345.701-.546.612-.58.736-.641.816-.677.724-.795.701-.734.658-.814.524-.89.546-.855.325-1.008.247-.99.095h-.233l-.228-.095-.18-.384-.29-.188-.38-.912-.237-.493-.255-.707-.21-.734-.113-.724-.313-1.648-.12-.972v-3.185l.12-2.379.196-1.214.23-1.252.21-1.347.374-1.254.42-1.443.431-1.407.578-1.448.545-1.38.754-1.4.699-1.52.855-1.425 1.006-1.538 1.023-1.382 1.069-1.538.891-1.071 1.142-1.227 1.202-1.237.56-.59.678-.662.985-.836 1.012-.853 1.647-1.446 1.242-.889z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/question.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/question.svg
new file mode 100755
index 0000000..a1446f6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/question.svg
@@ -0,0 +1 @@
+<svg t="1581238842264" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1409" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 0C229.233778 0 0 229.233778 0 512s229.233778 512 512 512 512-229.233778 512-512A512 512 0 0 0 512 0z m0 938.666667C276.366222 938.666667 85.333333 747.633778 85.333333 512 85.333333 276.366222 276.366222 85.333333 512 85.333333c235.633778 0 426.666667 191.032889 426.666667 426.666667a426.666667 426.666667 0 0 1-426.666667 426.666667z m0-717.653334a170.666667 170.666667 0 0 0-170.666667 170.666667 42.666667 42.666667 0 0 0 85.333334 0 85.333333 85.333333 0 1 1 85.333333 85.333333 42.666667 42.666667 0 0 0-42.666667 42.666667v111.36a42.666667 42.666667 0 0 0 85.333334 0v-74.24A170.666667 170.666667 0 0 0 512 221.013333z m-42.666667 542.293334a42.666667 42.666667 0 1 0 85.333334 0 42.666667 42.666667 0 0 0-85.333334 0z" p-id="1410"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/radio.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/radio.svg
new file mode 100755
index 0000000..96d2af5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/radio.svg
@@ -0,0 +1 @@
+<svg t="1575966775973" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="879" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M507.39346659 71.84873358c241.53533667 0 437.39770766 195.85422109 437.39770767 437.37442191 0 241.53766571-195.86237099 437.38955776-437.39770767 437.38955776-241.50040803 0-437.34997219-195.85189205-437.34997219-437.38955776C70.0434944 267.70295467 265.89189347 71.84873358 507.39346659 71.84873358L507.39346659 71.84873358zM507.39346659 282.81899805c-125.00686734 0-226.37039389 101.38914133-226.37039388 226.41813048 0 125.01268821 101.36352768 226.39717262 226.37039388 226.39717262 125.04295993 0 226.42395136-101.38448441 226.42395136-226.39717262C733.81625401 384.20813938 632.43642653 282.81899805 507.39346659 282.81899805L507.39346659 282.81899805zM507.39346659 120.78172615c-214.46664192 0-388.42047261 173.95150279-388.4204726 388.44026539 0 214.51204949 173.95499463 388.46122325 388.4204726 388.46122325 214.52369237 0 388.46005817-173.94800981 388.46005818-388.46122325C895.85236082 294.73322894 721.91715897 120.78172615 507.39346659 120.78172615z" p-id="880"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/rate.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/rate.svg
new file mode 100755
index 0000000..dd3c680
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/rate.svg
@@ -0,0 +1 @@
+<svg t="1577246781606" class="icon" viewBox="0 0 1069 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1098" xmlns:xlink="http://www.w3.org/1999/xlink" width="84.5595703125" height="81"><defs><style type="text/css"></style></defs><path d="M633.72929961 378.02038203l9.49872568 18.68789795 20.78025469 2.79745225 206.61592412 27.33248408a11.46496817 11.46496817 0 0 1 6.6095543 19.47324902l-147.2675168 147.35350284-14.89299345 14.89299345 3.8006376 20.68280244 37.84585956 204.89044571a11.46496817 11.46496817 0 0 1-16.4808914 12.2961788L554.68980898 751.84713388l-18.68789794-9.49299345-18.48726123 9.99171915-183.23885392 99.34968163a11.46496817 11.46496817 0 0 1-16.78471347-11.8662416l32.5433127-205.79617881 3.29617793-20.78598692-15.19108243-14.49172002-151.03375839-143.48407587a11.46496817 11.46496817 0 0 1 6.09936328-19.63949062l205.79617881-32.63503185 20.78598691-3.2961788L428.87898125 380.72038203 518.59235674 192.64331182a11.46496817 11.46496817 0 0 1 20.56815264-0.26369385l94.56879023 185.63503183zM496.64840732 85.52038203l-121.75796162 254.98089229L95.76433145 384.76178369A34.3949045 34.3949045 0 0 0 77.46050938 443.66879023l204.87324901 194.66369385-44.16879023 279.1146498a34.3949045 34.3949045 0 0 0 50.36560489 35.61592325l248.4-134.67898038 251.84522285 128.27579591a34.3949045 34.3949045 0 0 0 49.43694287-36.89426777l-51.30573223-277.85350284 199.73120977-199.90891758a34.3949045 34.3949045 0 0 0-19.82866201-58.40827998l-280.11783428-37.03184736L558.32993633 84.71210205a34.3949045 34.3949045 0 0 0-61.68152901 0.80254775z" p-id="1099"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/redis-list.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/redis-list.svg
new file mode 100755
index 0000000..ff6da89
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/redis-list.svg
@@ -0,0 +1,2 @@
+<svg t="1656035183065" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3395" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
+</style></defs><path d="M958.88 730.06H65.12c-18.28 0-33.12-14.82-33.12-33.12V68.91c0-18.29 14.83-33.12 33.12-33.12h893.77c18.28 0 33.12 14.82 33.12 33.12v628.03c-0.01 18.3-14.84 33.12-33.13 33.12zM98.23 663.83h827.53v-561.8H98.23v561.8z" p-id="3396"></path><path d="M512 954.55c-18.28 0-33.12-14.82-33.12-33.12V733.92c0-18.29 14.83-33.12 33.12-33.12s33.12 14.82 33.12 33.12v187.51c0 18.3-14.84 33.12-33.12 33.12z" p-id="3397"></path><path d="M762.01 988.21H261.99c-18.28 0-33.12-14.82-33.12-33.12 0-18.29 14.83-33.12 33.12-33.12h500.03c18.28 0 33.12 14.82 33.12 33.12-0.01 18.29-14.84 33.12-33.13 33.12zM514.74 578.55c-21.63 0-43.31-3.87-64.21-11.65-45.95-17.13-82.49-51.13-102.86-95.74-5.07-11.08-0.19-24.19 10.89-29.26 11.08-5.09 24.19-0.18 29.26 10.91 15.5 33.88 43.25 59.7 78.14 72.71 34.93 12.99 72.79 11.64 106.66-3.85 33.22-15.17 58.8-42.26 72.03-76.3 4.42-11.37 17.21-17.01 28.57-12.58 11.36 4.42 16.99 17.22 12.57 28.58-17.42 44.82-51.1 80.5-94.82 100.47-24.34 11.12-50.25 16.71-76.23 16.71z" p-id="3398"></path><path d="M325.27 528.78c-1.66 0-3.34-0.18-5.02-0.57-11.88-2.77-19.28-14.63-16.49-26.51l18.84-81c1.34-5.82 5-10.84 10.13-13.92 5.09-3.09 11.3-3.96 17.03-2.41l80.51 21.43c11.79 3.14 18.8 15.23 15.67 27.02-3.15 11.79-15.42 18.75-27.02 15.65l-58.49-15.57-13.69 58.81c-2.37 10.2-11.45 17.07-21.47 17.07zM360.8 351.01c-2.65 0-5.37-0.49-8-1.51-11.36-4.41-16.99-17.21-12.59-28.57 17.4-44.79 51.06-80.47 94.8-100.48 92.15-42.06 201.25-1.39 243.31 90.68 5.07 11.08 0.19 24.19-10.89 29.26-11.13 5.07-24.19 0.17-29.26-10.91-31.97-69.91-114.9-100.82-184.79-68.86-33.22 15.19-58.8 42.28-71.99 76.29-3.41 8.74-11.75 14.1-20.59 14.1z" p-id="3399"></path><path d="M684.68 376.74c-1.47 0-2.95-0.15-4.42-0.44l-81.61-16.68c-11.94-2.45-19.64-14.11-17.21-26.06 2.44-11.96 14.1-19.64 26.04-17.22l59.29 12.12 10.23-59.5c2.05-12 13.52-20.19 25.48-18.01 12.03 2.06 20.09 13.48 18.02 25.5l-14.08 81.96a22.089 22.089 0 0 1-9.29 14.49c-3.7 2.51-8.03 3.84-12.45 3.84z" p-id="3400"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/redis.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/redis.svg
new file mode 100755
index 0000000..e35ade3
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/redis.svg
@@ -0,0 +1 @@
+<svg t="1605865043777" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="856" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1023.786667 611.84c-0.426667 9.770667-13.354667 20.693333-39.893334 34.56-54.613333 28.458667-337.749333 144.896-397.994666 176.298667-60.288 31.402667-93.738667 31.104-141.354667 8.32-47.616-22.741333-348.842667-144.469333-403.114667-170.368-27.093333-12.970667-40.917333-23.893333-41.386666-34.218667v103.509333c0 10.325333 14.250667 21.290667 41.386666 34.261334 54.272 25.941333 355.541333 147.626667 403.114667 170.368 47.616 22.784 81.066667 23.082667 141.354667-8.362667 60.245333-31.402667 343.338667-147.797333 397.994666-176.298667 27.776-14.464 40.106667-25.728 40.106667-35.925333v-102.058667l-0.213333-0.085333z m0-168.746667c-0.512 9.770667-13.397333 20.650667-39.893334 34.517334-54.613333 28.458667-337.749333 144.896-397.994666 176.298666-60.288 31.402667-93.738667 31.104-141.354667 8.362667-47.616-22.741333-348.842667-144.469333-403.114667-170.410667-27.093333-12.928-40.917333-23.893333-41.386666-34.176v103.509334c0 10.325333 14.250667 21.248 41.386666 34.218666 54.272 25.941333 355.498667 147.626667 403.114667 170.368 47.616 22.784 81.066667 23.082667 141.354667-8.32 60.245333-31.402667 343.338667-147.84 397.994666-176.298666 27.776-14.506667 40.106667-25.770667 40.106667-35.968v-102.058667l-0.256-0.042667z m0-175.018666c0.469333-10.410667-13.141333-19.541333-40.533334-29.610667-53.248-19.498667-334.634667-131.498667-388.522666-151.253333-53.888-19.712-75.818667-18.901333-139.093334 3.84C392.234667 113.706667 92.629333 231.253333 39.338667 252.074667c-26.666667 10.496-39.68 20.181333-39.253334 30.506666V386.133333c0 10.325333 14.250667 21.248 41.386667 34.218667 54.272 25.941333 355.498667 147.669333 403.114667 170.410667 47.616 22.741333 81.066667 23.04 141.354666-8.362667 60.245333-31.402667 343.338667-147.84 397.994667-176.298667 27.776-14.506667 40.106667-25.770667 40.106667-35.968V268.074667h-0.341334zM366.677333 366.08l237.269334-36.437333-71.68 105.088-165.546667-68.650667z m524.8-94.634667l-140.330666 55.466667-15.232 5.973333-140.245334-55.466666 155.392-61.44 140.373334 55.466666z m-411.989333-101.674666l-22.954667-42.325334 71.594667 27.989334 67.498667-22.101334-18.261334 43.733334 68.778667 25.770666-88.704 9.216-19.882667 47.786667-32.085333-53.290667-102.4-9.216 76.416-27.562666z m-176.768 59.733333c70.058667 0 126.805333 21.973333 126.805333 49.109333s-56.746667 49.152-126.805333 49.152-126.848-22.058667-126.848-49.152c0-27.136 56.789333-49.152 126.848-49.152z" p-id="857"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/row.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/row.svg
new file mode 100755
index 0000000..0dc8d53
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/row.svg
@@ -0,0 +1 @@
+<svg t="1579339929870" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1182" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M152 854.856875h325.7146875V237.715625H134.856875v600q0 6.99375 5.0746875 12.0684375T152 854.856875z m737.143125-17.1421875v-600H546.284375v617.1421875H872q6.99375 0 12.0684375-5.07375t5.0746875-12.0684375z m68.5715625-651.429375V837.715625q0 35.3821875-25.16625 60.5484375T872 923.4284375H152q-35.383125 0-60.5484375-25.1653125T66.284375 837.7146875V186.284375q0-35.3821875 25.16625-60.5484375T152 100.5715625h720q35.383125 0 60.5484375 25.1653125t25.16625 60.5484375z" p-id="1183"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/search.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/search.svg
new file mode 100755
index 0000000..84233dd
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/search.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M124.884 109.812L94.256 79.166c-.357-.357-.757-.629-1.129-.914a50.366 50.366 0 0 0 8.186-27.59C101.327 22.689 78.656 0 50.67 0 22.685 0 0 22.688 0 50.663c0 27.989 22.685 50.663 50.656 50.663 10.186 0 19.643-3.03 27.6-8.201.286.385.557.771.9 1.114l30.628 30.632a10.633 10.633 0 0 0 7.543 3.129c2.728 0 5.457-1.043 7.543-3.115 4.171-4.157 4.171-10.915.014-15.073M50.671 85.338C31.557 85.338 16 69.78 16 50.663c0-19.102 15.557-34.661 34.67-34.661 19.115 0 34.657 15.559 34.657 34.675 0 19.102-15.557 34.661-34.656 34.661"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/select.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/select.svg
new file mode 100755
index 0000000..a5b9d00
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/select.svg
@@ -0,0 +1 @@
+<svg t="1575803481213" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="804" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M62 511.97954521C62 263.86590869 263.90681826 62 511.97954521 62s449.97954521 201.825 449.97954521 449.97954521c0 248.19545479-201.90681826 449.97954521-449.97954521 449.97954521C263.90681826 962 62 760.175 62 511.97954521M901.98636348 511.97954521c0-215.24318174-175.00909131-390.41590869-390.00681827-390.41590869-215.03863652 0-389.96590869 175.17272695-389.96590868 390.41590869 0 215.28409131 175.00909131 390.45681826 389.96590868 390.45681826C727.01818174 902.47727305 901.98636348 727.30454521 901.98636348 511.97954521M264.17272695 430.28409131c0-5.76818174 2.12727305-11.51590869 6.64772696-15.87272696 8.71363652-8.75454521 22.88863652-8.75454521 31.725 0l209.4340913 208.22727305L721.45454521 414.53409131c8.75454521-8.71363652 22.97045479-8.71363652 31.90909132 0 8.71363652 8.75454521 8.71363652 22.88863652 0 31.60227304L511.97954521 685.74090869 270.71818174 446.01363653C266.27954521 441.77954521 264.17272695 436.05227305 264.17272695 430.28409131" p-id="805"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/server.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/server.svg
new file mode 100755
index 0000000..8411165
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/server.svg
@@ -0,0 +1 @@
+<svg t="1547360688278" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M890 120H134a70 70 0 0 0-70 70v500a70 70 0 0 0 70 70h756a70 70 0 0 0 70-70V190a70 70 0 0 0-70-70z m-10 520a40 40 0 0 1-40 40H712V448a40 40 0 0 0-80 0v232h-80V368a40 40 0 0 0-80 0v312h-80V512a40 40 0 0 0-80 0v168H184a40 40 0 0 1-40-40V240a40 40 0 0 1 40-40h656a40 40 0 0 1 40 40zM696 824H328a40 40 0 0 0 0 80h368a40 40 0 0 0 0-80z" p-id="6718"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/shopping.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/shopping.svg
new file mode 100755
index 0000000..87513e7
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/shopping.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M42.913 101.36c1.642 0 3.198.332 4.667.996a12.28 12.28 0 0 1 3.89 2.772c1.123 1.184 1.987 2.582 2.592 4.193.605 1.612.908 3.318.908 5.118 0 1.8-.303 3.507-.908 5.118-.605 1.611-1.469 3.01-2.593 4.194a13.3 13.3 0 0 1-3.889 2.843 10.582 10.582 0 0 1-4.667 1.066c-1.729 0-3.306-.355-4.732-1.066a13.604 13.604 0 0 1-3.825-2.843c-1.123-1.185-1.988-2.583-2.593-4.194a14.437 14.437 0 0 1-.907-5.118c0-1.8.302-3.506.907-5.118.605-1.61 1.47-3.009 2.593-4.193a12.515 12.515 0 0 1 3.825-2.772c1.426-.664 3.003-.996 4.732-.996zm53.932.285c1.643 0 3.22.331 4.733.995a11.386 11.386 0 0 1 3.889 2.772c1.08 1.185 1.945 2.583 2.593 4.194.648 1.61.972 3.317.972 5.118 0 1.8-.324 3.506-.972 5.117-.648 1.611-1.513 3.01-2.593 4.194a12.253 12.253 0 0 1-3.89 2.843 11 11 0 0 1-4.732 1.066 10.58 10.58 0 0 1-4.667-1.066 12.478 12.478 0 0 1-3.824-2.843c-1.08-1.185-1.945-2.583-2.593-4.194a13.581 13.581 0 0 1-.973-5.117c0-1.801.325-3.507.973-5.118.648-1.611 1.512-3.01 2.593-4.194a11.559 11.559 0 0 1 3.824-2.772 11.212 11.212 0 0 1 4.667-.995zm21.781-80.747c2.42 0 4.3.355 5.64 1.066 1.34.71 2.29 1.587 2.852 2.63a6.427 6.427 0 0 1 .778 3.34c-.044 1.185-.195 2.204-.454 3.057-.26.853-.8 2.606-1.62 5.26a589.268 589.268 0 0 1-2.788 8.743 1236.373 1236.373 0 0 0-3.047 9.453c-.994 3.128-1.75 5.592-2.269 7.393-1.123 3.79-2.55 6.42-4.278 7.89-1.728 1.469-3.846 2.203-6.352 2.203H39.023l1.945 12.795h65.342c4.148 0 6.223 1.943 6.223 5.828 0 1.896-.41 3.53-1.232 4.905-.821 1.374-2.442 2.061-4.862 2.061H38.505c-1.729 0-3.176-.426-4.343-1.28-1.167-.852-2.14-1.966-2.917-3.34a21.277 21.277 0 0 1-1.88-4.478 44.128 44.128 0 0 1-1.102-4.55c-.087-.568-.324-1.942-.713-4.122-.39-2.18-.865-4.904-1.426-8.174l-1.88-10.947c-.692-4.027-1.383-8.079-2.075-12.154-1.642-9.572-3.5-20.234-5.574-31.986H6.87c-1.296 0-2.377-.356-3.24-1.067a9.024 9.024 0 0 1-2.14-2.558 10.416 10.416 0 0 1-1.167-3.2C.108 8.53 0 7.488 0 6.54c0-1.896.583-3.46 1.75-4.69C2.917.615 4.494 0 6.482 0h13.095c1.728 0 3.111.284 4.148.853 1.037.569 1.858 1.28 2.463 2.132a8.548 8.548 0 0 1 1.297 2.701c.26.948.475 1.754.648 2.417.173.758.346 1.825.519 3.199.173 1.374.345 2.772.518 4.193.26 1.706.519 3.507.778 5.403h88.678z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/size.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/size.svg
new file mode 100755
index 0000000..ddb25b8
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/size.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h54.796v18.286H36.531V128H18.265V73.143H0V54.857zm127.857-36.571H91.935V128H72.456V18.286H36.534V0h91.326l-.003 18.286z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/skill.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/skill.svg
new file mode 100755
index 0000000..a3b7312
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/skill.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M31.652 93.206h33.401c1.44 2.418 3.077 4.663 4.93 6.692h-38.33v-6.692zm0-10.586h28.914a44.8 44.8 0 0 1-1.264-6.688h-27.65v6.688zm0-17.27H59.39c.288-2.286.714-4.532 1.34-6.687H31.65v6.687h.003zm53.913 44.84v5.85c0 2.798-2.095 5.075-4.667 5.075h-70.07c-2.576 0-4.663-2.277-4.663-5.075V31.26l23.22-20.96v22.25H17.16v6.688h18.39V6.688h45.348c2.576 0 4.667 2.277 4.667 5.066v20.009c1.987-.675 4.053-1.128 6.17-1.445v-18.56C91.738 5.28 86.874 0 80.902 0H31.15L0 28.118v87.917c0 6.48 4.859 11.759 10.832 11.759h70.07c5.974 0 10.837-5.27 10.837-11.759v-4.41c-2.117-.312-4.183-.765-6.17-1.435h-.004zM23.279 58.667h-7.96v6.688h7.96v-6.688zm-7.956 41.23h7.96v-6.691h-7.96v6.692zm7.956-23.96h-7.96v6.687h7.96v-6.688zm89.718-15.042l-4.896-4.07-12.447 17.613-11.19-9.305-3.762 5.311 16.091 13.38 16.204-22.929zM128 70.978c0-18.632-13.97-33.782-31.147-33.782-17.168 0-31.135 15.155-31.135 33.782 0 18.628 13.97 33.783 31.135 33.783 17.172 0 31.143-15.15 31.143-33.783H128zm-6.17 0c0 14.933-11.203 27.1-24.981 27.1-13.77 0-24.987-12.158-24.987-27.1 0-14.941 11.195-27.099 24.987-27.099 13.778 0 24.982 12.158 24.982 27.1z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/slider.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/slider.svg
new file mode 100755
index 0000000..8b33fb0
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/slider.svg
@@ -0,0 +1 @@
+<svg t="1577185310368" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1238" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M951.453125 476.84375H523.671875a131.8359375 131.8359375 0 0 0-254.1796875 0H72.546875v70.3125h196.9453125a131.8359375 131.8359375 0 0 0 254.1796875 0H951.453125z" p-id="1239"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/star.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/star.svg
new file mode 100755
index 0000000..6cf86e6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/star.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M70.66 4.328l14.01 29.693c1.088 2.29 3.177 3.882 5.603 4.25l31.347 4.76c6.087.926 8.528 8.756 4.117 13.247L103.05 79.395c-1.75 1.78-2.544 4.352-2.132 6.867l5.352 32.641c1.043 6.337-5.33 11.182-10.778 8.19l-28.039-15.409a7.13 7.13 0 0 0-6.91 0l-28.039 15.41c-5.448 2.99-11.821-1.854-10.777-8.19l5.352-32.642c.415-2.515-.387-5.088-2.136-6.867L2.264 56.278C-2.146 51.787.286 43.957 6.38 43.031l31.343-4.76c2.419-.368 4.51-1.96 5.595-4.25L57.334 4.328c2.728-5.77 10.605-5.77 13.325 0z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/swagger.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/swagger.svg
new file mode 100755
index 0000000..895e387
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/swagger.svg
@@ -0,0 +1 @@
+<svg t="1566036776944" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6463" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M64 223.995345h168.001164v47.997673c0 26.428509 18.878836 47.997673 41.984 47.997673h140.036654c23.095855 0 41.984-21.569164 41.984-47.997673v-47.997673h504.003491a32.004655 32.004655 0 0 0 0-64.009309H455.996509V111.988364c0-26.428509-18.878836-47.997673-41.984-47.997673H273.985164c-23.095855 0-41.984 21.569164-41.984 47.997673v47.997672H64a32.004655 32.004655 0 0 0 0 64.009309zM288.004655 128h111.997672V256H288.004655V128zM960 479.995345H791.998836v-47.997672c0-26.372655-18.878836-47.997673-41.984-47.997673H609.978182c-23.095855 0-41.984 21.634327-41.984 47.997673v47.997672H64a32.004655 32.004655 0 0 0 0 64.00931h504.003491v47.997672c0 26.363345 18.878836 47.997673 41.984 47.997673h140.036654c23.095855 0 41.984-21.634327 41.984-47.997673v-47.997672h168.001164a32.004655 32.004655 0 1 0-0.009309-64.00931zM735.995345 576H623.997673v-128h111.997672v128zM960 800.293236v-0.288581H455.996509v-47.997673c0-26.363345-18.878836-47.997673-41.984-47.997673H274.050327c-23.105164 0-41.984 21.634327-41.984 47.997673v47.997673H64v0.288581a32.004655 32.004655 0 0 0 0 64.009309c0.986764 0 1.917673-0.195491 2.885818-0.288581h165.115346v47.997672c0 26.363345 18.878836 47.997673 41.984 47.997673h140.036654c23.095855 0 41.984-21.634327 41.984-47.997673v-47.997672h501.108364c0.968145 0.093091 1.899055 0.288582 2.895127 0.288581a32.004655 32.004655 0 1 0-0.009309-64.009309zM400.002327 896H288.004655V768h111.997672v128z" fill="" p-id="6464"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/switch.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/switch.svg
new file mode 100755
index 0000000..d545532
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/switch.svg
@@ -0,0 +1 @@
+<svg t="1576042673958" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1110" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M692 792H332c-150 0-270-120-270-270s120-270 270-270h360c150 0 270 120 270 270 0 147-120 270-270 270zM332 312c-117 0-210 93-210 210s93 210 210 210h360c117 0 210-93 210-210s-93-210-210-210H332z" p-id="1111"></path><path d="M341 522m-150 0a150 150 0 1 0 300 0 150 150 0 1 0-300 0Z" p-id="1112"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/system.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/system.svg
new file mode 100755
index 0000000..6d8d34c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/system.svg
@@ -0,0 +1,2 @@
+<svg t="1543827724451" class="icon" style="" viewBox="0 0 1084 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10233" xmlns:xlink="http://www.w3.org/1999/xlink" width="211.71875" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
+</style></defs><path d="M1080.09609 434.500756c-4.216302-23.731757-26.9241-47.945376-50.595623-53.185637l-17.648235-4.095836a175.940257 175.940257 0 0 1-101.612877-80.832531 177.807476 177.807476 0 0 1-18.732427-129.801867l5.541425-16.684509c7.10748-23.129428-2.108151-54.992624-20.599646-70.833873 0 0-16.624276-14.094495-63.244529-41.199293-46.800951-26.984332-66.858502-34.513443-66.858502-34.513443-22.76803-8.372371-54.631227-0.361397-71.255503 17.407304l-12.287509 13.251234a173.470708 173.470708 0 0 1-120.465769 48.065842A174.13327 174.13327 0 0 1 421.329029 33.590675L409.583617 20.761071C393.140039 2.99237 361.096144-4.898138 338.267881 3.353767c0 0-20.358715 7.529111-67.099434 34.513443-46.800951 27.34573-63.244529 41.440225-63.244529 41.440225-18.431263 15.66055-27.646894 47.222582-20.539413 70.592941l5.059562 16.865207a178.048407 178.048407 0 0 1-18.672194 129.621169 174.916297 174.916297 0 0 1-102.275439 81.073463l-17.045906 3.854904c-23.310126 5.42096-46.258856 29.333415-50.595623 53.185637 0 0-3.854905 21.382674-3.854905 75.712737 0 54.330062 3.854905 75.712736 3.854905 75.712736 4.216302 23.972688 26.9241 47.945376 50.595623 53.185637l16.624276 3.854905a174.253736 174.253736 0 0 1 102.395904 81.314394c23.310126 40.837896 28.911785 87.337683 18.732427 129.801867l-4.81863 16.443578c-7.10748 23.129428 2.108151 54.992624 20.599646 70.833872 0 0 16.624276 14.094495 63.244529 41.199293 46.800951 27.104798 66.918735 34.513443 66.918735 34.513443 22.707798 8.372371 54.631227 0.361397 71.255503-17.407303l11.624947-12.588673a175.096996 175.096996 0 0 1 242.256662 0.120465l11.624947 12.648906c16.383345 17.708468 48.427239 25.598976 71.255503 17.347071 0 0 20.358715-7.529111 67.159666-34.513443 46.740719-27.104798 63.124063-41.199293 63.124064-41.199293 18.491496-15.600317 27.707127-47.463513 20.599646-70.833873l-5.059562-17.106139a176.723284 176.723284 0 0 1 18.672194-129.139305 176.060722 176.060722 0 0 1 102.395904-81.314394l16.68451-3.854905c23.310126-5.42096 46.258856-29.333415 50.595623-53.185637 0 0 3.854905-21.382674 3.854904-75.712737-0.240932-54.330062-4.095836-75.833202-4.095836-75.833202z m-537.819428 293.334149c-119.261112 0-216.175824-97.336342-216.175824-217.621412a216.657687 216.657687 0 0 1 216.236057-217.320249c119.200879 0 216.115591 97.276109 216.11559 217.56118-0.240932 120.044139-96.974945 217.320248-216.175823 217.320249z" p-id="10234"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/tab.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/tab.svg
new file mode 100755
index 0000000..b4b48e4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/tab.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.921.052H49.08c-1.865 0-3.198 1.599-3.198 3.464v6.661c0 1.865 1.6 3.464 3.198 3.464h29.84c1.865 0 3.198-1.599 3.198-3.464V3.516C82.385 1.65 80.786.052 78.92.052zm45.563 0H94.642c-1.865 0-3.464 1.599-3.464 3.464v6.661c0 1.865 1.599 3.464 3.464 3.464h29.842c1.865-.266 3.464-1.599 3.464-3.464V3.516c0-1.865-1.599-3.464-3.464-3.464zm0 22.382H40.02c-1.866 0-3.464-1.599-3.464-3.464V3.516c0-1.865-1.599-3.464-3.464-3.464H3.516C1.65.052.052 1.651.052 3.516V124.75c0 1.598 1.599 3.197 3.464 3.197h120.968c1.865 0 3.464-1.599 3.464-3.464V25.898c0-1.865-1.599-3.464-3.464-3.464z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/table.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/table.svg
new file mode 100755
index 0000000..0e3dc9d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/table.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/textarea.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/textarea.svg
new file mode 100755
index 0000000..5e5acdf
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/textarea.svg
@@ -0,0 +1 @@
+<svg t="1575802855098" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2984" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 160H128c-35.2 0-64 28.8-64 64v576c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64z m0 608c0 16-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V256c0-16 12.8-32 32-32h704c19.2 0 32 12.8 32 32v512z" p-id="2985"></path><path d="M224 288c-19.2 0-32 12.8-32 32v256c0 16 12.8 32 32 32s32-12.8 32-32V320c0-16-12.8-32-32-32z m608 480c19.2 0 32-12.8 32-32V608L704 768h128z" p-id="2986"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/theme.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/theme.svg
new file mode 100755
index 0000000..5982a2f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/theme.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M125.5 36.984L95.336 2.83C93.735 1.018 91.565 0 89.3 0c-2.263 0-4.433 1.018-6.033 2.83l-3.786 4.286c-1.6 1.812-3.77 2.83-6.032 2.831H54.553c-2.263 0-4.434-1.018-6.033-2.83L44.734 2.83C43.134 1.018 40.964 0 38.701 0c-2.263 0-4.434 1.018-6.034 2.83L2.5 36.984C.9 38.796 0 41.254 0 43.815c0 2.562.899 5.02 2.5 6.831L14.565 64.31c2.178 2.468 5.367 3.403 8.33 2.444 1.35-.435 2.709.592 2.709 2.18v49.407c0 5.313 3.84 9.66 8.532 9.66h59.726c4.693 0 8.532-4.347 8.532-9.66V68.934c0-1.59 1.36-2.616 2.71-2.181 2.962.96 6.15.024 8.329-2.444L125.5 50.646c1.6-1.811 2.499-4.269 2.499-6.83 0-2.563-.899-5.02-2.5-6.832z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/time-range.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/time-range.svg
new file mode 100755
index 0000000..ea756ea
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/time-range.svg
@@ -0,0 +1 @@
+<svg t="1579774825624" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1248" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M498.595712 482.290351 345.420077 482.290351l0 57.307194 210.477712 0L555.897789 274.196942l-57.301054 0L498.596735 482.290351zM498.595712 482.290351" p-id="1249"></path><path d="M577.685002 644.98478l379.879913 0 0 57.302077L577.685002 702.286858 577.685002 644.98478 577.685002 644.98478zM577.685002 644.98478" p-id="1250"></path><path d="M577.685002 773.764795l379.879913 0 0 57.307194L577.685002 831.071989 577.685002 773.764795 577.685002 773.764795zM577.685002 773.764795" p-id="1251"></path><path d="M577.685002 902.549927l379.879913 0 0 57.307194L577.685002 959.857121 577.685002 902.549927 577.685002 902.549927zM577.685002 902.549927" p-id="1252"></path><path d="M102.523001 382.290823c4.450359 2.615571 9.470699 3.954055 14.530948 3.954055 2.969635 0 5.952572-0.461511 8.836249-1.394766l190.809767-61.886489c15.052834-4.882194 23.297612-21.040199 18.415418-36.08894-4.882194-15.052834-21.040199-23.297612-36.093033-18.415418L175.676092 308.458257c15.994276-26.115797 35.170011-50.537 57.370639-72.743768 73.767074-73.767074 171.845857-114.388237 276.16783-114.388237 104.32095 0 202.39564 40.622186 276.16169 114.388237s114.393353 171.845857 114.393353 276.16783c0 26.427906-2.615571 52.449559-7.709589 77.780481l58.302871 0c4.464685-25.499767 6.708795-51.470255 6.708795-77.780481 0-60.449767-11.845793-119.102608-35.204803-174.336584-22.559808-53.334719-54.850236-101.226472-95.968725-142.349055-41.122583-41.122583-89.017406-73.408917-142.348032-95.968725C628.317169 75.866898 569.659211 64.021106 509.215584 64.021106c-60.448744 0-119.106702 11.845793-174.336584 35.207873-53.334719 22.559808-101.230566 54.846142-142.349055 95.968725-23.980157 23.980157-44.934398 50.278103-62.727647 78.601172l-20.738323-105.655342c-3.043313-15.527648-18.105357-25.642007-33.631982-22.599717-15.527648 3.048429-25.64303 18.105357-22.599717 33.637098l36.102243 183.932126C90.51348 371.153158 95.460142 378.13313 102.523001 382.290823L102.523001 382.290823zM102.523001 382.290823" p-id="1253"></path><path d="M126.020158 587.9416 67.768453 587.9416c5.759167 33.679054 15.368012 66.544579 28.789697 98.278327 22.559808 53.333696 54.850236 101.225449 95.971795 142.348032 41.122583 41.122583 89.014336 73.408917 142.349055 95.968725 54.112432 22.88829 111.517863 34.71157 170.668031 35.18229L505.547031 902.395408c-102.94972-0.941442-199.594851-41.445948-272.499277-114.349351C177.545672 732.543975 140.810003 663.275355 126.020158 587.9416L126.020158 587.9416zM126.020158 587.9416" p-id="1254"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/time.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/time.svg
new file mode 100755
index 0000000..1c302c7
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/time.svg
@@ -0,0 +1 @@
+<svg t="1577099827399" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1008" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M520 559h204c17.673 0 32 14.327 32 32 0 17.673-14.327 32-32 32H488c-17.673 0-32-14.327-32-32 0-0.167 0.001-0.334 0.004-0.5a32.65 32.65 0 0 1-0.004-0.5V277c0-17.673 14.327-32 32-32 17.673 0 32 14.327 32 32v282z m-8 401C264.576 960 64 759.424 64 512S264.576 64 512 64s448 200.576 448 448-200.576 448-448 448z m0-64c212.077 0 384-171.923 384-384S724.077 128 512 128 128 299.923 128 512s171.923 384 384 384z" p-id="1009"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/tool.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/tool.svg
new file mode 100755
index 0000000..c8f143c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/tool.svg
@@ -0,0 +1 @@
+<svg t="1553828490559" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1684" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M898.831744 900.517641 103.816972 900.517641c-36.002982 0-65.363683-29.286-65.363683-65.313541l0-554.949184c0-36.041868 29.361725-65.326844 65.363683-65.326844l795.015795 0c36.002982 0 65.198931 29.284977 65.198931 65.326844l0 554.949184C964.030675 871.231641 934.834726 900.517641 898.831744 900.517641L898.831744 900.517641zM103.816972 255.593236c-13.576203 0-24.711821 11.085476-24.711821 24.662703l0 554.949184c0 13.576203 11.136641 24.662703 24.711821 24.662703l795.015795 0c13.577227 0 24.547069-11.086499 24.547069-24.662703l0-554.949184c0-13.577227-10.970866-24.662703-24.547069-24.662703L103.816972 255.593236 103.816972 255.593236zM664.346245 251.774257c-11.161201 0-20.332071-9.080819-20.332071-20.332071l0-101.278661c0-13.576203-11.047614-24.623817-24.699542-24.623817L383.181611 105.539708c-13.576203 0-24.712845 11.04659-24.712845 24.623817l0 101.278661c0 11.252275-9.041934 20.332071-20.332071 20.332071-11.20111 0-20.319791-9.080819-20.319791-20.332071l0-101.278661c0-35.989679 29.323862-65.275679 65.364707-65.275679l236.133022 0c36.06745 0 65.402569 29.284977 65.402569 65.275679l0 101.278661C684.717202 242.694461 675.636383 251.774257 664.346245 251.774257L664.346245 251.774257zM413.233044 521.725502 75.694471 521.725502c-11.163247 0-20.333094-9.117658-20.333094-20.35663 0-11.252275 9.169847-20.332071 20.333094-20.332071l337.538573 0c11.277858 0 20.319791 9.080819 20.319791 20.332071C433.552835 512.607844 424.510902 521.725502 413.233044 521.725502L413.233044 521.725502zM912.894018 521.725502 575.367725 521.725502c-11.213389 0-20.332071-9.117658-20.332071-20.35663 0-11.252275 9.118682-20.332071 20.332071-20.332071l337.526293 0c11.290137 0 20.332071 9.080819 20.332071 20.332071C933.226089 512.607844 924.184155 521.725502 912.894018 521.725502L912.894018 521.725502zM557.56322 634.217552 445.085496 634.217552c-11.213389 0-20.332071-9.079796-20.332071-20.331048l0-168.763658c0-11.251252 9.118682-20.332071 20.332071-20.332071l112.478747 0c11.290137 0 20.370956 9.080819 20.370956 20.332071l0 168.763658C577.934177 625.137757 568.853357 634.217552 557.56322 634.217552L557.56322 634.217552zM465.417567 593.514525l71.827909 0L537.245476 465.454918l-71.827909 0L465.417567 593.514525 465.417567 593.514525z" p-id="1685"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/tree-table.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/tree-table.svg
new file mode 100755
index 0000000..8aafdb8
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/tree-table.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M44.8 0h79.543C126.78 0 128 1.422 128 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H44.8c-2.438 0-3.657-1.422-3.657-4.267V4.267C41.143 1.422 42.362 0 44.8 0zm22.857 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 80 64 78.578 64 75.733V52.267C64 49.422 65.219 48 67.657 48zm0 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 128 64 126.578 64 123.733v-23.466C64 97.422 65.219 96 67.657 96zM50.286 68.267c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V32h6.4c2.02 0 3.658-1.91 3.658-4.267V4.267C27.429 1.91 25.79 0 23.77 0H3.657C1.637 0 0 1.91 0 4.267v23.466C0 30.09 1.637 32 3.657 32h6.4v80c0 2.356 1.638 4.267 3.657 4.267h36.572c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V68.267h32.915z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/tree.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/tree.svg
new file mode 100755
index 0000000..dd4b7dd
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/tree.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/upload.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/upload.svg
new file mode 100755
index 0000000..3074272
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/upload.svg
@@ -0,0 +1 @@
+<svg t="1577540289643" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7922" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M530.944 458.24l4.8 3.456 122.176 106.816a32 32 0 0 1-37.44 51.584l-4.672-3.392L546.56 556.16v280.704a32 32 0 0 1-26.24 31.488l-5.76 0.512a32 32 0 0 1-31.424-26.24l-0.512-5.76-0.064-280.704-69.12 60.48a32 32 0 0 1-40.96 0.896l-4.16-3.968a32 32 0 0 1-0.96-40.96l4.032-4.16 122.176-106.816a32 32 0 0 1 37.312-3.456zM497.92 128c128.128 0 239.168 82.304 275.52 199.04 123.968 11.264 221.312 113.088 221.312 237.44 0 128.128-103.68 232.96-234.88 238.272h-5.888l-35.52 0.192a32 32 0 0 1-0.192-64l35.264-0.128 4.672-0.064c96.384-3.84 172.544-80.896 172.544-174.272 0-96.128-80.512-174.464-179.584-174.464h-1.984a32 32 0 0 1-32-25.28C695.872 264.96 604.736 192 497.92 192 381.824 192 285.44 277.76 274.816 388.48a32 32 0 0 1-28.352 28.8c-83.968 9.152-147.84 78.208-147.84 159.552l0.192 7.936c3.84 85.76 77.056 154.112 166.592 154.112h45.632a32 32 0 0 1 0 64h-45.632C142.016 802.944 40.32 708.032 34.88 586.88l-0.192-9.28c0-106.88 76.352-197.184 179.968-219.904C239.488 226.112 357.76 128 497.856 128z" p-id="7923"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/user.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/user.svg
new file mode 100755
index 0000000..8f4b10a
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/user.svg
@@ -0,0 +1 @@
+<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/validCode.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/validCode.svg
new file mode 100755
index 0000000..ee0f0e3
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/validCode.svg
@@ -0,0 +1 @@
+<svg t="1569580729849" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1939" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M513.3 958.5c-142.2 0-397.9-222.1-401.6-440.5V268c1.7-39.6 31.7-72.3 71.1-77.3 49-4.6 97.1-16.5 142.7-35.3 47.8-14 91.9-38.3 129.4-71.1 30.3-24.4 72.9-26.3 105.3-4.6 39.9 30.7 83.8 55.9 130.5 74.6 48.6 14.7 98.2 25.9 148.4 33.7 38.5 7.6 67.1 40.3 69.5 79.5 3.3 84.9 2.5 169.9-2.6 254.7-33.7 281.6-253.7 436.4-392.7 436.3z m-0.1-813.7c-7.2-0.2-14.3 2-20 6.4-39.7 35.2-86.8 61.1-137.7 75.7-46.8 19.2-96.2 31-146.6 35.2-11 3.2-18.8 13-19.5 24.4v230.1c3.5 180.3 223.3 361 323.9 361s287.3-120.2 317.6-360.5c7.3-142.7 0-228.6 0-229.6-1.3-13.3-11-24.3-24-27.3-49.6-7.7-98.6-19-146.5-33.7-46.3-19.5-89.7-45.3-129-76.7-5.8-3.8-12.7-5.5-19.5-4.9l1.3-0.1z" fill="#C6CCDA" p-id="1940"></path><path d="M750.1 428L490.7 673.2c-11.7 11.1-29.5 12.9-43.1 4.2l-6.8-5.8-141.2-149.4c-9.3-9.3-12.7-22.9-9-35.5 3.8-12.6 14.1-22.1 27-24.8 12.9-2.7 26.1 1.9 34.6 11.9L469 597.5l233.7-221c14.6-12.8 36.8-11.6 49.9 2.7 13.2 14.2 11.5 35.3-2.5 48.8" fill="#C6CCDA" p-id="1941"></path></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/waiting.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/waiting.svg
new file mode 100755
index 0000000..2c2042d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/waiting.svg
@@ -0,0 +1 @@
+<svg t="1716005941920" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6808" width="200" height="200"><path d="M739.555556 512a256 256 0 1 1 0 512 256 256 0 0 1 0-512z m18.887111-512a180.167111 180.167111 0 0 1 179.882666 169.870222l0.284445 10.24v311.068445a28.444444 28.444444 0 0 1-56.433778 5.12l-0.455111-5.12V180.110222a123.278222 123.278222 0 0 0-114.460445-122.936889l-8.817777-0.341333H209.237333a123.278222 123.278222 0 0 0-122.993777 114.460444l-0.284445 8.817778v662.641778c0 65.080889 50.460444 118.385778 114.460445 122.88l8.817777 0.341333h283.875556a28.444444 28.444444 0 0 1 5.12 56.433778l-5.12 0.455111h-283.875556a180.167111 180.167111 0 0 1-179.882666-169.927111l-0.284445-10.24V180.167111A180.167111 180.167111 0 0 1 198.997333 0.227556L209.237333 0h549.205334zM739.555556 568.888889a199.111111 199.111111 0 1 0 0 398.222222 199.111111 199.111111 0 0 0 0-398.222222z m115.712 314.026667a14.222222 14.222222 0 0 1 0 28.444444h-222.890667a14.222222 14.222222 0 0 1 0-28.444444h222.890667z m-45.738667-227.555556a74.126222 74.126222 0 0 1-37.660445 95.459556v24.234666c0 6.257778 5.12 11.377778 11.377778 11.377778h51.313778c19.057778-0.170667 34.645333 16.042667 34.929778 36.295111v25.486222a11.377778 11.377778 0 0 1-11.377778 11.377778h-228.579556a11.377778 11.377778 0 0 1-11.377777-11.377778v-25.486222c0.512-20.48 16.213333-36.693333 35.271111-36.295111h51.143111a11.377778 11.377778 0 0 0 11.377778-11.377778v-24.291555c-16.440889-7.793778-32.426667-21.674667-39.253334-38.570667a73.500444 73.500444 0 0 1 36.295111-95.459556c35.328-16.497778 81.123556 0.967111 96.540445 38.684445zM360.789333 682.666667a28.444444 28.444444 0 0 1 5.12 56.433777l-5.12 0.455112H199.111111a28.444444 28.444444 0 0 1-5.12-56.433778L199.111111 682.666667h161.678222z m113.777778-227.555556a28.444444 28.444444 0 0 1 5.12 56.433778L474.510222 512H199.111111a28.444444 28.444444 0 0 1-5.12-56.433778L199.111111 455.111111h275.456zM768 227.555556a28.444444 28.444444 0 0 1 5.12 56.433777L768 284.444444H199.111111a28.444444 28.444444 0 0 1-5.12-56.433777L199.111111 227.555556h568.888889z" fill="currentColor" p-id="6809"></path></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/wechat.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/wechat.svg
new file mode 100755
index 0000000..c586e55
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/wechat.svg
@@ -0,0 +1 @@
+<svg width="128" height="110" xmlns="http://www.w3.org/2000/svg"><path d="M86.635 33.334c1.467 0 2.917.113 4.358.283C87.078 14.392 67.58.111 45.321.111 20.44.111.055 17.987.055 40.687c0 13.104 6.781 23.863 18.115 32.209l-4.527 14.352 15.82-8.364c5.666 1.182 10.207 2.395 15.858 2.395 1.42 0 2.829-.073 4.227-.189-.886-3.19-1.398-6.53-1.398-9.996 0-20.845 16.98-37.76 38.485-37.76zm-24.34-12.936c3.407 0 5.665 2.363 5.665 5.954 0 3.576-2.258 5.97-5.666 5.97-3.392 0-6.795-2.395-6.795-5.97 0-3.591 3.403-5.954 6.795-5.954zM30.616 32.323c-3.393 0-6.818-2.395-6.818-5.971 0-3.591 3.425-5.954 6.818-5.954 3.392 0 5.65 2.363 5.65 5.954 0 3.576-2.258 5.97-5.65 5.97z"/><path d="M127.945 70.52c0-19.075-18.108-34.623-38.448-34.623-21.537 0-38.5 15.548-38.5 34.623 0 19.108 16.963 34.622 38.5 34.622 4.508 0 9.058-1.2 13.584-2.395l12.414 7.167-3.404-11.923c9.087-7.184 15.854-16.712 15.854-27.471zm-50.928-5.97c-2.254 0-4.53-2.362-4.53-4.773 0-2.378 2.276-4.771 4.53-4.771 3.422 0 5.665 2.393 5.665 4.771 0 2.41-2.243 4.773-5.665 4.773zm24.897 0c-2.24 0-4.498-2.362-4.498-4.773 0-2.378 2.258-4.771 4.498-4.771 3.392 0 5.665 2.393 5.665 4.771 0 2.41-2.273 4.773-5.665 4.773z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/workflow.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/workflow.svg
new file mode 100755
index 0000000..2f7423a
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/workflow.svg
@@ -0,0 +1 @@
+<svg t="1716004936483" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2712" width="200" height="200"><path d="M1024.99477 113.778v227.555a57.458 57.458 0 0 1-58.027 56.89H734.86277a57.458 57.458 0 0 1-58.027-56.89v-56.889H560.83877v455.112h115.996v-56.89a57.458 57.458 0 0 1 58.027-56.888h231.936a57.458 57.458 0 0 1 58.197 56.889v227.555a57.458 57.458 0 0 1-58.027 56.89H734.86277a57.458 57.458 0 0 1-58.027-56.89v-56.889H502.86877a57.458 57.458 0 0 1-58.027-56.889V568.89L274.51677 735.972a46.763 46.763 0 0 1-65.252 0l-195.754-192a44.658 44.658 0 0 1 0-64l195.754-192.057a46.763 46.763 0 0 1 65.252 0L445.01277 455.11V227.556a57.458 57.458 0 0 1 58.027-56.89h173.966v-56.888a57.458 57.458 0 0 1 58.026-56.89h231.936a57.458 57.458 0 0 1 58.027 56.89z" fill="currentColor" p-id="2713"></path></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/menu/zip.svg b/ruoyi-plus-soybean/src/assets/svg-icon/menu/zip.svg
new file mode 100755
index 0000000..f806fc4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/menu/zip.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.527 116.793c.178.008.348.024.527.024h40.233c4.711-.005 8.53-3.677 8.534-8.21V18.895c-.004-4.532-3.823-8.204-8.534-8.209H79.054c-.179 0-.353.016-.527.024V0L0 10.082v107.406l78.527 10.342v-11.037zm0-101.362c.174-.024.348-.052.527-.052h40.233c2.018 0 3.659 1.578 3.659 3.52v89.713c-.003 1.942-1.64 3.517-3.659 3.519H79.054c-.179 0-.353-.028-.527-.052V15.431zM30.262 75.757l-18.721-.46V72.37l11.3-16.673v-.148l-10.266.164v-4.51l17.504-.44v3.264L18.696 70.76v.144l11.566.176v4.678zm9.419.231l-5.823-.144V50.671l5.823-.144v25.461zm22.255-11.632c-2.168 1.922-5.353 2.76-9.02 2.736-.702.004-1.402-.04-2.097-.131v9.303l-5.997-.148V50.743c1.852-.352 4.473-.647 8.218-.743 3.838-.096 6.608.539 8.48 1.913 1.807 1.306 3.032 3.5 3.032 6.112s-.926 4.833-2.612 6.331h-.004zM53.36 54.45c-.856-.01-1.71.083-2.541.275v7.682c.523.116 1.167.152 2.06.152 3.301-.004 5.36-1.614 5.36-4.314 0-2.425-1.772-3.843-4.875-3.791l-.004-.004zm39.847-37.066h9.564v3.795h-9.564v-3.795zm-9.568 5.68h9.564v3.8h-9.564v-3.8zm9.568 6.216h9.564v3.799h-9.564V29.28zm0 12h9.564v3.794h-9.564V41.28zm-9.568-6.096h9.564v3.795h-9.564v-3.795zm9.472 47.064c2.512 0 4.921-.96 6.697-2.67 1.776-1.708 2.773-4.026 2.772-6.442l-1.748-15.263c0-5.033-2.492-9.112-7.725-9.112-5.232 0-7.72 4.079-7.72 9.112l-1.752 15.263c-.001 2.417.996 4.735 2.773 6.444 1.777 1.71 4.187 2.669 6.7 2.668h.003zm-3.135-16.75h6.27v12.743h-6.27V65.5z"/></svg>
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/network-error.svg b/ruoyi-plus-soybean/src/assets/svg-icon/network-error.svg
new file mode 100755
index 0000000..52f97ab
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/network-error.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><path d="M464.5 113.47q-2.38-4-5-7.9c-23.17-34.35-63.06-57.84-102.3-68.46a231.87 231.87 0 00-52.71-7.69c-116.62-4-163.07 88-201.61 111.67S5 205.33 9 290.32s65.22 165 183.81 170 126.5-48.42 192.71-68.19c43.82-13.08 75-57.26 90.2-98.09 20.78-55.66 19.8-127.93-11.22-180.57z" fill="currentColor"/><path d="M464.5 113.47q-2.38-4-5-7.9c-23.17-34.35-63.06-57.84-102.3-68.46a231.87 231.87 0 00-52.71-7.69c-116.62-4-163.07 88-201.61 111.67S5 205.33 9 290.32s65.22 165 183.81 170 126.5-48.42 192.71-68.19c43.82-13.08 75-57.26 90.2-98.09 20.78-55.66 19.8-127.93-11.22-180.57z" fill="#fff" opacity=".7"/><g fill="#263238" stroke="#263238" stroke-miterlimit="10"><path d="M438.56 121.49v-3.29H377.7V69.42h-1.98v48.78h-60.43v3.29h60.43v45.16h-60.43v3.3h60.43v45.16h-60.43v3.29h60.43v47.45h1.98V218.4h60.86v-3.29H377.7v-45.16h60.86v-3.3H377.7v-45.16h60.86z"/><path d="M311.12 65.46v205.67H442.3V65.46zm126.75 198.72H315.55V72.4h122.32z"/></g><path d="M254 323.92a.1.1 0 00-.06-.13.1.1 0 00-.13.06 132 132 0 00-6.94 21.75 202 202 0 00-3.87 22.55l-.54 5.7c-.08.95-.19 1.9-.26 2.85l-.15 2.86c-.08 1.91-.2 3.82-.26 5.73l.06 5.73.06 2.86c0 .95.11 1.91.18 2.86.14 1.9.26 3.81.43 5.71.37 3.8.94 7.57 1.52 11.34a.11.11 0 00.1.08.09.09 0 00.1-.1c0-1.91-.09-3.8-.13-5.71s-.22-3.78-.19-5.68 0-3.8-.06-5.69l-.06-2.84v-8.52l.15-5.67.06-2.84c0-.95.11-1.89.16-2.84l.35-5.67a200.9 200.9 0 013.21-22.49 131.72 131.72 0 016.27-21.9zM254.81 353.6a.1.1 0 00-.06-.12.1.1 0 00-.13.06 132.44 132.44 0 00-6.43 29.24c-.5 5-1.06 9.94-1.13 14.94a127.13 127.13 0 00.53 15 .11.11 0 00.09.08.09.09 0 00.11-.08c.58-5 .93-9.94 1.28-14.9s.4-9.93.72-14.89a148.32 148.32 0 015-29.31zM271.27 364.05a.1.1 0 00-.14 0 50.06 50.06 0 00-11 9.59 44.64 44.64 0 00-4.3 6l-.88 1.67-.7 1.74a15.71 15.71 0 00-.6 1.75c-.18.6-.38 1.18-.54 1.78a71 71 0 00-2.27 14.52 84 84 0 00.54 14.62.11.11 0 00.1.08.09.09 0 00.1-.09l1.26-14.47a127.64 127.64 0 011.83-14.27c.13-.59.3-1.15.45-1.73a16 16 0 01.5-1.71l.59-1.67.76-1.58a43.25 43.25 0 013.94-6 49.72 49.72 0 0110.33-10.05.11.11 0 00.03-.18z" fill="#263238"/><path d="M263.3 382.64a16.06 16.06 0 00-5 6 35.33 35.33 0 00-2.81 7.37c-.19.63-.35 1.27-.49 1.91l-.45 1.92a34.52 34.52 0 00-.49 3.91c0 1.32-.05 2.63 0 3.94a34.57 34.57 0 00.51 3.91.13.13 0 00.08.08.1.1 0 00.12-.08 35.91 35.91 0 00.7-3.85c.2-1.28.4-2.54.55-3.81s.35-2.52.45-3.79.27-2.54.55-3.78a34.19 34.19 0 012.14-7.3 16.18 16.18 0 014.2-6.27.09.09 0 000-.13.11.11 0 00-.06-.03zM240.22 397.14c-.54-6.42-1.6-12.79-2.6-19.14s-2.12-12.7-3.28-19-2.46-12.63-3.83-18.91a.1.1 0 10-.19 0c1.1 6.34 2.09 12.69 3 19.05s1.87 12.71 2.68 19.08 1.4 12.77 2.19 19.13l2.35 19.13a.12.12 0 00.1.09.11.11 0 00.11-.1c.07-1.61.07-3.22.11-4.84s-.07-3.22-.09-4.83c-.03-3.23-.31-6.44-.55-9.66zM236.37 397.05c-.35-2.55-.81-5.08-1.3-7.6l-.9-3.75c-.3-1.25-.58-2.5-.94-3.74a105.84 105.84 0 00-4.92-14.61l-.77-1.77-.85-1.73c-.55-1.16-1.2-2.27-1.83-3.4a28.58 28.58 0 00-4.64-6.14.09.09 0 00-.13 0 .1.1 0 000 .14 28 28 0 014.24 6.29c.56 1.14 1.15 2.26 1.63 3.44l.76 1.75.67 1.79a105.45 105.45 0 014.28 14.61c.58 2.47 1 5 1.45 7.49l.6 3.76c.22 1.25.49 2.5.66 3.76.4 2.51.87 5 1.3 7.53s.85 5 1.39 7.55a.11.11 0 00.09.09.11.11 0 00.11-.1q0-3.85-.18-7.7c-.15-2.56-.46-5.11-.72-7.66zM231.05 397.38c-1.19-3.38-2.57-6.68-4.07-9.91a86.8 86.8 0 00-5.12-9.38.1.1 0 10-.17.1 87.62 87.62 0 014.37 9.68c1.3 3.28 2.48 6.62 3.46 10s1.73 6.83 2.63 10.23 1.71 6.83 2.76 10.27a.1.1 0 00.09.07.11.11 0 00.11-.1 51.83 51.83 0 00-1-10.69 66.72 66.72 0 00-3.06-10.27zM230.83 408.61a23.15 23.15 0 00-2.83-5.94 24.44 24.44 0 00-4.12-5.11 19.36 19.36 0 00-2.62-2 10.92 10.92 0 00-3-1.34.1.1 0 00-.11.06.1.1 0 000 .13 14.89 14.89 0 014.93 3.83 23.66 23.66 0 013.43 5.2c1 1.84 1.48 3.88 2.33 5.79.37 1 .74 1.95 1.18 2.93a23.09 23.09 0 001.29 3 .11.11 0 00.09.05.09.09 0 00.1-.09 22.57 22.57 0 00-.12-3.29c-.07-1.09-.29-2.16-.55-3.22z" fill="#263238"/><path d="M231.23 349.25a18.29 18.29 0 01-13-13.76c-2.43-11.34 5.4-6.48-1.62-24.3s-5.4-21.87-3-27 3.78-3.51 5.94 3.24 10 17.28 13 37-1.32 24.82-1.32 24.82zM252 330.09s-7-2.43-7.29-15.66 5.13-10.53 5.94-27 1.89-28.08 4.86-28.35 10.8 8.1 13.5 26.46-3.51 30.51-8.91 35.91-8.1 8.64-8.1 8.64zM227.45 365.18s-1.62-9.72-4.86-14.31-10.26-8.64-14.85-17.54-9.72-13.23-9.45-.54 5.94 19.16 13 23.48 16.16 8.91 16.16 8.91zM224.48 381.92s-6.48-12.42-15.93-21.6-17-13.77-14.31-9.18 5.94 4.32 10.53 12.42 3.24 14 9.72 18.63 9.99-.27 9.99-.27zM223.67 397.85c-.27-.81-.54-6.75-9.72-11.61s-17.82-3-16.74-.54 6.75 6.48 12.42 8.91 14.04 3.24 14.04 3.24zM251.47 365.72s-4.05-5.13-1.89-15.93 7-8.1 12.42-13.77 4.32-6.47 9.72-12.41 10.26-9.18 12.42-6.75-1.08 10-4.32 14.31-8.37 6.47-10.82 11.83-1.35 14-7.56 18.09-9.97 4.63-9.97 4.63zM265 368.69s9.72-11.07 16.74-19.17 9.72-14.3 10-8.91 2.43 9.72-3.24 16.74-23.5 11.34-23.5 11.34zM259.84 386.51s-.27-4.32 15.12-14.31 21.87-11.88 20.25-7.83-8.37 8.1-15.66 15.39-19.71 6.75-19.71 6.75z" fill="currentColor"/><path d="M231.87 348.1s-3.61-19.36-7.39-33.13-6.21-22.14-6.21-22.14M251.39 330.29s6.56-21 8.72-33.68-.27-22.14-1.35-27.81M226.6 364.78a101.76 101.76 0 00-9.95-14.18c-5.94-7-10.53-10.8-13.23-17.54M251.47 365.72s2.16-13 7.29-19.71 15.39-17.27 19.44-24M223 397.55s-9.08-5.91-15.56-9.15" fill="none" stroke="#263238" stroke-linecap="round" stroke-linejoin="round"/><path fill="#263238" stroke="#263238" stroke-linecap="round" stroke-linejoin="round" d="M252.89 454.81h-18.76l-9.38-46.97h37.52l-9.38 46.97z"/><g fill="none" stroke="#263238" stroke-miterlimit="10"><path d="M64.8 454.81h425.18M31.18 454.81h26.37"/></g><g><path d="M216.14 167v-31.5h-2.85V95.19a24.85 24.85 0 00-24.85-24.86H54.92a24.86 24.86 0 00-24.86 24.86v317.43a24.86 24.86 0 0024.86 24.86h133.52a24.85 24.85 0 0024.85-24.86V193.85h2.85v-18.61h-2.85V167z" fill="#707070" stroke="#263238" stroke-linejoin="round"/><path d="M184.08 434.05H59.27a26.33 26.33 0 01-26.33-26.33V100.09c0-14.55 7.46-26.34 26.33-26.34h124.81c17.31-.23 26.34 11.79 26.34 26.34v307.63a26.33 26.33 0 01-26.34 26.33z" fill="#263238" stroke="#263238" stroke-linejoin="round"/><path d="M131.34 77.8l-.05.19c-1.46 5.44-4.82 9-8.54 9h-2.14c-3.72 0-7.08-3.52-8.54-9v-.19l-52.08-.18A21.92 21.92 0 0038 99.53l-.07 304.94c0 14.21 11.12 25.73 24.85 25.73h117.84c13.73 0 24.86-11.52 24.86-25.73V97.34a19 19 0 00-18.78-19z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path d="M205.46 102.88v-5.54a19 19 0 00-18.78-19l-55.34-.57-.05.19c-1.46 5.44-4.82 9-8.54 9h-2.14c-3.72 0-7.08-3.52-8.54-9v-.19l-52.08-.18A21.92 21.92 0 0038 99.53v3.35z" fill="#dbdbdb" stroke="#263238" stroke-miterlimit="10"/><path d="M123.5 77.2a2.34 2.34 0 10-2.33 2.34 2.33 2.33 0 002.33-2.34z" fill="#707070"/><path d="M54.4 83.14c0 2.37-2.65 3.22-2.66 5v.12h2.57v.79h-3.43v-.65c0-2.53 2.65-3 2.65-5.22 0-.81-.27-1.23-.92-1.23s-.92.46-.92 1.15v.69h-.81v-.63c0-1.2.54-2 1.75-2s1.77.84 1.77 1.98zM58.69 83.13v.2a1.57 1.57 0 01-1 1.62 1.55 1.55 0 011 1.62v.6c0 1.2-.56 2-1.77 2s-1.76-.78-1.76-2v-.53H56v.59c0 .7.29 1.13.92 1.13s.91-.42.91-1.21v-.6c0-.78-.32-1.15-1-1.17h-.47v-.78h.51a.94.94 0 00.94-1.08v-.35c0-.81-.28-1.22-.91-1.22s-.92.43-.92 1.14v.4h-.82v-.36c0-1.19.56-2 1.76-2s1.77.81 1.77 2zM60.38 83.73v1.08h-.84v-1.08zm0 4.26v1.08h-.84V88zM62.14 84.43a1.28 1.28 0 011.17-.65c1 0 1.45.74 1.45 1.86v1.53c0 1.2-.57 2-1.76 2s-1.76-.78-1.76-2v-.52h.81v.58c0 .7.3 1.13.92 1.13s.92-.43.92-1.13v-1.52c0-.71-.29-1.13-.92-1.13a.84.84 0 00-.88.84v.17h-.82l.21-4.35h3.09V82h-2.3zM69.12 83.12v.15h-.82v-.2c0-.71-.29-1.12-.93-1.12s-1 .42-1 1.26V85a1.26 1.26 0 011.26-.84c1 0 1.46.73 1.46 1.86v1.18c0 1.2-.59 2-1.79 2s-1.8-.78-1.8-2v-4c0-1.24.56-2 1.8-2s1.82.72 1.82 1.92zm-2.7 2.93v1.18c0 .7.29 1.13.93 1.13s.93-.43.93-1.13v-1.18c0-.7-.3-1.13-.93-1.13s-.93.43-.93 1.13zM152.41 83.47h2.21V89h-2.21zM155.36 82.36h2.21V89h-2.21zM158.31 81.57h2.21v7.42h-2.21zM161.25 80.2h2.21v8.79h-2.21zM190.83 88.84h-13.27v-8.11h13.27zm-12.53-.73h11.79v-6.64H178.3z" fill="#263238"/><path fill="#263238" d="M179.41 82.27h7v5.03h-7zM190.37 83.83h1.67v1.91h-1.67zM172.7 87.08a.48.48 0 01-.26-.07c-2.7-1.61-4.48-.12-4.56-.05a.49.49 0 01-.69 0 .5.5 0 010-.7c.1-.08 2.38-2 5.72 0a.5.5 0 01-.25.92z"/><path d="M173.7 85.41a.48.48 0 01-.26-.07c-3.9-2.32-6.53-.07-6.55 0a.5.5 0 01-.66-.74s3.2-2.74 7.72-.06a.49.49 0 01.17.68.48.48 0 01-.42.19z" fill="#263238"/><path d="M174.6 83.48a.47.47 0 01-.25-.07c-4.94-2.94-8.23-.17-8.37 0a.49.49 0 01-.7 0 .48.48 0 01.05-.69s3.94-3.38 9.53-.06a.5.5 0 01.17.68.49.49 0 01-.43.14zM171.21 87.91a1.25 1.25 0 11-1.21-1.24 1.24 1.24 0 011.21 1.24zM121.53 217.66c22 .34 40.33 8.68 54.78 25.36a5.08 5.08 0 01-.47 7.33 5 5 0 01-7.15-.66 62.94 62.94 0 00-18.47-14.9 61.21 61.21 0 00-68.4 7.45 92.38 92.38 0 00-7.42 7.39 5.17 5.17 0 01-7.32.66 5.1 5.1 0 01-.36-7.33 71.44 71.44 0 0145.7-24.87c1.44-.19 2.89-.35 4.34-.41s3.18-.02 4.77-.02z" fill="#263238"/><path d="M123.35 238.05c13.9.36 27 6.29 37.24 18.17a5 5 0 01-.46 7.3 5 5 0 01-7.22-.7 39.62 39.62 0 00-19.8-12.9c-16.11-4.36-30.11-.38-41.81 11.57-.44.45-.84.93-1.25 1.39a5.08 5.08 0 11-7.68-6.65 51.11 51.11 0 0116.38-12.85c7.19-3.57 14.82-5.3 24.6-5.33z" fill="#263238"/><path d="M146.21 272.81a5.08 5.08 0 01-3.25 4.76 5 5 0 01-5.66-1.42 21.34 21.34 0 00-7.76-5.77C121.2 266.84 112 269 105.79 276a4.92 4.92 0 01-5.43 1.63 5.07 5.07 0 01-2.36-8.11 30.52 30.52 0 0147 .08 14.27 14.27 0 011.5 3.09z" fill="#263238"/><path d="M121.52 299.5a10.2 10.2 0 1110.17-10.15 10.14 10.14 0 01-10.17 10.15z" fill="#263238"/><path d="M121.58 182a72.72 72.72 0 1072.71 72.71A72.8 72.8 0 00121.58 182zm-62.72 72.75A62.68 62.68 0 01164 208.56l-88.61 88.57a62.52 62.52 0 01-16.53-42.38zm62.72 62.72a62.4 62.4 0 01-38.85-13.54l88-88a62.67 62.67 0 01-49.18 101.57z" fill="currentColor"/></g><g><path fill="#263238" stroke="#263238" stroke-miterlimit="10" d="M427.5 454.81h-9.48l-2.03-22.56h13.54l-2.03 22.56zM466.98 454.81h-9.48l-2.03-22.56h13.54l-2.03 22.56zM372.23 454.81h-9.48l-2.02-22.56h13.53l-2.03 22.56zM329.93 454.81h-9.47l-2.03-22.56h13.53l-2.03 22.56z"/><path d="M375.17 286.9h98.52s.39 34.25-4.68 58.5-11.28 38.91-11.28 38.91h-93.62s13.89-53.64 11.06-97.41z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path d="M416.06 292.13c-8.64-.68-12.07-2.42-18.53-6-8.41-4.61-10.21-1.54-9.61 3.07s-8.17 35-10 50.3-1.21 23.53-1.21 23.53-5.4 4.61-1.8 6.14a32.28 32.28 0 007.21 2.05h69.66s12.38-63.61 13.58-73.84-4.2-11.25-7.21-9.72-3.6 2.56-7.81 3.07c-9.84 1.27-22.44 2.32-34.28 1.4z" fill="currentColor"/><path d="M393.63 293.16a16.26 16.26 0 003.69-7.1c-.84-.45-1.61-.82-2.32-1.12a13.69 13.69 0 01-3.26 6.61 14.3 14.3 0 01-4.54 3.65c-.18.91-.38 1.9-.6 3a16.06 16.06 0 007.03-5.04zM456 313.06c-4.41 5.18-4.24 9.78-4.08 14.24.16 4.16.3 8.08-3.48 12.53s-7.69 4.92-11.82 5.44c-4.42.54-9 1.11-13.4 6.29s-4.25 9.78-4.08 14.23a28.53 28.53 0 01-.15 5.47h2.52a33.48 33.48 0 00.11-5.56c-.15-4.16-.3-8.09 3.49-12.53s7.68-4.93 11.82-5.44c4.42-.54 9-1.11 13.4-6.29s4.24-9.78 4.08-14.24c-.16-4.16-.3-8.09 3.49-12.53a14 14 0 015.34-4c.17-1 .34-1.92.5-2.83a16.07 16.07 0 00-7.74 5.22zM385.23 370.14c4.43-.55 9-1.12 13.41-6.3s4.24-9.78 4.08-14.23c-.16-4.16-.3-8.09 3.48-12.53s7.69-4.93 11.82-5.44c4.42-.55 9-1.11 13.4-6.29s4.25-9.79 4.08-14.24c-.15-4.16-.3-8.09 3.49-12.53s7.69-4.93 11.82-5.44 8.26-1 12.34-5.14a4.35 4.35 0 00-2.74-.86 14.82 14.82 0 01-7.92 3.27 19.67 19.67 0 01-2.12.36l-3 .35A16.3 16.3 0 00437.1 297c-4.42 5.19-4.25 9.79-4.08 14.24.15 4.16.3 8.09-3.49 12.54s-7.68 4.92-11.82 5.43c-4.42.55-9 1.11-13.4 6.29s-4.24 9.79-4.08 14.24c.16 4.16.3 8.09-3.48 12.53s-7.69 4.93-11.82 5.44a23.21 23.21 0 00-8.38 2.16 32.7 32.7 0 004 1.12 32.06 32.06 0 014.68-.85zM379.74 347.75c4.41-5.18 4.24-9.78 4.08-14.23-.15-4.17-.3-8.09 3.49-12.54s7.68-4.92 11.81-5.43c4.42-.55 9-1.12 13.41-6.3s4.24-9.78 4.08-14.23c0-1-.07-1.91-.06-2.86h-.49l-2-.19c0 1.07 0 2.12.05 3.17.16 4.16.3 8.09-3.48 12.53s-7.69 4.93-11.82 5.44c-4.42.55-9 1.11-13.41 6.29s-4.24 9.78-4.07 14.24c.15 4.16.29 8.09-3.49 12.53-.21.25-.42.48-.64.7-.1 1.27-.19 2.47-.26 3.59a18.26 18.26 0 002.8-2.71z" fill="#fff"/><path d="M416.06 292.13c-8.64-.68-12.07-2.42-18.53-6-8.41-4.61-10.21-1.54-9.61 3.07s-8.17 35-10 50.3-1.21 23.53-1.21 23.53-5.4 4.61-1.8 6.14a32.28 32.28 0 007.21 2.05h69.66s12.38-63.61 13.58-73.84-4.2-11.25-7.21-9.72-3.6 2.56-7.81 3.07c-9.84 1.27-22.44 2.32-34.28 1.4z" fill="none" stroke="#263238" stroke-miterlimit="10"/><path d="M375.17 286.9h-5.57a4.49 4.49 0 00-4.37 3.45l-6.92 29c-1.28 5.38-5.66 20.88-10.48 23.59h0a19.79 19.79 0 01-9.69 2.53h-20.88l8.88 90.77h38l10-144.53a5.11 5.11 0 000-1h1.13z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path d="M322.83 345.46h20.89a19.82 19.82 0 009.69-2.53h0c4.82-2.71 9.19-18.21 10.48-23.59l6.91-29a4.5 4.5 0 014.37-3.45h0a4.49 4.49 0 014.48 4.8L369.4 436.23h-38z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path fill="#fff" stroke="#263238" stroke-miterlimit="10" d="M412.04 436.28l2.17-65.02-87.11-.77 2.18 65.79h82.76zM479.26 286.9h-5.57a4.5 4.5 0 00-4.37 3.45l-6.92 29c-1.28 5.38-5.65 20.88-10.48 23.59h0a19.76 19.76 0 01-9.69 2.53h-20.88l3.65 90.76h42.82l10.35-144.53a4.39 4.39 0 000-1h1.13z"/><path d="M426.92 345.46h20.89a19.82 19.82 0 009.69-2.53h0c4.82-2.71 9.2-18.21 10.48-23.59l6.92-29a4.49 4.49 0 014.36-3.45h0a4.48 4.48 0 014.48 4.8L473.58 434a2.39 2.39 0 01-2.37 2.21h-40.88z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path d="M414.21 352L412 436.28h18.29l3.18-84.28a6.54 6.54 0 00-6.55-6.54h-6.16a6.54 6.54 0 00-6.55 6.54zM307.94 352l3.14 84.28h18.29L327.19 352a6.54 6.54 0 00-6.54-6.54h-6.16a6.54 6.54 0 00-6.55 6.54z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path fill="none" stroke="#263238" stroke-miterlimit="10" d="M327.54 399.23h85.84"/></g><g><path d="M285.55 287.23s-7.94 44.12-9.68 51.58-6 45.49-6 54.94.74 38 .74 38-3.23 1.49-3.23 2.73 2.49 9.2 2.49 9.2l14.66 5.47s3-7 3.23-9.45-1.74-3.48-1.74-7.71 7.46-53.44 9.7-61.64 8.7-32.81 9.94-35.05 7.46-18.64 7.46-18.64l3.48 1.74s2.23 51.7 3.48 54.93S338 429.2 338 429.2s-2.73 1.24-1 4.23 4.72 9.69 4.72 9.69 2.74-3.73 6-4.47 8.7 2.48 8.7 2.48 3.23-7.46 2.48-10.93-4-9.45-4.22-11.19-7-51.86-7-51.86 1.74-70.1 1.74-74.82a31.49 31.49 0 00-1.74-9.45 117.57 117.57 0 01-32.32 6.22c-17.65.74-27.34-6.46-27.34-6.46z" fill="#263238" stroke="#263238" stroke-miterlimit="10"/><path d="M269.91 443.71s-8.21 7.45-9.7 8.45-11.93 5-11.68 7 3 3.23 3 3.23h33.81s.24-1 .74-5.72a18.86 18.86 0 00-1.49-8.95z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path d="M285.83 458.76l-37.12 1a6.16 6.16 0 002.8 2.57h33.81s.17-.67.51-3.57zM341.74 443.12s-.25 6.21-.25 9.45-.49 7-.25 7.7.75 2 .75 2h19.64s.74-2-.25-5.72-2.74-5.71-3.23-9a36.31 36.31 0 00-1.74-6.46s-9.7-7.66-14.67 2.03z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path d="M361.89 459.91c-7.47 0-16.77-.24-20.72-.34a3.34 3.34 0 00.07.7c.25.75.75 2 .75 2h19.64a7.22 7.22 0 00.26-2.36z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path d="M278.5 193.64s-21.12 19.83-20.2 21.68 6.7 16.86 6.7 16.86 1.66 10 .74 16.31a64.54 64.54 0 00-.56 12.05 25.59 25.59 0 007 6.11c4.08 2.23 7.23 6.12 9.83-.18s1.48-17.61 1.48-17.61 3.51 13.72 4.1 16.14-2 22.24-2 22.24S298.33 298 320 295.56s28.54-10 28.54-10-2.23-24.28-2.23-27.06a54 54 0 00-.37-5.75s7.6 7.79 11.31 7.42 10.19-15.57 8.52-18-7.41-12.25-10.93-19.29a85.73 85.73 0 01-5.28-13.19s-6.12-20.76-8-22.24-22.7-1.25-26.41-2.17a124.1 124.1 0 00-14.83-2c-1.24-.02-17.74 3.87-21.82 10.36z" fill="currentColor" stroke="#263238" stroke-miterlimit="10"/><path d="M340.8 221.39c-.1-5.32-.12-9.46-.12-9.46M346 252.75a78.12 78.12 0 01-3.7-8.89c-.74-2.21-1.13-10.1-1.34-17.49M272.93 230.8c-1.89-3.3-3.33-5.88-3.51-6.4M283.51 248.86s-4.74-8-8.72-14.84M307.6 185.85a92.16 92.16 0 01.19 19.65M320.07 277.32c2.23.45 4.62.86 7.17 1.19M293.14 265.36s6 6.58 22 10.8M309.45 279.44a50.88 50.88 0 0021.87 4.45" fill="none" stroke="#263238" stroke-miterlimit="10"/><path d="M265.16 163.61s2.22 10.38 2.41 12.61.74 5.19 3.52 9.63 10 14.27 11.49 14.83 7.23-.93 7.23-.93 6.11 12.05 7.78 11.68 5.93-14.09 6.49-19.65a39.64 39.64 0 00.18-8.34l-2.59-9.26s-2.22-13-2.22-13.9 1.29-7.6-5.93-10.38-16.13-3-22.06 3.89-6.3 9.82-6.3 9.82z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path d="M285 151.94a33.32 33.32 0 01-10 9.82c-6.85 4.45-3.52-.37-3.52-.37s-4.82 3-8.71 5-2.41 2.6-3.89 2-7-16.38 5.39-24 21.43-5.61 21.43-5.61-5.38-2.41-.56-2.41S292 139 292 139s2.59-.74 3.7.74a4.4 4.4 0 001.77 1.17c6 2.54 8.09 8.22 9.22 14.15.74 3.89-2.41 13.16-2.41 13.16l-5.2 2.78a12.88 12.88 0 01-3-4.08 15.94 15.94 0 01-.74-4.63s-3.15 4.07-3.71 1.11l-2-10.56s.56 3-1.29 3-3.34-3.9-3.34-3.9z" fill="#263238" stroke="#263238" stroke-miterlimit="10"/><path d="M298.7 172.32s-.18-6.3 3.53-8.34 6.3.19 5.18 6.12-7.78 8.15-7.78 8.15" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path fill="#263238" stroke="#263238" stroke-miterlimit="10" d="M289.81 199.75l9.82-9.82-6.49 15.01-3.33-5.19z"/><path d="M285.27 189.82a1.66 1.66 0 01-.51 2.2c-.67.27-1.52-.28-1.9-1.23s-.15-1.94.51-2.21 1.51.29 1.9 1.24zM287.83 172.66c.36 1.18.1 2.3-.57 2.51s-1.53-.58-1.89-1.75-.1-2.29.57-2.5 1.53.57 1.89 1.74zM275.66 174.72c.36 1.18.1 2.3-.58 2.51s-1.52-.57-1.88-1.75-.11-2.29.57-2.5 1.52.57 1.89 1.74z" fill="#263238"/><path d="M276.1 170.1a7.06 7.06 0 012.59 5.56c0 3.71-.56 9.27-.56 9.27l6.68-.93M280.54 167.69s3.34-5.93 8.53-4.82M274.61 168.8s-4.07-1.29-6.85.37" fill="none" stroke="#263238" stroke-miterlimit="10"/><path d="M388 216.43s4.08-1.48 5.56-2 3 .37 5.56-.92 5.56-3.9 5.75-2-.75 2.59-2.23 3.52-3.89 2.78-3.89 2.78l-.74 1.85-11.12-.92s.21-1.74 1.11-2.31zM410.35 221.22s5.11-4.05 5.85-5.9-.56-2-1.85-1.3-5.35 4.08-5.35 4.08z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path d="M406.17 222.1s3.8.4 4.18-.88 3.38-9.34 2.89-10.35-1.44-.92-2.49.75a57.18 57.18 0 00-2.9 7.07s-2.54.57-3.18 2.31 1.5 1.1 1.5 1.1z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path d="M401.37 223.1s3.53 1.49 4.27.37 5.93-8 5.74-9.08-1.11-1.29-2.59 0a55.84 55.84 0 00-4.82 5.93s-2.6-.18-3.71 1.3 1.11 1.48 1.11 1.48z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path d="M349.67 253.5a23.39 23.39 0 0110.38-14.27c8.89-5.75 20.95-16.13 20.95-16.13s4.08-5.74 7-6.67 12.6 1.85 12.6 1.85 7.79-7.78 8-6.11-3.7 7.6-5.56 9.82-7.6 2.78-8.89 2.78a67 67 0 00-6.86.56c-.74.18-13 16.86-18.53 25s-8 9.63-11.49 9.82-7.23-2.6-8.16-3.89.56-2.76.56-2.76z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path d="M233.77 222.82l11.68 25.34a1.86 1.86 0 002 1l9.69-1.79a1.85 1.85 0 001.36-2.52L248 220.12a1.82 1.82 0 00-1.89-1.11l-10.89 1.21a1.84 1.84 0 00-1.45 2.6z" fill="#263238" stroke="#263238" stroke-miterlimit="10"/><path d="M241.44 244.51s-4.19-6.42-4.19-7.86 2.44-4.85 3.87-7.93 1.65-5.14 2.88-4.72 1 1.64.61 3.69a79.85 79.85 0 01-2.66 7.8l.41 2.47a26.4 26.4 0 013.69-5.55c1.85-1.84 3.9-4.92 5.34-4.31s2.05 1 .82 2.47a42.71 42.71 0 00-4.14 5.91 36.87 36.87 0 00-2.79 5.58 42.73 42.73 0 014.37-3.66c2-1.44 4.82-3.52 6-2.7s1.44 1.64.21 2.67-2.35 1.73-4 3.17a60.68 60.68 0 00-4.84 5.45 17.87 17.87 0 014.84-2.66c3.49-1.43 6.68-1.27 6.81-.17s-3.67 2.1-3.67 2.1c-2.72.62-2.85 1.55-2.85 1.55s2.26 4.52 1.85 6c0 0 9.12-1.43 12.61-2.45s8.94 1 10.59 2.65c3.49 3.49 5.13 8.83 3.29 13.35s-1.85 3.08-16.43-1-14.3-5.54-17.86-11.5c-1.29-2.32-4.76-10.35-4.76-10.35z" fill="#fff" stroke="#263238" stroke-miterlimit="10"/><path fill="none" stroke="#263238" stroke-miterlimit="10" d="M254.06 253.77l-.31 3.76"/></g></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/no-icon.svg b/ruoyi-plus-soybean/src/assets/svg-icon/no-icon.svg
new file mode 100755
index 0000000..f6dcdd0
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/no-icon.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 32 32"><g fill="none"><g clip-path="url(#svgIDa)"><path fill="url(#svgIDb)" d="M15.268 25.734a.91.91 0 0 1 .188-.555l1.211-1.855c.245-.316.256-.647.063-.993l-2.485-4.914c-.214-.386-.067-.592.086-.765c.45-.509 1.344-1.696 1.69-2.139l.633-.803c.225-.274.276-.65.153-.986c-.497-1.329-1.078-2.919-1.624-4.408c-.185-.504-.35-1.079-.735-1.438c-.74-.691-2.076-1.416-3.099-1.594C4.27 3.777 1.577 9.98 2.534 14.94c1.39 6.086 7.969 12.148 11.961 14.16c.45.226.75.06.75-.309l.024-3.058Z"/><path fill="url(#svgIDc)" d="M15.268 25.734a.91.91 0 0 1 .188-.555l1.211-1.855c.245-.316.256-.647.063-.993l-2.485-4.914c-.214-.386-.067-.592.086-.765c.45-.509 1.344-1.696 1.69-2.139l.633-.803c.225-.274.276-.65.153-.986c-.497-1.329-1.078-2.919-1.624-4.408c-.185-.504-.35-1.079-.735-1.438c-.74-.691-2.076-1.416-3.099-1.594C4.27 3.777 1.577 9.98 2.534 14.94c1.39 6.086 7.969 12.148 11.961 14.16c.45.226.75.06.75-.309l.024-3.058Z"/><path fill="url(#svgIDd)" d="M15.268 25.734a.91.91 0 0 1 .188-.555l1.211-1.855c.245-.316.256-.647.063-.993l-2.485-4.914c-.214-.386-.067-.592.086-.765c.45-.509 1.344-1.696 1.69-2.139l.633-.803c.225-.274.276-.65.153-.986c-.497-1.329-1.078-2.919-1.624-4.408c-.185-.504-.35-1.079-.735-1.438c-.74-.691-2.076-1.416-3.099-1.594C4.27 3.777 1.577 9.98 2.534 14.94c1.39 6.086 7.969 12.148 11.961 14.16c.45.226.75.06.75-.309l.024-3.058Z"/><path fill="url(#svgIDe)" d="M15.268 25.734a.91.91 0 0 1 .188-.555l1.211-1.855c.245-.316.256-.647.063-.993l-2.485-4.914c-.214-.386-.067-.592.086-.765c.45-.509 1.344-1.696 1.69-2.139l.633-.803c.225-.274.276-.65.153-.986c-.497-1.329-1.078-2.919-1.624-4.408c-.185-.504-.35-1.079-.735-1.438c-.74-.691-2.076-1.416-3.099-1.594C4.27 3.777 1.577 9.98 2.534 14.94c1.39 6.086 7.969 12.148 11.961 14.16c.45.226.75.06.75-.309l.024-3.058Z"/><path fill="url(#svgIDf)" d="M21.815 5.09c-1.64.283-3.489 1.394-4.428 2.377c0 0-.123.102-.123.254c0 .06.02.142.03.152l1.943 5.341a.657.657 0 0 1-.07.703c-.725.898-1.524 1.969-2.227 2.871c-.31.315-.341.681-.125 1.067l2.422 4.675c.196.355.117.594-.043.829l-1.554 2.37c-.135.173-.266.516-.266.731l-.027 2.524c0 .316.482.348.875.074c3.828-2.094 10.411-7.917 11.78-14.063c1.126-5.047-1.75-10.78-8.187-9.906Z"/><path fill="url(#svgIDg)" d="M21.815 5.09c-1.64.283-3.489 1.394-4.428 2.377c0 0-.123.102-.123.254c0 .06.02.142.03.152l1.943 5.341a.657.657 0 0 1-.07.703c-.725.898-1.524 1.969-2.227 2.871c-.31.315-.341.681-.125 1.067l2.422 4.675c.196.355.117.594-.043.829l-1.554 2.37c-.135.173-.266.516-.266.731l-.027 2.524c0 .316.482.348.875.074c3.828-2.094 10.411-7.917 11.78-14.063c1.126-5.047-1.75-10.78-8.187-9.906Z"/><path fill="url(#svgIDh)" d="M21.815 5.09c-1.64.283-3.489 1.394-4.428 2.377c0 0-.123.102-.123.254c0 .06.02.142.03.152l1.943 5.341a.657.657 0 0 1-.07.703c-.725.898-1.524 1.969-2.227 2.871c-.31.315-.341.681-.125 1.067l2.422 4.675c.196.355.117.594-.043.829l-1.554 2.37c-.135.173-.266.516-.266.731l-.027 2.524c0 .316.482.348.875.074c3.828-2.094 10.411-7.917 11.78-14.063c1.126-5.047-1.75-10.78-8.187-9.906Z"/><path fill="url(#svgIDi)" d="M21.815 5.09c-1.64.283-3.489 1.394-4.428 2.377c0 0-.123.102-.123.254c0 .06.02.142.03.152l1.943 5.341a.657.657 0 0 1-.07.703c-.725.898-1.524 1.969-2.227 2.871c-.31.315-.341.681-.125 1.067l2.422 4.675c.196.355.117.594-.043.829l-1.554 2.37c-.135.173-.266.516-.266.731l-.027 2.524c0 .316.482.348.875.074c3.828-2.094 10.411-7.917 11.78-14.063c1.126-5.047-1.75-10.78-8.187-9.906Z"/><g filter="url(#svgIDj)"><ellipse cx="24.453" cy="12.672" fill="url(#svgIDk)" rx="4.04" ry="5.171" transform="rotate(41.528 24.453 12.672)"/></g><g filter="url(#svgIDl)"><ellipse cx="12.11" cy="11.281" fill="url(#svgIDm)" rx="2.968" ry="4.662" transform="rotate(150.611 12.11 11.281)"/></g><g filter="url(#svgIDn)"><path stroke="url(#svgIDo)" stroke-linecap="round" stroke-width=".25" d="m16.41 22.606l-2.75-5.438"/></g><g filter="url(#svgIDp)"><path stroke="url(#svgIDq)" stroke-linecap="round" stroke-opacity=".75" stroke-width=".15" d="m13.815 16.511l2.688-3.313l-1.375-3.78"/></g></g><defs><radialGradient id="svgIDd" cx="0" cy="0" r="1" gradientTransform="rotate(-138.643 11.823 5.79) scale(23.9312 14.1578)" gradientUnits="userSpaceOnUse"><stop offset=".671" stop-color="#FF7AF2" stop-opacity="0"/><stop offset=".785" stop-color="#FF96D7"/></radialGradient><radialGradient id="svgIDe" cx="0" cy="0" r="1" gradientTransform="rotate(178.046 10.034 10.148) scale(25.1878 31.5353)" gradientUnits="userSpaceOnUse"><stop offset=".536" stop-color="#AB1F51" stop-opacity="0"/><stop offset=".814" stop-color="#AB1F51"/></radialGradient><radialGradient id="svgIDh" cx="0" cy="0" r="1" gradientTransform="matrix(39.74996 -6.5 3.74657 22.9117 2.972 16.136)" gradientUnits="userSpaceOnUse"><stop offset=".67" stop-color="#FF7AF2" stop-opacity="0"/><stop offset=".735" stop-color="#FF96D7"/></radialGradient><radialGradient id="svgIDi" cx="0" cy="0" r="1" gradientTransform="matrix(-16.37517 -20.43737 54.0988 -43.34597 25.66 24.948)" gradientUnits="userSpaceOnUse"><stop offset=".607" stop-color="#AB1F51" stop-opacity="0"/><stop offset=".814" stop-color="#AB1F51"/></radialGradient><radialGradient id="svgIDk" cx="0" cy="0" r="1" gradientTransform="rotate(99.933 9.313 14.79) scale(9.0832 29.5242)" gradientUnits="userSpaceOnUse"><stop stop-color="#EF6E9D"/><stop offset="1" stop-color="#EF6E9D" stop-opacity="0"/></radialGradient><radialGradient id="svgIDm" cx="0" cy="0" r="1" gradientTransform="rotate(96.854 2.12 10.142) scale(8.73812 23.3563)" gradientUnits="userSpaceOnUse"><stop offset=".229" stop-color="#EC5A8F"/><stop offset="1" stop-color="#EC5A8F" stop-opacity="0"/></radialGradient><linearGradient id="svgIDb" x1="9.765" x2="9.765" y1="29.266" y2="5.164" gradientUnits="userSpaceOnUse"><stop offset=".026" stop-color="#EC008C"/><stop offset=".198" stop-color="#FF1F50"/><stop offset=".536" stop-color="#FF2868"/><stop offset="1" stop-color="#EC008C"/></linearGradient><linearGradient id="svgIDc" x1="17.784" x2="14.329" y1="14.744" y2="14.744" gradientUnits="userSpaceOnUse"><stop stop-color="#C6162A"/><stop offset="1" stop-color="#C6162A" stop-opacity="0"/></linearGradient><linearGradient id="svgIDf" x1="23.302" x2="23.302" y1="29.26" y2="5.156" gradientUnits="userSpaceOnUse"><stop stop-color="#FF2657"/><stop offset=".444" stop-color="#FF2F67"/><stop offset="1" stop-color="#EC008C"/></linearGradient><linearGradient id="svgIDg" x1="16.284" x2="19.097" y1="14.728" y2="14.728" gradientUnits="userSpaceOnUse"><stop stop-color="#C01567"/><stop offset="1" stop-color="#C01567" stop-opacity="0"/></linearGradient><linearGradient id="svgIDo" x1="14.597" x2="12.803" y1="19.262" y2="16.218" gradientUnits="userSpaceOnUse"><stop stop-color="#FF757B"/><stop offset="1" stop-color="#FF757B" stop-opacity="0"/></linearGradient><linearGradient id="svgIDq" x1="15.675" x2="13.659" y1="14.573" y2="9.039" gradientUnits="userSpaceOnUse"><stop stop-color="#FF989D"/><stop offset="1" stop-color="#FF989D" stop-opacity="0"/></linearGradient><filter id="svgIDj" width="15.144" height="15.416" x="16.881" y="4.964" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_7133_6142" stdDeviation="1.5"/></filter><filter id="svgIDl" width="12.906" height="14.633" x="5.657" y="3.965" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_7133_6142" stdDeviation="1.5"/></filter><filter id="svgIDn" width="3.7" height="6.388" x="13.184" y="16.693" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_7133_6142" stdDeviation=".175"/></filter><filter id="svgIDp" width="3.547" height="7.944" x="13.39" y="8.992" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_7133_6142" stdDeviation=".175"/></filter><clipPath id="svgIDa"><path fill="#fff" d="M0 0h32v32H0z"/></clipPath></defs></g></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/no-permission.svg b/ruoyi-plus-soybean/src/assets/svg-icon/no-permission.svg
new file mode 100755
index 0000000..4c408ca
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/no-permission.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><ellipse cx="250" cy="384.61" rx="209.73" ry="94.79" fill="#f5f5f5"/><g fill="#e0e0e0"><ellipse cx="352.36" cy="395.73" rx="56.05" ry="32.36"/><path d="M91.71 412.52c-9.53 5.57-9.53 14.59 0 20.15s25 5.56 34.51 0 9.53-14.58 0-20.15-24.98-5.52-34.51 0z"/></g><g fill="#e0e0e0"><path d="M103.87 225.91v3.46c0 1.09.77 1.53 1.71 1l3.34-1.92V221l-3.34 2a3.78 3.78 0 00-1.71 2.91zM93.28 223v3.45c0 1.09.77 1.53 1.71 1l13.93-8V212L95 220a3.78 3.78 0 00-1.72 3zM100.82 262.05L85 271.18a3.8 3.8 0 00-1.71 3v3.46c0 1.09.77 1.53 1.71 1l15.82-9.13a3.78 3.78 0 001.71-3V263c0-1.06-.77-1.5-1.71-.95zM108.92 302.88l-13.93 8a3.78 3.78 0 00-1.71 3v3.46c0 1.09.77 1.53 1.71 1l13.93-8zM93.28 186.59V190c0 1.09.77 1.53 1.71 1l13.93-8v-7.4l-13.93 8a3.78 3.78 0 00-1.71 2.99zM77.44 295.91v3.46c0 1.09.76 1.53 1.71 1L95 291.23a3.77 3.77 0 001.71-3v-3.46c0-1.09-.76-1.53-1.71-1L79.15 293a3.77 3.77 0 00-1.71 2.91zM93.28 259.32v3.46c0 1.09.77 1.53 1.71 1l13.93-8v-7.41l-13.92 8a3.78 3.78 0 00-1.72 2.95zM91.94 318.09v-3.45c0-1.09-.76-1.54-1.71-1l-15.82 9.13a3.8 3.8 0 00-1.71 3v3.46c0 1.09.77 1.53 1.71 1l15.82-9.14a3.75 3.75 0 001.71-3zM92 176.33l-15.8 9.13a3.8 3.8 0 00-1.71 3v3.46c0 1.09.77 1.53 1.71 1l15.8-9.18a3.8 3.8 0 001.71-3v-3.46c.02-1.05-.71-1.49-1.71-.95zM100.82 189.31L85 198.44a3.8 3.8 0 00-1.71 3v3.46c0 1.09.77 1.53 1.71 1l15.82-9.14a3.78 3.78 0 001.71-3v-3.46c0-1.1-.77-1.54-1.71-.99zM100.82 225.68L85 234.81a3.8 3.8 0 00-1.71 3v3.46c0 1.09.77 1.53 1.71 1l15.82-9.13a3.78 3.78 0 001.71-3v-3.46c0-1.11-.77-1.55-1.71-1zM85 333.14l15.82-9.14a3.8 3.8 0 001.71-3v-3.46c0-1.09-.77-1.53-1.71-1L85 325.73a3.8 3.8 0 00-1.71 3v3.46c0 1.05.77 1.49 1.71.95zM235.65 161.92v3.46a3.77 3.77 0 01-1.7 3l-3.34 1.93v-7.41l3.34-1.92c.94-.59 1.7-.15 1.7.94zM230.61 281.09l13.93-8c.94-.55 1.7-.1 1.7 1v3.45a3.77 3.77 0 01-1.7 3l-13.93 8zM246.24 146.74v3.46a3.79 3.79 0 01-1.7 3l-13.93 8v-7.4l13.93-8c.94-.59 1.7-.15 1.7.94zM238.71 194.54l15.82-9.14c.94-.54 1.71-.1 1.71 1v3.46a3.8 3.8 0 01-1.71 3l-15.82 9.13c-1 .55-1.71.1-1.71-1v-3.49a3.75 3.75 0 011.71-2.96zM230.61 244.72l13.93-8c.94-.54 1.7-.1 1.7 1v3.45a3.77 3.77 0 01-1.7 3l-13.93 8zM246.24 110.37v3.46a3.77 3.77 0 01-1.7 3l-13.93 8v-7.4l13.93-8c.94-.59 1.7-.15 1.7.94zM262.09 201.4v3.46a3.77 3.77 0 01-1.71 3L244.56 217c-.94.55-1.7.11-1.7-1v-3.46a3.77 3.77 0 011.7-3l15.82-9.13c.95-.54 1.71-.1 1.71.99zM246.24 183.11v3.46a3.79 3.79 0 01-1.7 3l-13.93 8v-7.4l13.93-8c.94-.59 1.7-.17 1.7.94zM247.59 240.33v-3.46a3.77 3.77 0 011.71-3l15.82-9.13c.94-.55 1.71-.11 1.71 1v3.46a3.78 3.78 0 01-1.71 3l-15.82 9.14c-.95.52-1.71.08-1.71-1.01zM238.71 121.8l15.82-9.14c.94-.54 1.71-.1 1.71 1v3.46a3.8 3.8 0 01-1.71 3l-15.82 9.13c-1 .55-1.71.1-1.71-1v-3.46a3.75 3.75 0 011.71-2.99zM238.71 158.17l15.82-9.17c.94-.54 1.71-.1 1.71 1v3.46a3.8 3.8 0 01-1.71 3l-15.82 9.13c-1 .55-1.71.1-1.71-1v-3.45a3.75 3.75 0 011.71-2.97zM254.53 247.36l-15.82 9.14c-1 .54-1.71.1-1.71-1v-3.46a3.77 3.77 0 011.71-3l15.82-9.04c.94-.55 1.71-.1 1.71 1v3.46a3.78 3.78 0 01-1.71 2.9zM230.61 299.27l13.93-8c.94-.54 1.7-.1 1.7 1v3.46a3.79 3.79 0 01-1.7 3l-13.93 8z"/></g><path d="M108.92 413V165.44a12 12 0 015.42-9.38l107.12-61.85a3.83 3.83 0 013.83-.38L229 96a3.79 3.79 0 011.6 3.51v247.55l-7.81 4.51-6.34-3.67-96 55.42v7.33l-7.81 4.51z" fill="#37474f"/><path d="M134.28 395.32l-13.85 8v7.33l-7.81 4.51-3.7-2.14V165.44a11 11 0 011.6-5.35l23.58 13.62z" fill="#263238"/><path d="M112.62 167.59v247.55l7.81-4.5v-7.32l96-55.42 6.34 3.66 7.81-4.5V99.48c0-3.47-2.43-4.85-5.4-3.13L118 158.21a12 12 0 00-5.38 9.38z" fill="#455a64"/><path fill="currentColor" d="M120.43 168.9v237.48l98.67-56.96V111.95l-98.67 56.95z"/><path fill="#fff" opacity=".7" d="M120.43 168.9v237.48l98.67-56.96V111.95l-98.67 56.95z"/><g opacity=".43"><path d="M137.25 377.26V178.61l65-37.54v198.64zM140 180.19v192.34l59.59-34.4V145.8z" fill="currentColor"/><path d="M137.25 178.61v198.65l65-37.55V141.07zm62.31 159.52L140 372.53V180.19l59.59-34.39z" opacity=".1"/></g><path fill="#263238" d="M219.1 349.42V111.95l3.65-2.12.01 241.74-3.66-2.15z"/><path fill="#e0e0e0" d="M120.43 410.64l102.33-59.07-3.66-2.15-98.67 56.96v4.26z"/><g><path fill="#e0e0e0" d="M122.34 258.22l1.97 1.14.01 32.73-1.98-1.14v-32.73z"/><path fill="#fafafa" d="M124.31 259.36l10.63-6.14.01 32.73-10.63 6.14-.01-32.73z"/><path d="M135 262.23l-4.66 2.68v5.32a.64.64 0 001 .55l3.7-2.13z" opacity=".05"/><path fill="#ebebeb" d="M134.94 253.22l-1.97-1.14-10.63 6.14 1.97 1.14 10.63-6.14z"/><path d="M128.39 263.82l4.88 2.81 1.95-3.38-4.88-2.81a.94.94 0 00-1 .09 3 3 0 00-1.38 2.39 1 1 0 00.43.9z" fill="#e0e0e0"/><path opacity=".1" d="M130.29 264.91l1.77 1.03v-2.33l1.89-1.09-1.77-1.02-1.89 1.08v2.33z"/><path fill="#fafafa" d="M132.65 263.98v5.11l19.5-11.25v-5.11l-19.5 11.25z"/><path fill="#e0e0e0" d="M132.65 263.98l-1.77-1.03v5.12l1.77 1.02v-5.11z"/><path fill="#ebebeb" d="M152.15 252.73l-1.77-1.03-19.5 11.25 1.77 1.03 19.5-11.25z"/><path d="M127.84 278.23a4 4 0 011.79-3.1c1-.57 1.79-.11 1.79 1a3.92 3.92 0 01-1.08 2.52V282a1.35 1.35 0 01-.61 1.05l-.2.12c-.34.19-.61 0-.61-.35v-3.34c-.64.08-1.08-.4-1.08-1.25z" fill="#455a64"/></g><g><path d="M155.5 200.16l30.07-17.36c1.79-1 3.25-.2 3.25 1.87v20.65a7.21 7.21 0 01-3.25 5.63l-30.07 17.36c-1.79 1-3.25.19-3.25-1.88v-20.65a7.17 7.17 0 013.25-5.62z" opacity=".15"/><path d="M156.65 201l30.07-17.36c1.79-1 3.25-.2 3.25 1.87v20.65a7.21 7.21 0 01-3.25 5.63l-30.07 17.36c-1.8 1-3.25.19-3.25-1.88v-20.61a7.15 7.15 0 013.25-5.66z" fill="#455a64"/><path d="M165.59 198h-.08a.37.37 0 01-.29-.45l6.1-26.68a.36.36 0 01.35-.29.37.37 0 01.36.26l6.1 19.65a.38.38 0 11-.72.22l-5.68-18.31-5.73 25.34a.38.38 0 01-.41.26z" fill="#263238"/><path d="M157.66 220.88a.72.72 0 01-.09-.44v-10.81a1.37 1.37 0 01.09-.55.58.58 0 01.29-.31l3.27-1.89c.11-.06.18 0 .23.05a1.51 1.51 0 01.06.52v.45a2.41 2.41 0 01-.06.6.5.5 0 01-.23.31l-2.16 1.25v3L161 212c.1-.06.18 0 .22.05a1.3 1.3 0 01.07.53v.42a1.89 1.89 0 01-.07.6.55.55 0 01-.22.32l-1.94 1.08v3.28l2.17-1.26a.15.15 0 01.22 0 1.54 1.54 0 01.06.53v.45a2.34 2.34 0 01-.06.61.54.54 0 01-.22.3l-3.28 1.9c-.14.12-.24.13-.29.07zM162.78 217.6v-11a1.19 1.19 0 01.1-.53.72.72 0 01.29-.33l1.81-1q2.25-1.31 2.25 2a5.9 5.9 0 01-1 3.53v.07c.3.08.55.56.76 1.44l.68 2.95a.44.44 0 000 .1.34.34 0 010 .1c0 .27-.2.52-.61.75l-.32.19c-.35.2-.55.18-.58-.06l-.62-3a1.21 1.21 0 00-.31-.64c-.12-.08-.29-.05-.53.09l-.42.24v4.26a1 1 0 01-.59.85l-.31.17c-.38.22-.6.15-.6-.18zm2.28-7.69a1.4 1.4 0 00.51-.76 3.86 3.86 0 00.22-1.37 2 2 0 00-.21-1.1q-.21-.27-.57-.06l-.76.44v3.32zM168.65 214.21v-11a1.38 1.38 0 01.09-.54.8.8 0 01.3-.33l1.81-1q2.25-1.31 2.25 2a5.94 5.94 0 01-1 3.53v.06c.3.09.55.57.76 1.45l.67 2.94V211.53c0 .26-.2.51-.61.74l-.32.19c-.36.2-.55.18-.59-.06l-.61-3a1.19 1.19 0 00-.31-.63c-.12-.08-.3-.06-.54.08l-.41.24v4.27a1 1 0 01-.6.84l-.3.18c-.4.22-.59.17-.59-.17zm2.28-7.69a1.36 1.36 0 00.51-.76 4.07 4.07 0 00.21-1.37 1.92 1.92 0 00-.2-1.09.39.39 0 00-.57-.07l-.76.44V207zM174.29 205.36q0-6.15 2.89-7.82t2.89 4.48q0 6.18-2.89 7.85t-2.89-4.51zm3.93 1a13.59 13.59 0 00.33-3.46 9.2 9.2 0 00-.33-3.07q-.33-.77-1-.36a2.49 2.49 0 00-1 1.56 13.45 13.45 0 00-.33 3.45 9.36 9.36 0 00.33 3.08c.23.52.57.64 1 .37a2.55 2.55 0 001-1.57zM181.35 206.88v-11a1.38 1.38 0 01.09-.54.68.68 0 01.3-.33l1.8-1q2.27-1.31 2.26 2a5.94 5.94 0 01-1.05 3.53v.06c.29.09.55.57.76 1.45l.67 2.94a.3.3 0 010 .1.41.41 0 010 .11c0 .26-.2.51-.6.74l-.33.19c-.35.2-.54.18-.58-.06l-.62-3a1.13 1.13 0 00-.31-.63c-.11-.09-.29-.06-.53.08l-.41.24V206a1 1 0 01-.6.84l-.3.17c-.36.26-.55.2-.55-.13zm2.27-7.69a1.32 1.32 0 00.52-.76 4.11 4.11 0 00.21-1.37 2 2 0 00-.21-1.1.39.39 0 00-.56-.06l-.76.44v3.32z" fill="#fafafa"/></g><g><path opacity=".2" d="M112.53 369.86l-3.7-2.11.01 12.45 3.69 2.11v-12.45z"/><path fill="currentColor" d="M112.53 366.54l-4.18-2.44.01 12.45 4.17 2.44v-12.45z"/><path opacity=".2" d="M112.53 366.54l-4.18-2.44.01 12.45 4.17 2.44v-12.45z"/><path opacity=".25" d="M230.53 215.01l-118 154.85.01 12.45 117.99-154.84v-12.46z"/><path fill="currentColor" d="M230.53 211.7l-118 154.84.01 12.45 117.99-154.84V211.7z"/><path fill="#455a64" d="M230.53 224.13v-5.65l-5.85.9-4.87 6.39 10.72-1.64zM215.94 243.32l-10.76 1.65-4.88 6.4 10.77-1.66 4.87-6.39zM225.69 230.52l-10.76 1.65-4.88 6.4 10.77-1.65 4.87-6.4zM206.19 256.11l-10.76 1.66-4.88 6.39 10.77-1.65 4.87-6.4zM196.44 268.91l-10.77 1.65-4.87 6.4 10.76-1.65 4.88-6.4zM186.69 281.71l-10.77 1.65-4.87 6.4 10.76-1.65 4.88-6.4zM176.94 294.5l-10.77 1.66-4.88 6.39 10.77-1.65 4.88-6.4zM167.18 307.3l-10.76 1.65-4.88 6.4 10.77-1.65 4.87-6.4zM157.43 320.1l-10.76 1.65-4.88 6.4 10.77-1.65 4.87-6.4zM147.68 332.89l-10.77 1.66-4.87 6.39 10.77-1.65 4.87-6.4zM137.93 345.69l-10.77 1.65-4.87 6.4 10.76-1.65 4.88-6.4zM128.18 358.49l-10.77 1.65-4.88 6.4 10.77-1.66 4.88-6.39zM118.41 371.29l-5.88.9v6.8l1-1.31 4.88-6.39z"/><path opacity=".25" d="M112.53 283.52l118 15.41v12.45l-118-15.4v-12.46z"/><path fill="currentColor" d="M112.53 281.42l118 15.4v12.45l-118-15.4v-12.45z"/><path opacity=".2" d="M112.53 283.52l-3.7-2.17.01 12.46 3.69 2.16v-12.45z"/><path fill="currentColor" d="M112.53 281.42l-4.18-2.45.01 12.46 4.17 2.44v-12.45z"/><path opacity=".2" d="M112.53 281.42l-4.18-2.45.01 12.46 4.17 2.44v-12.45z"/><path d="M117.8 284.78s.06 0 .07.09a.54.54 0 010 .22l-.17 1.15a.52.52 0 01-.06.2c0 .06-.07.08-.1.08l-1.34-.17-.22 1.48 1.25.16s.06 0 .08.1a.74.74 0 010 .21l-.17 1.16a.66.66 0 01-.06.2s-.07.08-.1.07l-1.25-.16-.34 2.35a.77.77 0 01-.07.2c0 .06-.07.09-.1.08l-.59-.07-.07-.1a.49.49 0 010-.21l1-7a.59.59 0 01.06-.2s.07-.08.1-.08zM118.2 287.72a7.33 7.33 0 01.27-1.14 4.74 4.74 0 01.41-.9 1.83 1.83 0 01.53-.57.8.8 0 01.59-.11.81.81 0 01.55.31 1.82 1.82 0 01.35.68 4.55 4.55 0 01.13 1 7.67 7.67 0 010 1.16c0 .32-.09.64-.14 1s-.1.66-.15 1a7.47 7.47 0 01-.27 1.13 4.85 4.85 0 01-.41.91 2 2 0 01-.53.57.86.86 0 01-.61.16.84.84 0 01-.56-.31 1.88 1.88 0 01-.34-.68 4 4 0 01-.13-1 7.78 7.78 0 010-1.16c0-.3.08-.62.13-1s.12-.77.18-1.05zm1.69 2.13c0-.13 0-.28.07-.43s.06-.33.08-.49 0-.33.07-.49 0-.29 0-.42a2.9 2.9 0 000-.5 1.58 1.58 0 00-.05-.41.71.71 0 00-.13-.29.34.34 0 00-.48-.06.74.74 0 00-.22.24 2.29 2.29 0 00-.16.38 3.33 3.33 0 00-.12.5 3.86 3.86 0 00-.07.41c0 .16 0 .32-.08.49s0 .33-.07.49 0 .3 0 .43a2.79 2.79 0 000 .93c0 .25.17.39.36.41a.47.47 0 00.46-.31 2.93 2.93 0 00.34-.88zM123.63 285.52a1 1 0 01.5.22 1.3 1.3 0 01.34.51 2.92 2.92 0 01.13.81 5.59 5.59 0 01-.07 1.11 5.42 5.42 0 01-.34 1.34 1.91 1.91 0 01-.51.77l.18 2.66v.12a.38.38 0 01-.06.16s0 .07-.08.07l-.61-.08a.13.13 0 01-.11-.12.69.69 0 010-.2l-.16-2.39-.42-.05-.34 2.32a.52.52 0 01-.06.2c0 .06-.07.09-.1.08l-.59-.07-.07-.1a.49.49 0 010-.21l1-7a.59.59 0 01.06-.2c0-.05.07-.08.1-.08zm-1 3.23l.51.07a.37.37 0 00.33-.14 1.11 1.11 0 00.22-.61 1.27 1.27 0 000-.65.35.35 0 00-.29-.21l-.51-.07zM124.75 293.42a.12.12 0 01-.07-.1.48.48 0 010-.21l1-7a.93.93 0 01.07-.2c0-.05.07-.08.1-.07l1.44.18a.69.69 0 01.48.27 1.37 1.37 0 01.25.56 2.93 2.93 0 01.08.75 7.35 7.35 0 01-.07.82 3.19 3.19 0 01-.11.54 4 4 0 01-.12.4c0 .11-.09.19-.13.26l-.09.15a2 2 0 01.15.71 7.05 7.05 0 01-.07.94 6.25 6.25 0 01-.18.86 3.27 3.27 0 01-.29.73 1.32 1.32 0 01-.4.49.63.63 0 01-.51.14zm1-1.6l.74.1c.09 0 .17-.05.24-.17a1.24 1.24 0 00.15-.46 1.26 1.26 0 000-.48c0-.14-.1-.21-.19-.22l-.74-.09zm1.49-3.49a1.17 1.17 0 000-.46c0-.12-.09-.19-.18-.2l-.7-.09-.18 1.24.7.09c.09 0 .16 0 .23-.14a1.44 1.44 0 00.09-.44zM128.27 293.87s-.05 0-.07-.1a.49.49 0 010-.21l1-7a.59.59 0 01.06-.2c0-.05.07-.08.1-.07l.59.07a.12.12 0 01.07.1.48.48 0 010 .21l-1 7a.86.86 0 01-.07.21c0 .05-.07.08-.1.07zM132.37 286.64a.83.83 0 01.53.29 1.72 1.72 0 01.35.69 3.94 3.94 0 01.16 1 8.93 8.93 0 01-.06 1.24q0 .24-.06.42v.37c0 .12 0 .23-.06.36a3.55 3.55 0 01-.06.4 7.41 7.41 0 01-.29 1.21 4.66 4.66 0 01-.43.92 1.94 1.94 0 01-.52.57.81.81 0 01-.59.15l-1.22-.15-.07-.1a.7.7 0 010-.21l1-7a.59.59 0 01.06-.2c0-.05.07-.08.1-.07zm.15 3.06c.11-.83 0-1.28-.42-1.32l-.46-.06-.6 4.13.48.06c.37.05.63-.34.77-1.18l.06-.42.06-.4.06-.38c.02-.13.03-.27.05-.43zM136 287.1a.79.79 0 01.53.29 1.75 1.75 0 01.35.68 4.1 4.1 0 01.16 1 8.93 8.93 0 01-.06 1.24c0 .16 0 .29-.06.42s0 .25 0 .37 0 .23-.05.35 0 .26-.07.41a7.67 7.67 0 01-.29 1.21 4.66 4.66 0 01-.43.92 1.94 1.94 0 01-.52.57.81.81 0 01-.59.15l-1.22-.15-.07-.1a.49.49 0 010-.21l1-7a.59.59 0 01.06-.2s.07-.08.1-.07zm.15 3.06c.11-.83 0-1.28-.42-1.33l-.46-.05-.6 4.13.48.06c.37 0 .63-.34.77-1.18l.06-.43.02-.36.06-.38c.02-.16.03-.3.05-.46zM139.62 293.55l.07.09a.49.49 0 010 .21l-.16 1.16a.93.93 0 01-.07.2.14.14 0 01-.1.08l-2.13-.28-.07-.1a.67.67 0 010-.2l1-7a.52.52 0 01.06-.2c0-.06.07-.08.1-.08l2.09.27s.06 0 .08.1a.74.74 0 010 .21l-.17 1.15a.52.52 0 01-.06.2c0 .06-.07.08-.1.08l-1.38-.18-.17 1.18 1.28.16a.12.12 0 01.07.1.48.48 0 010 .21l-.17 1.15a.65.65 0 01-.06.21c0 .05-.07.08-.1.07l-1.28-.16-.18 1.21zM142.39 295.67s-.08 0-.1-.11a.41.41 0 010-.16l-.49-3.85-.53 3.67a.77.77 0 01-.07.2c0 .06-.07.09-.1.08l-.59-.07s-.05 0-.07-.1a.7.7 0 010-.21l1-7a.59.59 0 01.06-.2c0-.05.07-.08.1-.07l.47.06s.09 0 .11.11 0 .12 0 .16l.48 3.85.54-3.68a.59.59 0 01.06-.2c0-.05.07-.08.1-.07l.59.07s.06 0 .07.1a.48.48 0 010 .21l-1 7a.65.65 0 01-.06.21s-.07.08-.1.07zM146.72 296.22s-.06 0-.08-.09a.74.74 0 010-.21l.16-1.11-1.41-.18s-.06 0-.08-.1a.74.74 0 010-.21l.17-1.15a1.4 1.4 0 01.12-.47l1.94-3.92a.42.42 0 01.1-.15.14.14 0 01.09 0l.68.09s.06 0 .07.1a.48.48 0 010 .21l-.61 4.17.39.05a.12.12 0 01.07.1.48.48 0 010 .21l-.17 1.15a.48.48 0 01-.07.21c0 .05-.06.08-.09.07h-.39l-.16 1.11a.52.52 0 01-.07.2c0 .06-.06.08-.1.08zm.35-3.15l.3-2-1 1.92zM151.09 288.92a.79.79 0 01.54.31 1.88 1.88 0 01.31.7 4.59 4.59 0 01.1 1A11.23 11.23 0 01152 292c0 .32-.09.64-.14 1s-.1.66-.14 1a11.3 11.3 0 01-.26 1.11 4.09 4.09 0 01-.38.91 1.84 1.84 0 01-.49.59.81.81 0 01-.61.17.79.79 0 01-.55-.31 1.81 1.81 0 01-.31-.7 4.59 4.59 0 01-.1-1 11.23 11.23 0 01.07-1.13c0-.3.08-.62.13-1s.1-.67.16-1a9 9 0 01.25-1.11 4.09 4.09 0 01.38-.91 1.84 1.84 0 01.49-.59.82.82 0 01.59-.11zm-.23 4.89c.1-.62.19-1.22.27-1.83v-.5a2.55 2.55 0 000-.41.71.71 0 00-.11-.28.27.27 0 00-.21-.13.3.3 0 00-.24.07.7.7 0 00-.18.25 2.35 2.35 0 00-.14.38c0 .15-.07.32-.1.5q-.15.9-.27 1.83a4.77 4.77 0 000 .5 1.76 1.76 0 000 .41.66.66 0 00.11.28.27.27 0 00.21.13.3.3 0 00.24-.07.7.7 0 00.18-.25 1.68 1.68 0 00.14-.38c.04-.15.07-.31.1-.5zM155.51 289.6s.06 0 .07.09a.49.49 0 010 .21l-.17 1.16a.65.65 0 01-.06.21l-.09.16-.73 1.25a1.59 1.59 0 01.46.81 3.34 3.34 0 010 1.39 3.93 3.93 0 01-.62 1.81 1.09 1.09 0 01-1.06.49.92.92 0 01-.55-.26 1.49 1.49 0 01-.35-.54 2.71 2.71 0 01-.16-.72 3.56 3.56 0 010-.8.69.69 0 010-.17s.05-.06.08-.06l.57.07a.17.17 0 01.13.12 2.17 2.17 0 00.07.24.7.7 0 00.13.24.32.32 0 00.26.14.43.43 0 00.38-.13.84.84 0 00.22-.53.86.86 0 00-.06-.58.47.47 0 00-.33-.22h-.37a.12.12 0 01-.07-.1.48.48 0 010-.21l.14-.94a.65.65 0 01.06-.21l.09-.17.64-1.1-1.17-.15s-.06 0-.07-.1a.48.48 0 010-.21l.17-1.15a.4.4 0 01.07-.2c0-.06.06-.08.09-.08zM160.1 290.18s.06 0 .08.1a.74.74 0 010 .21l-.17 1.15a.58.58 0 01-.06.21s-.07.08-.1.07l-1.34-.17-.22 1.49 1.25.16s.06 0 .08.1a.74.74 0 010 .21l-.17 1.15a.65.65 0 01-.06.21c0 .05-.07.08-.1.07L158 295l-.34 2.35a.52.52 0 01-.07.2c0 .06-.06.08-.1.08l-.59-.08s-.05 0-.07-.09a.54.54 0 010-.22l1-7a.52.52 0 01.06-.2c0-.06.07-.08.1-.08zM160.47 293.12a8.91 8.91 0 01.27-1.13 4.74 4.74 0 01.41-.9 2.15 2.15 0 01.53-.58.91.91 0 01.62-.16.86.86 0 01.55.31 2 2 0 01.34.69 3.93 3.93 0 01.13 1 6.63 6.63 0 010 1.16c0 .31-.09.64-.14 1s-.1.66-.14 1a8.64 8.64 0 01-.28 1.14 4.19 4.19 0 01-.41.9 1.88 1.88 0 01-.53.58.84.84 0 01-.61.15.89.89 0 01-.56-.3 1.93 1.93 0 01-.33-.69 3.9 3.9 0 01-.14-1 7.71 7.71 0 01.06-1.16c0-.3.08-.62.13-1s.05-.7.1-1.01zm1.69 2.13c0-.13.05-.27.08-.43s0-.32.07-.49.05-.33.07-.48 0-.3.05-.42a4.77 4.77 0 000-.51 2.42 2.42 0 00-.05-.41.78.78 0 00-.14-.29.34.34 0 00-.23-.13.32.32 0 00-.25.07.83.83 0 00-.21.24 1.76 1.76 0 00-.17.39 3.19 3.19 0 00-.12.49c0 .12 0 .26-.07.42s-.05.31-.08.48l-.06.49c0 .16 0 .31-.06.44a2.73 2.73 0 000 .92c0 .25.17.39.36.41s.34-.08.47-.3a3 3 0 00.34-.89zM165.89 290.92a1 1 0 01.51.22 1.19 1.19 0 01.33.51 2.64 2.64 0 01.13.81 5.59 5.59 0 01-.07 1.11 6 6 0 01-.33 1.34 2 2 0 01-.52.77l.18 2.66a.48.48 0 010 .12.43.43 0 01-.06.17.1.1 0 01-.07.06l-.62-.08a.14.14 0 01-.11-.11.76.76 0 010-.21l-.16-2.39h-.41l-.34 2.33a.59.59 0 01-.07.2c0 .05-.06.08-.1.07l-.58-.07s-.06 0-.08-.1a.74.74 0 010-.21l1-7a.48.48 0 01.07-.21s.06-.08.09-.07zm-1 3.23l.51.07a.39.39 0 00.34-.14 1.12 1.12 0 00.21-.61 1.08 1.08 0 000-.65.35.35 0 00-.29-.21l-.5-.07zM167 298.82s-.06 0-.07-.1a.48.48 0 010-.21l1-7a.65.65 0 01.07-.21s.06-.08.1-.07l1.44.18a.73.73 0 01.48.27 1.37 1.37 0 01.25.56 3 3 0 01.07.75 5.55 5.55 0 01-.06.82 5.33 5.33 0 01-.11.55 2.54 2.54 0 01-.13.39 1.29 1.29 0 01-.12.26l-.09.15a1.8 1.8 0 01.14.71 5.5 5.5 0 01-.06.94c-.05.3-.11.58-.18.86a3.83 3.83 0 01-.29.73 1.47 1.47 0 01-.4.5.67.67 0 01-.51.14zm.95-1.6l.74.1c.09 0 .17 0 .25-.16a1.37 1.37 0 00.15-.47 1.25 1.25 0 000-.48c0-.13-.09-.21-.18-.22l-.74-.09zm1.5-3.49a1.4 1.4 0 000-.46c0-.12-.09-.19-.18-.2l-.7-.09-.18 1.24.7.09a.22.22 0 00.22-.14 1.14 1.14 0 00.16-.44zM170.55 299.27s-.06 0-.07-.1a.48.48 0 010-.21l1-7a.52.52 0 01.07-.2c0-.06.06-.08.1-.08l.58.08s.06 0 .08.09a.74.74 0 010 .21l-1 7a.4.4 0 01-.07.2c0 .06-.06.08-.09.08zM174.66 292a.82.82 0 01.53.3 1.75 1.75 0 01.35.68 4.1 4.1 0 01.16 1 9 9 0 01-.06 1.24q0 .23-.06.42c0 .13 0 .25-.05.36s0 .24-.05.36 0 .26-.07.4a7.54 7.54 0 01-.29 1.22 4.94 4.94 0 01-.43.92 1.94 1.94 0 01-.52.57.86.86 0 01-.59.15l-1.22-.16s-.05 0-.07-.09a.5.5 0 010-.21l1-7a.52.52 0 01.06-.2c0-.06.07-.08.1-.08zm.15 3.07c.11-.84 0-1.28-.42-1.33l-.46-.06-.6 4.13.48.07c.37 0 .63-.35.77-1.18l.06-.43.06-.39.06-.39c.02-.09.03-.23.05-.38zM178.25 292.5a.87.87 0 01.53.3 1.75 1.75 0 01.35.68 3.63 3.63 0 01.15 1 6.71 6.71 0 01-.06 1.24c0 .15 0 .29-.05.42s0 .25 0 .36l-.06.36c0 .12 0 .26-.06.4a6.81 6.81 0 01-.3 1.22 4.38 4.38 0 01-.42.92 1.94 1.94 0 01-.52.57.93.93 0 01-.6.15L176 300s-.06 0-.07-.1a.48.48 0 010-.21l1-7a.77.77 0 01.07-.2c0-.06.07-.08.1-.08zm.15 3.07c.1-.84 0-1.28-.43-1.33l-.46-.06-.6 4.13.48.06c.38.05.64-.34.77-1.17 0-.15.05-.3.07-.43l.06-.39c0-.13 0-.26.05-.39s.04-.27.06-.42zM181.91 299s.05 0 .07.1a.49.49 0 010 .21l-.16 1.15a.77.77 0 01-.07.2c0 .06-.07.09-.1.08l-2.12-.27s-.06 0-.08-.1a.74.74 0 010-.21l1-7a.48.48 0 01.07-.21c0-.05.06-.08.09-.07l2.09.27s.06 0 .08.09a.8.8 0 010 .22l-.17 1.15a.59.59 0 01-.06.2.14.14 0 01-.1.08l-1.38-.18-.17 1.17 1.28.17s.06 0 .07.1a.48.48 0 010 .21l-.17 1.15a.52.52 0 01-.06.2c0 .06-.07.08-.1.08l-1.29-.17-.17 1.22zM184.67 301.08a.15.15 0 01-.11-.12.73.73 0 010-.16L184 297l-.53 3.68a.4.4 0 01-.07.2c0 .06-.06.08-.09.08l-.59-.08s-.06 0-.07-.1a.48.48 0 010-.21l1-7a.52.52 0 01.07-.2c0-.06.06-.08.1-.08l.46.06a.14.14 0 01.11.11.9.9 0 010 .16l.49 3.85.53-3.67a.4.4 0 01.07-.2c0-.06.06-.08.09-.08l.59.07s.06 0 .08.1a.74.74 0 010 .21l-1 7a.4.4 0 01-.07.2c0 .06-.06.08-.09.08zM189 301.63a.12.12 0 01-.07-.1.48.48 0 010-.21l.16-1.11-1.41-.18s-.06 0-.07-.1a.48.48 0 010-.21l.17-1.15a1.61 1.61 0 01.12-.46l1.94-3.93a.42.42 0 01.1-.15.11.11 0 01.1 0l.67.08s.06 0 .08.1a.74.74 0 010 .21l-.6 4.18.38.05s.06 0 .07.09a.53.53 0 010 .22l-.17 1.15a.52.52 0 01-.06.2s-.07.08-.1.08l-.39-.05-.16 1.11a.43.43 0 01-.07.2c0 .05-.06.08-.09.07zm.36-3.16l.29-2-.95 1.92zM193.37 294.33a.76.76 0 01.54.31 1.83 1.83 0 01.31.69 4.07 4.07 0 01.1 1 8.94 8.94 0 01-.07 1.14c0 .31-.08.64-.13 1s-.1.66-.15.95a8.77 8.77 0 01-.26 1.12 4.53 4.53 0 01-.37.91 2 2 0 01-.5.59.79.79 0 01-.61.16.76.76 0 01-.55-.31 1.83 1.83 0 01-.3-.69 4.63 4.63 0 01-.11-1 9 9 0 01.08-1.14c0-.3.08-.62.13-1s.1-.66.15-1a9 9 0 01.25-1.11 4.53 4.53 0 01.38-.91 2 2 0 01.49-.59.82.82 0 01.62-.12zm-.23 4.88c.11-.61.19-1.22.27-1.82v-.51a1.67 1.67 0 000-.4.7.7 0 00-.11-.29.28.28 0 00-.21-.12.26.26 0 00-.23.06.86.86 0 00-.19.25 2.49 2.49 0 00-.14.39 4.9 4.9 0 00-.1.5c-.1.59-.19 1.2-.27 1.82v.5a1.73 1.73 0 000 .41.7.7 0 00.11.29.28.28 0 00.21.12.29.29 0 00.24-.06.7.7 0 00.18-.25 1.77 1.77 0 00.14-.39 3.15 3.15 0 00.1-.5zM197.78 295s.05 0 .07.1a.7.7 0 010 .21l-.17 1.15a.65.65 0 01-.07.21.79.79 0 01-.08.16l-.73 1.26a1.52 1.52 0 01.46.8 3.57 3.57 0 010 1.4 3.84 3.84 0 01-.62 1.8 1.13 1.13 0 01-1.07.5 1 1 0 01-.54-.26 1.48 1.48 0 01-.35-.55 2.54 2.54 0 01-.16-.72 3.07 3.07 0 010-.8.42.42 0 01.05-.17s0-.06.08-.06l.57.08q.09 0 .12.12c0 .07.05.15.08.23a.85.85 0 00.13.25.34.34 0 00.26.13.45.45 0 00.38-.12.94.94 0 00.22-.54.84.84 0 00-.06-.57.44.44 0 00-.33-.22l-.37-.05s-.06 0-.08-.1a.74.74 0 010-.21l.13-.93a.81.81 0 01.07-.22l.09-.16.64-1.11-1.17-.14a.12.12 0 01-.07-.1.48.48 0 010-.21l.17-1.16a.59.59 0 01.06-.2c0-.05.07-.08.1-.07zM202.36 295.59s.06 0 .08.09a.81.81 0 010 .22l-.17 1.15a.59.59 0 01-.06.2c0 .05-.07.08-.1.08l-1.34-.18-.22 1.49 1.25.16s.06 0 .08.1a.74.74 0 010 .21l-.17 1.16a.59.59 0 01-.06.2s-.07.08-.1.07l-1.25-.16-.34 2.35a.77.77 0 01-.07.2c0 .06-.06.08-.1.08l-.59-.08-.07-.09a.49.49 0 010-.21l1-7a.59.59 0 01.06-.2c0-.06.07-.08.1-.08zM202.76 298.53a7.18 7.18 0 01.28-1.13 3.78 3.78 0 01.41-.9 2 2 0 01.52-.58.87.87 0 01.62-.16.84.84 0 01.55.31 1.89 1.89 0 01.34.68 4.06 4.06 0 01.14 1 7.85 7.85 0 01-.06 1.16c0 .31-.08.63-.13 1s-.1.66-.15.95A7.6 7.6 0 01205 302a3.94 3.94 0 01-.41.9 1.85 1.85 0 01-.52.58.88.88 0 01-1.17-.15 1.86 1.86 0 01-.34-.69 4.52 4.52 0 01-.14-1 7.91 7.91 0 01.06-1.17c0-.29.08-.61.13-.95s.1-.68.15-.99zm1.7 2.13c0-.13 0-.27.07-.43s.05-.32.08-.49l.06-.49c0-.15 0-.29.05-.42a3 3 0 000-.5 1.64 1.64 0 00-.05-.41.71.71 0 00-.13-.29.32.32 0 00-.23-.13.36.36 0 00-.26.07.63.63 0 00-.21.24 2.7 2.7 0 00-.17.38c0 .15-.08.32-.11.5s-.05.26-.08.42 0 .31-.07.48-.05.33-.07.49 0 .3-.05.44a3 3 0 000 .92c.06.25.18.39.37.41a.47.47 0 00.46-.31 2.83 2.83 0 00.34-.88zM208.17 296.33a.91.91 0 01.5.22 1.13 1.13 0 01.33.5 2.62 2.62 0 01.14.81 6.52 6.52 0 01-.07 1.12 5.54 5.54 0 01-.34 1.34 2 2 0 01-.51.77l.18 2.65v.12a.43.43 0 01-.06.17c0 .05-.05.06-.08.06l-.61-.08c-.06 0-.1 0-.11-.11a.75.75 0 010-.2l-.16-2.39-.42-.06-.34 2.33a.52.52 0 01-.06.2c0 .06-.07.08-.1.08l-.59-.08s-.05 0-.07-.09a.79.79 0 010-.22l1-7a.52.52 0 01.06-.2c0-.06.07-.08.1-.08zm-1 3.23l.51.06a.37.37 0 00.33-.13 1.68 1.68 0 00.19-1.26.38.38 0 00-.29-.22l-.51-.06zM209.29 304.22s-.06 0-.07-.09a.48.48 0 010-.21l1-7a.77.77 0 01.07-.2c0-.06.06-.08.1-.08l1.44.19a.68.68 0 01.48.26 1.41 1.41 0 01.25.57 2.52 2.52 0 01.07.75 5.55 5.55 0 01-.06.82 4.63 4.63 0 01-.11.54 2.35 2.35 0 01-.13.4 2.17 2.17 0 01-.12.26l-.09.14a1.85 1.85 0 01.14.72 5.39 5.39 0 01-.06.93 6.09 6.09 0 01-.18.87 3.27 3.27 0 01-.29.73 1.53 1.53 0 01-.4.49.67.67 0 01-.51.14zm1-1.59l.75.09c.08 0 .16 0 .24-.16a1.31 1.31 0 00.15-.46 1.5 1.5 0 000-.48c0-.14-.1-.21-.19-.22l-.74-.1zm1.5-3.5a1.17 1.17 0 000-.46c0-.12-.09-.18-.18-.19l-.7-.09-.18 1.24.7.09c.09 0 .16 0 .23-.14a1.43 1.43 0 00.08-.45zM212.83 304.68s-.06 0-.08-.1a.74.74 0 010-.21l1-7a.43.43 0 01.07-.2c0-.05.06-.08.09-.07l.59.07s.06 0 .07.1a.48.48 0 010 .21l-1 7a.52.52 0 01-.07.2c0 .06-.06.09-.1.08zM216.92 297.45a.81.81 0 01.53.29 1.89 1.89 0 01.35.68 4.13 4.13 0 01.15 1 6.68 6.68 0 01-.06 1.24v.42l-.06.37c0 .11 0 .23-.05.35s0 .26-.06.41a7.64 7.64 0 01-.3 1.21 4.15 4.15 0 01-.42.92 2 2 0 01-.53.57.8.8 0 01-.59.15l-1.21-.15a.12.12 0 01-.07-.1.48.48 0 010-.21l1-7a.59.59 0 01.07-.2c0-.05.06-.08.1-.07zm.15 3.06c.1-.83 0-1.28-.43-1.33l-.46-.06-.6 4.14.48.06c.38.05.64-.35.77-1.18 0-.15 0-.29.07-.43l.06-.39v-.38c0-.13.09-.28.11-.43zM220.51 297.91a.81.81 0 01.54.29 2.06 2.06 0 01.35.68 4.69 4.69 0 01.15 1 6.68 6.68 0 01-.06 1.24l-.06.42c0 .13 0 .25-.05.36s0 .24 0 .36l-.06.4a8.71 8.71 0 01-.3 1.22 4.15 4.15 0 01-.42.92 2 2 0 01-.53.57.81.81 0 01-.59.15l-1.21-.16s-.06 0-.08-.09a.74.74 0 010-.21l1-7a.4.4 0 01.07-.2c0-.06.06-.08.1-.08zm.16 3.06c.1-.83 0-1.28-.43-1.33l-.46-.06-.6 4.14.48.06c.38 0 .63-.35.77-1.18 0-.15 0-.29.07-.43s0-.26.05-.39l.06-.38c.02-.13.04-.28.06-.4zM224.18 304.36l.07.09a.49.49 0 010 .21l-.16 1.16a.93.93 0 01-.07.2c0 .05-.06.08-.1.07l-2.13-.27-.07-.09a.74.74 0 010-.21l1-7a.4.4 0 01.07-.2c0-.06.06-.08.09-.08l2.09.27s.06 0 .08.1a.74.74 0 010 .21l-.17 1.15a.52.52 0 01-.06.2c0 .06-.07.08-.1.08l-1.38-.18-.17 1.18 1.28.16a.12.12 0 01.07.1.48.48 0 010 .21l-.17 1.15a.52.52 0 01-.06.2c0 .06-.07.08-.1.08l-1.29-.16-.17 1.21zM226.94 306.48c-.05 0-.08 0-.11-.11a.69.69 0 010-.16l-.49-3.85-.54 3.67a.52.52 0 01-.06.2c0 .06-.07.08-.1.08l-.59-.08s-.06 0-.07-.09a.48.48 0 010-.21l1-7a.52.52 0 01.06-.2c0-.06.07-.08.1-.08l.47.06c.05 0 .08.05.1.12a.41.41 0 010 .16l.49 3.85.53-3.68a.77.77 0 01.07-.2.14.14 0 01.1-.08l.59.08.07.1a.48.48 0 010 .21l-1 7a.52.52 0 01-.06.2c0 .06-.07.08-.1.08z" fill="#37474f"/></g><g><path d="M142.21 105.27a1.09 1.09 0 00-.17-.13l-5.26-3V77.19a1.71 1.71 0 00-.41-1.27.64.64 0 00-.17-.13l-5.62-3.24a.75.75 0 00-.82 0L122 77.11a3.55 3.55 0 00-1.07.89 7.56 7.56 0 00-.85 1.65l-15.52 38.95a10.66 10.66 0 00-.67 3.81v8.45a1.67 1.67 0 00.42 1.27.7.7 0 00.17.13l5.62 3.25a.75.75 0 00.81-.06l10.67-6.16v1.63a1.69 1.69 0 00.42 1.27l.16.13 5.63 3.25a.79.79 0 00.82-.06l6.77-3.91a2.64 2.64 0 001-1.21 4.14 4.14 0 00.41-1.75v-8.12l4.45-2.57a2.63 2.63 0 001-1.21 4 4 0 00.41-1.75v-8.44a1.7 1.7 0 00-.44-1.28z" fill="currentColor"/><path d="M126.47 81.24v.1a7.51 7.51 0 00-.79 1.55l-15.47 39a9.49 9.49 0 00-.46 1.6l-5.66-3.27a10.25 10.25 0 01.45-1.58L120 79.65a7.74 7.74 0 01.86-1.65z" opacity=".15"/><path d="M110 135.47c-.63-.37-5.32-3.08-5.55-3.2l-.17-.14a1.7 1.7 0 01-.42-1.28v-8.43a11.37 11.37 0 01.22-2.22l5.62 3.25a11.3 11.3 0 00-.21 2.22v8.44a1.73 1.73 0 00.41 1.27zM127.47 98.5v14.65l-4.74-2.73 4.74-11.92z" opacity=".3"/><path opacity=".4" d="M127.47 113.15l-7.55 4.36 2.81-7.09 4.74 2.73z"/><path d="M127.62 135.44l.1.08-5.55-3.2a.43.43 0 01-.17-.13 1.67 1.67 0 01-.42-1.27v-1.63l5.63-3.25v8.12a1.74 1.74 0 00.41 1.28z" opacity=".3"/><path d="M142 105.11a.78.78 0 00-.74.09l-4.45 2.57v-5.66z" opacity=".15"/><path d="M136.16 75.77a.79.79 0 00-.78.07l-7.81 4.51a3.76 3.76 0 00-1 .89L120.89 78a3.72 3.72 0 011-.88l7.82-4.51a.75.75 0 01.82 0z" fill="#fff" opacity=".4"/><path d="M128.61 135.51a.77.77 0 01-1-.07 1.74 1.74 0 01-.41-1.28V126l-16.3 9.41a.74.74 0 01-1-.07 1.74 1.74 0 01-.41-1.28v-8.44a10.45 10.45 0 01.67-3.81l15.47-39a7.24 7.24 0 01.85-1.64 3.51 3.51 0 011.06-.89l7.81-4.51a.75.75 0 011 .07 1.71 1.71 0 01.41 1.27v30.58l4.45-2.57a.76.76 0 011 .07 1.7 1.7 0 01.41 1.28V115a4 4 0 01-.41 1.75 2.63 2.63 0 01-1 1.21l-4.45 2.57v8.12a4.14 4.14 0 01-.41 1.75 2.64 2.64 0 01-1 1.21zm-1.14-22.36V98.5l-7.55 19zM181.49 70a34.85 34.85 0 00-1.06-7.63 11.8 11.8 0 00-2.74-5.31 6.76 6.76 0 00-1.41-1.06l-5.64-3.25a6.94 6.94 0 00-3.21-.91 12.68 12.68 0 00-6.67 2.1 24.42 24.42 0 00-6.67 5.6 32.84 32.84 0 00-4.63 7.38 38.69 38.69 0 00-2.75 8.47 55.19 55.19 0 00-1.06 8.85q-.1 3.5-.1 7.23c0 2.5 0 4.81.1 7a35.47 35.47 0 001.06 7.63 11.81 11.81 0 002.75 5.31 6.12 6.12 0 001.4 1.1c.94.56 5.18 3 5.72 3.31a7.11 7.11 0 003.13.86 12.68 12.68 0 006.67-2.1 24.46 24.46 0 006.68-5.6 33.57 33.57 0 004.63-7.38 38.67 38.67 0 002.74-8.47 54.08 54.08 0 001.06-8.85q.1-3.33.1-7.07t-.1-7.21zm-20.44 28.3a22.2 22.2 0 01-.2-2.6q-.25-6.63 0-13.38a26.75 26.75 0 01.36-3.88 17.86 17.86 0 01.93-3.44 11.62 11.62 0 011.66-2.91 8.47 8.47 0 012.25-2q.16 1.17.24 2.58.21 6.48 0 13.38a29.35 29.35 0 01-.33 3.85 15.61 15.61 0 01-.93 3.45 11.91 11.91 0 01-1.69 2.92 8.75 8.75 0 01-2.29 2.05z" fill="currentColor"/><path d="M156.43 115.65c-.93-.53-4.7-2.7-5.57-3.22a6.65 6.65 0 01-1.4-1.1 11.81 11.81 0 01-2.75-5.31 35.47 35.47 0 01-1.06-7.63c-.06-2.14-.1-4.45-.1-7s0-4.91.1-7.23a55.06 55.06 0 011.06-8.85 38.81 38.81 0 011.75-6.06l5.62 3.25a39 39 0 00-1.74 6 53.12 53.12 0 00-1.06 8.86q-.1 3.48-.11 7.23c0 2.5 0 4.81.11 7a34.34 34.34 0 001.06 7.63 11.71 11.71 0 002.74 5.3 6.54 6.54 0 001.35 1.13z" opacity=".15"/><path d="M176.06 55.85a7.13 7.13 0 00-3-.79q-2.79-.14-6.68 2.1a24.57 24.57 0 00-6.67 5.61 33.21 33.21 0 00-4.63 7.36c-.19.41-.36.81-.53 1.23l-.47 1.19-5.62-3.25c.13-.36.27-.71.41-1.06s.38-.9.59-1.35a33.3 33.3 0 014.62-7.38 24.76 24.76 0 016.68-5.6 12.59 12.59 0 016.67-2.1 6.9 6.9 0 013.21.91z" fill="#fff" opacity=".4"/><path d="M171.92 89.32a29.32 29.32 0 01-.34 3.86 15.52 15.52 0 01-.92 3.44A12 12 0 01170 98l-5.62-3.25a12.61 12.61 0 00.63-1.34 15.77 15.77 0 00.94-3.45 29.35 29.35 0 00.33-3.85q.21-6.89 0-13.38c0-.94-.13-1.8-.24-2.59l.33-.21a4.35 4.35 0 012.59-.78 2.34 2.34 0 011.66 1 6.11 6.11 0 01.9 2.38 26.61 26.61 0 01.39 3.45q.21 6.45.01 13.34z" opacity=".3"/><path d="M170 98a10.51 10.51 0 01-1 1.58 8.86 8.86 0 01-2.59 2.21 4.38 4.38 0 01-2.58.77 2.33 2.33 0 01-1.68-1 5.76 5.76 0 01-.93-2.37c-.06-.28-.1-.57-.14-.87a8.6 8.6 0 002.29-2 11.4 11.4 0 001-1.58z" opacity=".4"/><path d="M166.38 57.16q3.88-2.25 6.68-2.1a6.71 6.71 0 014.63 2 11.8 11.8 0 012.74 5.31 34.85 34.85 0 011.06 7.63q.1 3.36.1 7.11t-.1 7.07a54.08 54.08 0 01-1.06 8.85 38.67 38.67 0 01-2.74 8.47 33.57 33.57 0 01-4.63 7.38 24.46 24.46 0 01-6.68 5.6 12.68 12.68 0 01-6.67 2.1 6.68 6.68 0 01-4.63-2 11.71 11.71 0 01-2.74-5.3 34.94 34.94 0 01-1.06-7.63c-.07-2.14-.1-4.45-.1-7s0-4.9.1-7.23a54 54 0 011.06-8.85 38.93 38.93 0 012.74-8.48 33.5 33.5 0 014.63-7.37 24.57 24.57 0 016.67-5.56zm5.54 32.16q.21-6.88 0-13.38a26.64 26.64 0 00-.39-3.44 6.17 6.17 0 00-.9-2.38 2.31 2.31 0 00-1.66-1 4.44 4.44 0 00-2.59.78 9 9 0 00-2.58 2.21 11.62 11.62 0 00-1.66 2.89 17.86 17.86 0 00-.93 3.44 26.75 26.75 0 00-.36 3.88q-.25 6.75 0 13.38a21.32 21.32 0 00.34 3.46 5.52 5.52 0 00.93 2.37 2.31 2.31 0 001.68 1 4.38 4.38 0 002.58-.77 9 9 0 002.62-2.21 11.88 11.88 0 001.68-2.92 15.76 15.76 0 00.93-3.45 31.14 31.14 0 00.31-3.86zM219.68 56a6.27 6.27 0 00-1.16-.85 6.56 6.56 0 00-.76-.35 6 6 0 01.58.25l-4.27-2.48 5.3-11.28c.21-.44.43-1 .66-1.57a4.82 4.82 0 00.36-1.77v-8.36a1.68 1.68 0 00-.41-1.26.91.91 0 00-.24-.16L214.25 25a.81.81 0 00-.79.07L189 39.14a2.31 2.31 0 00-.83 1 1.49 1.49 0 00-.14.24 4.17 4.17 0 00-.41 1.74v8.28a1.66 1.66 0 00.41 1.25 1.46 1.46 0 00.21.16c.19.09 4.54 2.62 5.41 3.12 0 0-.05 0-.06-.06a.75.75 0 001 .07l3.61-2.09-1.2 2.72a17.85 17.85 0 00-.71 1.67 3.17 3.17 0 00-.14.4c0 .13-.08.26-.11.4a.11.11 0 010 .05 1.9 1.9 0 000 .25v.08a3.1 3.1 0 00-.05.6v6.76a1.73 1.73 0 00.4 1.26 1.43 1.43 0 00.22.15l5.39 3.15-.08-.06a.75.75 0 00.94 0l4.23-2.45a7.94 7.94 0 01-1.52 3.72 12.92 12.92 0 01-4.06 3.6 5.83 5.83 0 01-3.13 1.07 5.76 5.76 0 01-.66-.1l-2.44-1.41a1 1 0 00-.34-.11 2 2 0 00-1.23.36l-6.44 3.72a2.18 2.18 0 00-.79 1 .49.49 0 000 .11 4.15 4.15 0 00-.22.65 2.88 2.88 0 00-.07.65 12.08 12.08 0 001.09 5.06 7.11 7.11 0 003 3.28c.82.46 4.08 2.38 5.34 3.09-.18-.11-.34-.24-.5-.36a6 6 0 00.72.48 7.9 7.9 0 004.77.78 17.12 17.12 0 006.45-2.42 29.39 29.39 0 0015.12-26.58c0-4.1-.83-6.9-2.5-8.42z" fill="currentColor"/><path d="M188.18 40.22a.64.64 0 000-.07l-.13.25a4 4 0 00-.41 1.74v8.32a1.63 1.63 0 00.41 1.26.7.7 0 00.2.15l5.43 3.13a.12.12 0 00-.06-.06 1.65 1.65 0 01-.41-1.26v-8.34a3.87 3.87 0 01.41-1.72 3 3 0 01.28-.46c-.06.09-.13.17-.19.28zM201.46 62.06l-5.56-3.21v6.93a1.73 1.73 0 00.4 1.26 1.43 1.43 0 00.22.15c.21.11 4.83 2.78 5.46 3.15l-.07-.07a1.65 1.65 0 01-.41-1.24v-6.8s-.04-.11-.04-.17z" opacity=".15"/><path d="M201.8 60.45c.24-.61.48-1.16.72-1.67l5.41-11.54-9.72 5.62-1.21 2.71c-.24.5-.48 1-.71 1.67a3 3 0 00-.14.39c0 .14-.08.26-.11.4v.06a2 2 0 010 .24.19.19 0 010 .08 2.25 2.25 0 000 .44l5.56 3.21a5.05 5.05 0 01.2-1.61z" opacity=".3"/><path d="M186.45 79.66v.11a3.41 3.41 0 00-.22.66 3 3 0 00-.08.64 12.13 12.13 0 001.1 5.07 7 7 0 003 3.27l5.33 3.1a.45.45 0 01-.1-.09 7.07 7.07 0 01-2.66-3.06 12 12 0 01-1.1-5.07 3.33 3.33 0 01.33-1.41 3 3 0 01.27-.44l-5.57-3.2a2.77 2.77 0 00-.3.42z" opacity=".15"/><path d="M197.71 76.16l-2.44-1.42a1.13 1.13 0 00-.34-.1 2.08 2.08 0 00-1.23.35l-6.44 3.73a1.76 1.76 0 00-.52.53l5.57 3.2a1.9 1.9 0 01.53-.53l6.43-3.71c.71-.41 1.26-.48 1.63-.19l-3.17-1.85z" opacity=".3"/><path d="M203.92 79.47a5.75 5.75 0 003.15-1.07 12.88 12.88 0 004.05-3.6 8.13 8.13 0 001.62-4.94c0-1.82-.55-2.86-1.62-3.08a6 6 0 00-4.05 1.1 7.85 7.85 0 01-1.52 3.71 12.88 12.88 0 01-4.05 3.6 5.71 5.71 0 01-3.14 1.06 4.25 4.25 0 01-.65-.09l3.17 1.84 1.26.89a3.75 3.75 0 001.78.58z" opacity=".4"/><path d="M214.07 52.55s3.08 1.8 4.27 2.48a.46.46 0 00-.14-.05.34.34 0 00-.15-.07 7.72 7.72 0 00-4.86-.48zM216.76 26.52l-2.52-1.47a.81.81 0 00-.79.07L189 39.21a2.36 2.36 0 00-.84.94.64.64 0 010 .07l5.55 3.22c.06-.11.13-.19.19-.28a3 3 0 01.19-.3 1.79 1.79 0 01.49-.43L219 28.34a.82.82 0 01.71-.11z" fill="#fff" opacity=".4"/><path d="M219 28.26a.75.75 0 011 .07 1.68 1.68 0 01.41 1.26v8.34a4.82 4.82 0 01-.41 1.77c-.23.61-.45 1.13-.66 1.57l-5.3 11.28-.88 1.88a7.58 7.58 0 015.33.71 6.63 6.63 0 011.16.85c1.67 1.53 2.5 4.33 2.5 8.42A29.39 29.39 0 01207.06 91a17.12 17.12 0 01-6.51 2.42 7.9 7.9 0 01-4.77-.78 7.12 7.12 0 01-3-3.27 12.2 12.2 0 01-1.1-5.07 3.3 3.3 0 01.33-1.41 2.07 2.07 0 01.8-1l6.43-3.72c.72-.41 1.26-.48 1.64-.2s.79.58 1.25.89a3.68 3.68 0 001.78.58 5.86 5.86 0 003.14-1.06 13.09 13.09 0 004.06-3.61 8 8 0 001.61-4.93c0-1.84-.53-2.86-1.61-3.08a6.09 6.09 0 00-4.06 1.08l-4.23 2.45a.75.75 0 01-1-.07 1.69 1.69 0 01-.4-1.26v-6.73a5 5 0 01.35-1.78c.24-.61.48-1.16.72-1.67l5.41-11.54-13.33 7.7a.75.75 0 01-1-.07 1.69 1.69 0 01-.41-1.26v-8.33a4.06 4.06 0 01.41-1.73 2.61 2.61 0 011-1.19z" fill="currentColor"/></g><g><path d="M414.41 218.19l7.08-.31 4.58 8.68s-2.85 1.48-13.05 1.6z" fill="#f28f8f"/><path d="M369.6 197.3c2.7 1.27 4.17 2.74 7.07 5.1 3.58 2.92 15.93 11.31 18.58 12a170.44 170.44 0 0021.36 3.44s-1.15 7 .34 10.45c-6.34.28-22.5.82-28.14-.51-6-1.42-12.66-4.8-17.78-7.77s-3.98-20.19-1.43-22.71z" fill="currentColor"/><path d="M369.6 197.3c2.7 1.27 4.17 2.74 7.07 5.1 3.58 2.92 15.93 11.31 18.58 12a170.44 170.44 0 0021.36 3.44s-1.15 7 .34 10.45c-6.34.28-22.5.82-28.14-.51-6-1.42-12.66-4.8-17.78-7.77s-3.98-20.19-1.43-22.71z" fill="#fff" opacity=".7"/><path d="M371 220.06c5.12 3 11.74 6.35 17.78 7.77 4.08 1 13.68.95 21.08.76a118.81 118.81 0 01-19-3.06 73.51 73.51 0 01-11.77-6.2c-3.33-2.37-3.53-6.62-5.23-12.77-2.26-8.16-4.25-9.26-4.25-9.26-2.56 2.52-3.69 19.79 1.39 22.76z" opacity=".1"/><path d="M409.89 228.59a14.48 14.48 0 01.31-8.69s-2.06 2-1.62 8.72z" fill="currentColor"/><path d="M409.89 228.59a14.48 14.48 0 01.31-8.69s-2.06 2-1.62 8.72z" opacity=".1"/><path d="M424.39 226.71c6-.21 8.23-3.25 9.07-8.52s.69-12.16.76-14-.47-2.46-1.2-2.56c-.27 0-1.43.25-1.56 2.2-.24 3.56-.33 5.75-1 5.72s-.57-2.66-.51-6.25c0-2.42 0-4.46-1.4-4.46s-1.79 1.44-1.86 4.71 0 5.7-.86 5.77c-.7 0-.56-3.43-.72-5.67s-.15-3.58-1.55-3.55-1.61 1.83-1.56 4a86.09 86.09 0 01-.23 9c-.39 1.29-1.73-1.89-2.71-3.2-1.61-2.15-3.58-1.17-2.78.79a38.83 38.83 0 012.18 7.4c.42 2.49 1.43 8.64 5.93 8.62z" fill="#ffa8a7"/><path d="M424.36 215.82s3.62 3.77 2.32 7.8a5.35 5.35 0 00.65-5.4 4.09 4.09 0 00-2.97-2.4z" fill="#f28f8f"/><path d="M345.39 404.83c-.13 1.14-.28 4.34-6.07 5.13s-9.44-1.12-11-3.9-1.19-5.06-1.44-7.64-2.37-3.95-3-5.28c-.9-1.95.42-5 .42-5zM387.63 395.77a3.49 3.49 0 01-.38 2.8 12.16 12.16 0 01-9.28 2.48 22.39 22.39 0 01-9.81-3.85 12.92 12.92 0 00-7.58-2.18c-2.79-.1-4.82-.72-5-1.45-.37-1.28-.37-2.1.23-2.06z" fill="#263238"/><path d="M345 402.2a31.89 31.89 0 00-2.66-3.17 28.64 28.64 0 01-6-15.36c0-.31-.06-.91-.37-1.1s-.63.08-1 .22a6 6 0 01-1.79.47 33.38 33.38 0 01-4.83-.19c-.4.05-.52 1.29-.49 2.62-.73-.46-1-1.38-1.62-1.91-.36-.29-.51-.3-.51-.77v-.9c-.52-.15-1 1.87-1.4 3.41-.5 1.78-1.12 3.79-.42 5.6a15.72 15.72 0 001.63 2.8 11.62 11.62 0 012.1 5.12c.08.81 0 1.64.13 2.45a7.16 7.16 0 005.06 5.87c3.52 1.11 8.82.93 11.93-1.53 1.09-.83.72-2.68.24-3.63zM385.29 392c-.77-.24-1.53-.52-2.28-.82a41.85 41.85 0 01-6.07-3c-.85-.52-1.69-1.06-2.51-1.63s-1.65-1.27-2.47-1.93a8.81 8.81 0 01-1.18-1.26 13 13 0 01-1.74-2.82 1 1 0 00-.43-.57.84.84 0 00-.82.17 4 4 0 01-.64.43l-.68.28a7.85 7.85 0 01-2.28.56c-2.94.26-3-.33-3.16.62a7.93 7.93 0 000 1.06c-1.55-.25-2.77-.76-2.84-1.34l-.18-1.88c-2.31-.32-2.08 2.38-2.36 5.34-.23 2.54-.82 4.73-.12 6.58 2.33 2 6.46 1.54 8.81 1.93 3.32.55 4.88 3 9.5 4.61 7.44 2.58 12.15-.4 13.54-1.69.9-1.64 1.08-3.59-2.09-4.64z" fill="#455a64"/><path d="M329.2 250.14c-.93 9-1.91 22.55-2.29 33.12-.7 19.86-.43 44.69-.43 44.69a68.59 68.59 0 00-2.14 12.73c-.47 12.69-.38 44.1-.38 44.1s5.26 5.3 13.13 1.14c0 0 7-42.87 8.92-55.38 1.87-11.93 5.5-38 5.5-38l1.64 36.05a52.37 52.37 0 00-1.23 16.26c.42 5.81 3.74 37.44 3.74 37.44 3 2.84 11.84 1.3 13.77-1 0 0 3.09-45.34 3.43-51.47.74-13.26 1-63.83 0-71.5s-43.66-8.18-43.66-8.18z" fill="#263238"/><path d="M351.51 292.56l2.31-15.29a30.68 30.68 0 0011-4.64s-1.8 4.08-8.82 7l-2.28 14.23-.55 34.75z"/><path d="M340 155.33s-.21 0-.54.11a2.48 2.48 0 00-1.33-2.8 2.67 2.67 0 00-3.45 1.41l3.76 1.73a5.24 5.24 0 00-2.86 2.44c-1 2.12.33 6.64 1.7 12.22a79 79 0 003 10.34c.63 1.63 1.77 1.68 1.77 1.68v-5.57l-.24-4.61s3-4.05 3.29-7.37c.41-4.26-.48-6-.48-6z" fill="#263238"/><path d="M342 171.54c-1.11 1-2-1.53-2.95-2.51s-4-2.3-5.5.91 1.39 7.71 3.68 8.77c3.34 1.55 4.84-1.82 4.84-1.82v13.93c3.52 6.35 11.29 6.14 14.89 7a12.33 12.33 0 00-.14-6.25v-4.54a16.7 16.7 0 004.6.25c3-.48 4.67-3 5.58-6.28 1.47-5.29 1.77-14.18-.27-24.45-3.4-2.63-14.92-2.21-22.06 2.32.52 9.13-1.53 11.7-2.67 12.67z" fill="#ffa8a7"/><path d="M356.82 187.06s-6.79-1.34-9.17-2.6a7.88 7.88 0 01-3.29-3.23 10.64 10.64 0 001.88 3.82c1.74 2.21 10.59 3.82 10.59 3.82z" fill="#f28f8f"/><path d="M353.73 169.64a1.45 1.45 0 11-1.45-1.45 1.45 1.45 0 011.45 1.45zM365.24 168.58a1.45 1.45 0 11-1.45-1.45 1.45 1.45 0 011.45 1.45zM351.77 165.78l-3 1.59a1.79 1.79 0 01.75-2.37 1.67 1.67 0 012.25.78z" fill="#263238"/><path d="M354.48 178.6l5.09 1.32a2.57 2.57 0 01-3.15 2 2.75 2.75 0 01-1.94-3.32z" fill="#b16668"/><path d="M355 179.67a3.05 3.05 0 00-.52.05 2.73 2.73 0 002 2.17 2.41 2.41 0 001.28 0 2.77 2.77 0 00-2.76-2.22z" fill="#f28f8f"/><path d="M366.44 166.06l-3.12-1.48a1.66 1.66 0 012.25-.86 1.82 1.82 0 01.87 2.34z" fill="#263238"/><path fill="#f28f8f" d="M358.23 167.93l.91 9.31 4.65-2.09-5.56-7.22z"/><path d="M336.08 190.82c-6.18 2.21-10.77 4.37-15.06 8.05 0 0 4.44 19.27 7.22 29.88 2.52 9.67.16 29.37.16 29.37s3.89 5.88 26 5.24C366.9 263 373 259 373 259s1.06-21.73.58-39.9c-.38-14.62-1.44-18.15-3.93-21.81-1.8-1.22-11.75-4.9-11.75-4.9a26.09 26.09 0 00-.86 5.45c-3.68-5.66-20.96-7.02-20.96-7.02z" fill="currentColor"/><path d="M336.08 190.82c-6.18 2.21-10.77 4.37-15.06 8.05 0 0 4.44 19.27 7.22 29.88 2.52 9.67.16 29.37.16 29.37s3.89 5.88 26 5.24C366.9 263 373 259 373 259s1.06-21.73.58-39.9c-.38-14.62-1.44-18.15-3.93-21.81-1.8-1.22-11.75-4.9-11.75-4.9a26.09 26.09 0 00-.86 5.45c-3.68-5.66-20.96-7.02-20.96-7.02z" fill="#fff" opacity=".3"/><path d="M333.15 240.67c-.26-6.51-1.67-18.85-1.57-28.2l-4.41-6.69-2.57 8.31c1.23 5.1 2.56 10.54 3.64 14.66 2.52 9.67.16 29.37.16 29.37s3.89 5.88 26 5.24h.4a37.07 37.07 0 01-11.87-3.36c-6.87-3.4-9.52-12.81-9.78-19.33z" opacity=".1"/><path d="M342.1 184.93V186c.2 1 2.25 3.45 7.41 5.77a17.37 17.37 0 017.48 6.06s-1.76-2.71-3.93-1.28-4.37 5-5.49 5-4.86-3.57-7.79-6.63c-2.45-2.58-3.7-4.13-3.7-4.13s4.01-5.79 6.02-5.86zM357 197.85c1.57-3.76 4.4-.55 4.58-.53.82.05-1.79-6.86-4.73-8.76v3z" fill="currentColor"/><path d="M342.1 184.93V186c.2 1 2.25 3.45 7.41 5.77a17.37 17.37 0 017.48 6.06s-1.76-2.71-3.93-1.28-4.37 5-5.49 5-4.86-3.57-7.79-6.63c-2.45-2.58-3.7-4.13-3.7-4.13s4.01-5.79 6.02-5.86zM357 197.85c1.57-3.76 4.4-.55 4.58-.53.82.05-1.79-6.86-4.73-8.76v3z" fill="#fff" opacity=".65"/><path d="M361.11 262.77c.26-13.86.62-49.46-4.12-64.92 0 0 4.47 32.91 2.05 65.19q1.08-.12 2.07-.27z" fill="currentColor"/><path d="M361.11 262.77c.26-13.86.62-49.46-4.12-64.92 0 0 4.47 32.91 2.05 65.19q1.08-.12 2.07-.27z" opacity=".1"/><path d="M338.56 214.48a38.54 38.54 0 0014.09-1.7l.22 2.72a35 35 0 01-14 1.46z" fill="currentColor"/><path d="M338.56 214.48a38.54 38.54 0 0014.09-1.7l.22 2.72a35 35 0 01-14 1.46z" opacity=".1"/><path d="M373.17 208.11a.2.2 0 00-.07-.2c-.05 0-.38-.23-.43-.26-.05 0-.12 0-.22.06a3.15 3.15 0 01-3.12.34 2.24 2.24 0 01-.35-.34.32.32 0 00-.09-.08l-.44-.27c-.1-.05-.2 0-.27.14-.93 2.11-2.84 4.22-4.08 4.72a.7.7 0 00-.25.17.53.53 0 00-.15.35c-.09 2.75 1.27 7.19 4.07 8.76a5.58 5.58 0 00.52.32c4.03-3.97 4.77-10.45 4.88-13.71z" fill="#ebebeb"/><path d="M367.74 221.45c-2.8-1.57-4.16-6-4.07-8.76a.55.55 0 01.06-.22l.43.28a.46.46 0 00-.05.21c-.11 2.6 1.12 6.72 3.63 8.49z" fill="#e0e0e0"/><path d="M373.17 208.11c-.11 3.26-.85 9.74-4.91 13.66-2.86-1.53-4.24-6-4.15-8.81a.53.53 0 01.15-.35.74.74 0 01.25-.17c1.24-.5 3.15-2.61 4.08-4.72.07-.15.16-.19.26-.14a.32.32 0 01.09.08 3 3 0 003.91.28.25.25 0 01.22-.07.2.2 0 01.1.24z" fill="#fafafa"/><path d="M368.85 207.58c-.1-.05-.19 0-.26.14-.93 2.11-2.84 4.22-4.08 4.72a.74.74 0 00-.25.17.53.53 0 00-.15.35c-.09 2.77 1.29 7.28 4.15 8.81a30.26 30.26 0 00.59-14.19z" fill="#f0f0f0"/><path d="M346.57 160.89s9.32 4.54 19.32.61c10.27-4.05 1.27-6.92 1.27-6.92z" fill="#263238"/><path d="M367.42 141c-7.75-1.72-16.22-2.63-26.39 1.54s-12.73 10.4-12.73 10.4 9.86 3.38 18.19 1.55S367.42 141 367.42 141z" fill="#455a64"/><path d="M334.13 158.62s-5.45-4.43-5.83-5.73a34.25 34.25 0 0011.82-1.45c6.68-2 13.93-8.7 21.41-10.25s10.4 2 10.4 2a35.42 35.42 0 01-4.74 8.65s-2.61 5.09-11.41 6.75-21.65.03-21.65.03z" fill="#37474f"/><path d="M334.13 158.62a14.94 14.94 0 00.42 1.74 8.37 8.37 0 00.81 1.47c3.12.61 10.62 1.1 20.51-1.26 6.2-1.49 9.06-3.21 10.24-4.45a3.36 3.36 0 001.2-2.9l-.12-1.37s-2.06 2.73-12.2 5.26a70.36 70.36 0 01-20.86 1.51z" fill="currentColor"/><path d="M334.13 158.62a14.94 14.94 0 00.42 1.74 8.37 8.37 0 00.81 1.47c3.12.61 10.62 1.1 20.51-1.26 6.2-1.49 9.06-3.21 10.24-4.45a3.36 3.36 0 001.2-2.9l-.12-1.37s-2.06 2.73-12.2 5.26a70.36 70.36 0 01-20.86 1.51z" opacity=".05"/><path d="M339.53 295.16c-1-1.46-3.31-2.78-4.17-3.64-1.52-1.54-3.74-4.79-3.41-5.39s1.26.18 3.2.17c3 0 3.82-1.18 3.79-2.28 0-.86-1.83-.7-4-1.18-1.91-.61-4.2-2.76-5.45-4.45s-2.26-6.1-2.26-6.1-6.49.09-11 3.16c0 0 1.29 6.7 2.77 11.45 1.65 5.33 5.39 11.21 11.08 12.53a13.83 13.83 0 007.16-.36 5.29 5.29 0 001.9-.94 2 2 0 00.39-2.97z" fill="#ffa8a7"/><path d="M321 198.87s-1-.2-2.68 3.66a61.94 61.94 0 00-3.27 11.53c-.67 3.22-3.87 20.13-3.87 27.4s2.17 21.88 5 34.33c3.48.35 9.46-.55 11.46-2.83-1.31-12.42-2.41-18.81-2.11-28.58 2-14.5 7.87-29.69 8.12-31.38.53-3.71-5.33-15.37-12.65-14.13z" fill="currentColor"/><path d="M321 198.87s-1-.2-2.68 3.66a61.94 61.94 0 00-3.27 11.53c-.67 3.22-3.87 20.13-3.87 27.4s2.17 21.88 5 34.33c3.48.35 9.46-.55 11.46-2.83-1.31-12.42-2.41-18.81-2.11-28.58 2-14.5 7.87-29.69 8.12-31.38.53-3.71-5.33-15.37-12.65-14.13z" fill="#fff" opacity=".65"/><path d="M316.27 260c-1.74-8.5-4.59-25.14-2.64-38.23-1.12 6.43-2.43 15-2.43 19.69 0 7.27 2.17 21.88 5 34.33a20.33 20.33 0 004.16-.08c-1.25-4.11-3.02-10.47-4.09-15.71z" opacity=".1"/><path d="M314.94 269.69c2.84.18 7.89-.62 10-2 0 0-3.68 1.63-10.19.7z" fill="currentColor"/><path d="M314.94 269.69c2.84.18 7.89-.62 10-2 0 0-3.68 1.63-10.19.7z" opacity=".1"/><g><path d="M364.79 145.63v-.15l-.32-.2h-.17a2.37 2.37 0 01-2.33.25 1.6 1.6 0 01-.26-.26.19.19 0 00-.07-.06l-.32-.2c-.07 0-.14 0-.19.11a7.61 7.61 0 01-3.05 3.52.5.5 0 00-.19.13.41.41 0 00-.11.26c-.07 2 .95 5.37 3 6.54a3 3 0 00.38.24c3-2.91 3.55-7.74 3.63-10.18z" fill="#ebebeb"/><path d="M360.74 155.59c-2.1-1.17-3.11-4.49-3-6.54a.42.42 0 010-.16l.33.2a.29.29 0 000 .16c-.07 1.94.79 5.02 2.67 6.34z" fill="#e0e0e0"/><path d="M364.79 145.63c-.08 2.44-.63 7.27-3.67 10.2-2.13-1.14-3.16-4.51-3.09-6.58a.36.36 0 01.11-.26.59.59 0 01.18-.13 7.5 7.5 0 003.05-3.52c.05-.11.13-.15.2-.11a.19.19 0 01.07.06 2.19 2.19 0 002.92.21c.07-.05.12-.06.16 0s.08.05.07.13z" fill="#fafafa"/><path d="M361.57 145.23c-.07 0-.15 0-.2.11a7.5 7.5 0 01-3.05 3.52.59.59 0 00-.18.13.36.36 0 00-.11.26c-.07 2.07 1 5.44 3.09 6.58a22.54 22.54 0 00.45-10.6z" fill="#f0f0f0"/></g></g><g><path d="M349.86 130l-5.46-12.44 4.39-12.67v-7.81l-13.44 7.76v7.76l5.78 12.18a.87.87 0 00.39.35z" fill="currentColor"/><path d="M349.86 130l-5.46-12.44 4.39-12.67v-7.81l-13.44 7.76v7.76l5.78 12.18a.87.87 0 00.39.35z" opacity=".2"/><path d="M390.07 55.14a8.93 8.93 0 00-4-7l-3.28-1.89a8.88 8.88 0 00-8.06 0l-77.12 44.61a8.9 8.9 0 00-4 7v30.74a8.93 8.93 0 004 7l3.28 1.89a8.88 8.88 0 008.06 0l34.84-20 5.77 12.17a.86.86 0 001.55-.15l6.1-19.77L386 92.88a8.91 8.91 0 004-7z" fill="currentColor"/><path d="M293.58 128.6a8.93 8.93 0 004 7l3.28 1.89a9 9 0 007.37.33c-1.89.71-3.34-.36-3.34-2.65v-30.79a8.2 8.2 0 011.18-4l-11.34-6.55a8.26 8.26 0 00-1.18 4z" opacity=".2"/><path d="M389.94 54c-.47-1.71-2-2.25-3.89-1.18L309 97.41a7.84 7.84 0 00-2.84 3l-11.33-6.54a8 8 0 012.83-3l77.08-44.6a9 9 0 018.07 0l3.29 1.9a8.93 8.93 0 013.84 5.83z" fill="#fff" opacity=".5"/><path d="M310.48 123.13v-16.25a2.14 2.14 0 01.15-.88 1.11 1.11 0 01.47-.51l4.61-2.67c.17-.09.29-.06.37.1a2.33 2.33 0 01.12.91v.8a3.6 3.6 0 01-.12 1.05.84.84 0 01-.37.52l-2.61 1.51v3.77l2.46-1.42c.17-.1.29-.07.37.09a2.44 2.44 0 01.11.92v.8a3.74 3.74 0 01-.11 1 .87.87 0 01-.37.52l-2.46 1.42v6.76a1.64 1.64 0 01-1 1.39l-.67.38c-.62.43-.95.34-.95-.21zM317.25 111q0-9.22 4.39-11.77 2.14-1.23 3.26.37c.74 1.06 1.12 3.18 1.12 6.34a21.71 21.71 0 01-1.12 7.64 7.75 7.75 0 01-3.26 4.13q-4.38 2.5-4.39-6.71zm5.38 2.24a6.69 6.69 0 00.55-2.11 27.55 27.55 0 00.19-3.66 22.45 22.45 0 00-.19-3.41 2.52 2.52 0 00-.55-1.48c-.25-.2-.57-.18-1 0a2.53 2.53 0 00-1 1.08 7 7 0 00-.57 2.12 27.42 27.42 0 00-.19 3.64 22.69 22.69 0 00.19 3.42 2.51 2.51 0 00.57 1.48.83.83 0 001 0 2.63 2.63 0 001-1.12zM327.56 113.27V97a2 2 0 01.16-.88 1.11 1.11 0 01.51-.54L331 94a2.29 2.29 0 012.48-.26c.63.43.94 1.55.94 3.37a9 9 0 01-1.59 5.28v.11a1.13 1.13 0 01.67.64 4.44 4.44 0 01.45 1.29l1 4.38.05.28c0 .46-.34.89-1 1.29l-.58.34c-.59.34-.93.31-1-.11l-.89-4.39a1.08 1.08 0 00-.34-.6c-.13-.08-.33 0-.61.12l-.4.23v5.83a1.67 1.67 0 01-1 1.41l-.57.33c-.72.36-1.05.28-1.05-.27zm3.52-11.63a1.69 1.69 0 00.62-.93 5.19 5.19 0 00.26-1.8 2.64 2.64 0 00-.26-1.44.47.47 0 00-.68-.11l-.88.51v4.31zM336.35 108.72a1.09 1.09 0 01-.15-.7V92a2.09 2.09 0 01.15-.85 1.11 1.11 0 01.47-.51l3-1.73c2.35-1.36 3.52-.58 3.52 2.32a8.92 8.92 0 01-1.51 5.13v.09a1.28 1.28 0 011.34.73 5 5 0 01.49 2.48 9.46 9.46 0 01-1 4.24 7 7 0 01-2.84 3l-3.05 1.77c-.16.15-.32.15-.42.05zm3.49-12.47a1.85 1.85 0 00.79-1 5.05 5.05 0 00.26-1.82 2.79 2.79 0 00-.24-1.44q-.24-.34-.78 0l-1.19.69v4.25zm.12 7.51a2.12 2.12 0 00.91-1.06 5.23 5.23 0 00.29-2 2.65 2.65 0 00-.28-1.52c-.19-.23-.5-.22-.92 0l-1.28.73v4.52zM345.15 103.11V86.6a1.62 1.62 0 011-1.41l.71-.41c.63-.37.95-.26.95.3v16.51a1.63 1.63 0 01-.95 1.41l-.71.4c-.68.38-1 .28-1-.29zM349.78 101a1 1 0 01-.16-.7v-16a2 2 0 01.16-.88 1 1 0 01.46-.51l3.19-1.84q2.18-1.26 3.21.39c.68 1.1 1 3.17 1 6.19a22.19 22.19 0 01-1 7.38 7.47 7.47 0 01-3.21 4.1l-3.19 1.83c-.24.11-.37.12-.46.04zm3.56-5.19a3.08 3.08 0 001.28-2 20.41 20.41 0 00.38-4.59 14.66 14.66 0 00-.38-4.15c-.26-.7-.68-.89-1.28-.54l-1.07.61V96.4zM359.44 95.39a1.11 1.11 0 01-.15-.7v-16a2.14 2.14 0 01.15-.88 1.11 1.11 0 01.47-.51l3.19-1.84q2.17-1.26 3.2.39t1 6.19a22 22 0 01-1 7.38 7.41 7.41 0 01-3.2 4.1l-3.19 1.83c-.21.13-.37.14-.47.04zm3.57-5.2a3.07 3.07 0 001.28-2 20.48 20.48 0 00.38-4.59 14.66 14.66 0 00-.38-4.15c-.26-.71-.69-.89-1.28-.55l-1.07.62v11.3zM369.11 89.81a1.11 1.11 0 01-.15-.7v-16a2.12 2.12 0 01.15-.87 1.18 1.18 0 01.47-.52l4.94-2.85a.23.23 0 01.37.09 2.39 2.39 0 01.12.92v.8a3.64 3.64 0 01-.12 1 .87.87 0 01-.37.52l-2.95 1.8v3.74l2.62-1.51c.17-.09.29-.06.37.1a2.3 2.3 0 01.12.91V78a3.6 3.6 0 01-.12 1.05.84.84 0 01-.37.52l-2.62 1.51v4.18l3-1.71c.17-.1.29-.07.36.1a2.63 2.63 0 01.11.92v.8a4 4 0 01-.11 1 .82.82 0 01-.36.52l-5 2.86c-.2.15-.36.16-.46.06zM376.53 85V68.48a1.61 1.61 0 011-1.38l.43-.26a1 1 0 01.54-.16c.13 0 .24.14.33.38l3 6.67a13.89 13.89 0 01.57 1.69l.07-.1c-.09-.84-.14-1.49-.14-2v-8.19a1.62 1.62 0 011-1.38l.41-.23c.64-.38 1-.29 1 .26v16.51a1.64 1.64 0 01-1 1.39l-.32.18a1.06 1.06 0 01-.56.18c-.14 0-.26-.14-.35-.38l-3.2-6.92a10.16 10.16 0 01-.45-1.39l-.08.12a14.88 14.88 0 01.13 1.68v8.49a1.62 1.62 0 01-1 1.39l-.4.23c-.65.37-.98.28-.98-.26z" fill="#455a64"/></g><g><path d="M124.7 425.24c8.41-8.69 13.36-44 5.15-50.91H88.08c-8.2 6.88-3.27 42.21 5.15 50.9l.23.24.29.28a8.57 8.57 0 00.76.69l.2.16.63.48a10 10 0 001.09.7c6.92 4.05 18.15 4.05 25.07 0a10 10 0 001.09-.7c.21-.15.4-.3.6-.46l.24-.19c.27-.22.51-.44.74-.66l.33-.33z" fill="#455a64"/><path d="M91.71 371.55c-9.53 5.57-9.53 14.59 0 20.15s25 5.56 34.51 0 9.53-14.58 0-20.15-24.98-5.55-34.51 0z" fill="#455a64"/><path d="M91.71 371.55c-9.53 5.57-9.53 14.59 0 20.15s25 5.56 34.51 0 9.53-14.58 0-20.15-24.98-5.55-34.51 0z" fill="#fff" opacity=".1"/><path d="M96.29 374.23c-7 4.08-7 10.71 0 14.79s18.35 4.09 25.35 0 7-10.71 0-14.79-18.35-4.09-25.35 0z" fill="#263238"/><path d="M121.63 381.25c-7-4.09-18.34-4.09-25.34 0a11.69 11.69 0 00-4.19 3.89 11.76 11.76 0 004.19 3.86c7 4.09 18.35 4.09 25.34 0a11.72 11.72 0 004.2-3.88 11.65 11.65 0 00-4.2-3.87z" fill="#f5f5f5"/><g><path d="M85.81 305.77l16.7 11.42-15.79-6a28.78 28.78 0 003.12 12.14s10.43 5.06 16.1 10.4l-13.89-4.37a15.32 15.32 0 00.43 7.26c.09.32.19.65.29 1 .74 2.22 1.79 6.43 3.56 11.4a73.25 73.25 0 005.74 12.56 51.3 51.3 0 003.82 5.64l.26.34q3.87-2.58 8-5.05c1.6-4.1 3.37-14.06 3.69-22.14 1.21-30.93-23.23-46.89-42.31-51.47.06.01 7.04 7.6 10.28 16.87z" fill="currentColor"/><path d="M85.81 305.77l16.7 11.42-15.79-6a28.78 28.78 0 003.12 12.14s10.43 5.06 16.1 10.4l-13.89-4.37a15.32 15.32 0 00.43 7.26c.09.32.19.65.29 1 .74 2.22 1.79 6.43 3.56 11.4a73.25 73.25 0 005.74 12.56 51.3 51.3 0 003.82 5.64l.26.34q3.87-2.58 8-5.05c1.6-4.1 3.37-14.06 3.69-22.14 1.21-30.93-23.23-46.89-42.31-51.47.06.01 7.04 7.6 10.28 16.87z" opacity=".3"/><path d="M99.13 306.12a22.94 22.94 0 014.34 5.23 48.28 48.28 0 013.06 6.1 62.65 62.65 0 013.74 13.08 57.68 57.68 0 01-1.47 26.89.44.44 0 00.85.26 58.35 58.35 0 001.06-27.23 62.82 62.82 0 00-4-13.09 47.87 47.87 0 00-3.16-6.07 23.37 23.37 0 00-4.42-5.17z" fill="#fafafa"/><path d="M157.52 321.57c-.38.49-6.29 5.64-15.36 21.07l-18.39 4.62 15 1.35-6.88 12-15.7 3.88 12.44 1.54s-3 12.18-11.61 17.55c-4.78 3-7.15 3.48-8.67 2.15-1.06-.94-1.52-6.71-2.58-11.84s-1.45-13.52 3-23.83a31.46 31.46 0 014.78-7.64c11.17-13.42 33.03-20.59 43.97-20.85z" fill="currentColor"/><path d="M157.52 321.57c-.38.49-6.29 5.64-15.36 21.07l-18.39 4.62 15 1.35-6.88 12-15.7 3.88 12.44 1.54s-3 12.18-11.61 17.55c-4.78 3-7.15 3.48-8.67 2.15-1.06-.94-1.52-6.71-2.58-11.84s-1.45-13.52 3-23.83a31.46 31.46 0 014.78-7.64c11.17-13.42 33.03-20.59 43.97-20.85z" opacity=".15"/><path d="M112.17 355.34a48.67 48.67 0 00-3.17 13.47 145.8 145.8 0 00.22 17.45 2.92 2.92 0 01-.91-.51c-1.06-.94-1.52-6.71-2.58-11.84s-1.45-13.52 3-23.83a31.46 31.46 0 014.78-7.64c11.2-13.49 33.06-20.63 44-20.89-16.68 3.39-34.29 15.26-42.7 28.59a34.19 34.19 0 00-2.64 5.2z" opacity=".1"/><path d="M140.91 327.48c-8.17 4-15.85 9.28-21.77 16.31a43.5 43.5 0 00-7.21 11.67 41.81 41.81 0 00-3 13.34h.09a53.43 53.43 0 013.72-13 50.07 50.07 0 016.94-11.55 57.58 57.58 0 019.76-9.41 75.9 75.9 0 0111.5-7.28v-.08z" fill="#fafafa"/><path d="M60.05 333.25a33.68 33.68 0 014 0 64.21 64.21 0 0115.67 2.9c1.89.59 3.78 1.26 5.66 2 .93.39 1.86.8 2.78 1.23a45 45 0 0112.34 8.3 28.44 28.44 0 014.39 5.5c.16.26.33.52.48.78.39.68.73 1.36 1.07 2a38.81 38.81 0 014.56 18.87c.09 4.62-1.19 10-2 10.89a2.23 2.23 0 01-.38.32c-1.27.91-3.39.54-7.42-1.43-8.07-3.94-11.76-14.86-11.76-14.86l10.78-2.45-14.12-2-6.17-8.54 10.34-2.47L76 352.87a209.33 209.33 0 00-15.88-19.6z" fill="currentColor"/><path d="M64.1 333.22a64.21 64.21 0 0115.67 2.9c1.89.59 3.78 1.26 5.66 2 .93.39 1.86.8 2.78 1.23a45 45 0 0112.34 8.3 28.44 28.44 0 014.39 5.5c.16.26.33.52.48.78.39.68.73 1.36 1.07 2a38.81 38.81 0 014.51 18.9c.09 4.62-1.19 10-2 10.89a2.23 2.23 0 01-.38.32 2.42 2.42 0 01-.85.37c.57-7.2-4-25.13-9.68-32.29-6-7.45-19.73-18-38-20.85a33 33 0 014.01-.05z" opacity=".05"/><path d="M107.81 386.41a61.14 61.14 0 00-1.91-15.75A50.94 50.94 0 0099.83 356a36 36 0 00-5.13-6.15 52.73 52.73 0 00-6.14-5.12 59.51 59.51 0 00-14.07-7.4h-.06a.06.06 0 000 .06 78.8 78.8 0 0113.58 8 66.22 66.22 0 016 5 35.22 35.22 0 015.15 5.9 50.3 50.3 0 016.23 14.42 61.24 61.24 0 012.25 15.64l.17.06z" fill="#fafafa"/></g></g></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/not-found.svg b/ruoyi-plus-soybean/src/assets/svg-icon/not-found.svg
new file mode 100755
index 0000000..a513656
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/not-found.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><ellipse cx="249.03" cy="368.25" rx="228.44" ry="118.95" fill="#f5f5f5"/><g fill="#e6e6e6"><ellipse cx="395.38" cy="348.61" rx="60.93" ry="35.18"/><path d="M150 327.73c23.31 13.46 24.26 35.68.94 49.14s-62.05 13.06-85.37-.4-23.31-35.28 0-48.74 61.1-13.46 84.43 0z"/><path d="M340.14 400.26l-76.52-44.18a9.57 9.57 0 00-8.66 0l-76.52 44.18c-2.39 1.38-2.39 3.62 0 5l34.85 20.12-32.55 18.79c-6.61 3.81-17.37 3.81-24 0l-51.2-29.56c-2.82-1.63-4.38-3.68-4.38-5.76s1.56-4.14 4.38-5.77l45.37-26.2a2 2 0 10-2-3.46l-45.35 26.18c-4.11 2.38-6.38 5.66-6.38 9.24s2.27 6.85 6.38 9.23l51.2 29.56a30.56 30.56 0 0028 0l34.55-19.94L255 449.44a9.57 9.57 0 008.66 0l76.52-44.18c2.35-1.38 2.35-3.62-.04-5z"/></g><path d="M427.58 332.09s2.78-41.18 2-56.36c-.65-12-2.28-17-2.62-19.71 0 0-2.95-28-3.19-51.89-.12-11.48-1.18-19.64-7.47-33.28l-40.43 9.67c-.82 6.06-3.6 46.36-4.26 82.19-.57 31.37.32 58.83.32 58.83v1.65c0 1.84-.4 3.12-1.58 6.06a35.32 35.32 0 01-6.26 10.36c-.83.9-6.6 5.94-7.36 7-2.48 2.89 2.66 4.52 6.47 4 4-.54 10.26-2.07 12-4.51 1.18-1.64 2.07-7.35 3.28-9 4.09-5.58 5.77-8 5.8-9.76.06-3.22-1.05-4.49-1.45-6.31.33-3.8 8.54-35.05 8.68-46.12.07-5.27-.29-12.53-.29-12.53l6.36-36.52c2.46 9.12 6.78 31.76 8.24 40.45 1.77 10.62 6.4 37.88 9.14 54.14 1.05 6.27 1.54 9.44 2 12.35l.11 1.24c.09 1.58 1.24 18.48 1.86 22 1.3 7.39 7.87 6.46 8.92.48.9-5.08-.22-20.84-.25-22.77z" fill="#ffa8a7"/><path d="M427.58 332.09c1.86 0 .64 7.21 1.12 11.82.51 4.95 2.13 8.57 2.6 12.62a16.48 16.48 0 01-1.55 10.39c-1.29 3-7.79 7-10.26 2.79s-3-9.08-2.71-13.7 0-7.72.09-11.35c.09-3.2-2.26-11.37.15-11.83l.11 1.24c.87 1.73 9.65 2.07 10.53-.27z" fill="#37474f"/><path d="M384.7 331.27c.57 2.31 0 8.65 0 8.65s-1 2.19-4.57 3.64l-1.6-8.82z" fill="#263238"/><path d="M380.11 343.56l-7.86-2.58-1.9-6.98 8.84 4.47.92 5.09z"/><path d="M371.93 323.19v-1.65c-1.07.52-3.42 9.13-6.6 15-2.9 5.39-8.65 8.39-10.52 10.74-2.5 3.13 2.36 7.18 10.15 5.51 3.87-.83 9.73-3.3 11.32-5.91s2.12-7.41 3.42-9.33 4.39-4.13 5-6.31a12 12 0 000-6c-.61-2-1.15-4.39-1.83-4.17v1.51c-.52 1.06-2.41 2.3-6.34 2.52-1.92.09-3.74-.2-4.6-1.91z" fill="#37474f"/><path d="M375.05 180s-2.67 29.67-3.53 55.94-.09 60.17-.09 60.17 2.71 1.94 8.68 2.21 8-1.72 8-1.72 3.65-11.91 4.22-20.29a85.71 85.71 0 000-13.63l5.33-35.06s6.76 34.52 7.53 38.69 7.24 41.06 7.24 41.06 2.54 2.34 9.06 2.22c5.77-.11 7.44-2.18 7.44-2.18s1.39-22.62 1.07-32.28c-.27-8.52-2.22-15.3-2.61-20.83s-.61-37-1-49-1.39-22.3-11.66-36.74z" fill="#455a64"/><path d="M397.7 227.65l-4.44-22s-7.73-2-11.34-6.74c0 0 1 5.24 9 8.59l4.72 21.88L394 252z" fill="#37474f"/><g><path d="M318.56 174.53a19.1 19.1 0 008.51-.63 16.74 16.74 0 01-3.37-2.23 2.33 2.33 0 01-.4-3.22c.37-.4 1.29.53 3.37 1.12 2.66.75 4.59.91 7.15 2.39a4.69 4.69 0 003.2.56c6.24-1.17 20.79-8 27.46-11.56 1.36-5.55 3.72-13.71 6.63-23.89 2.69-9.38 7-13.48 13.88-13.33l-.99 20.37s-3.28 14.07-7.34 25.56c-.71 2-3.56 4.33-8.78 6.27-6.76 2.51-14.92 4.88-24.89 7.61a77.29 77.29 0 01-12.34 2.45c-10.54.9-14.58-2-16-4.08-4.04-5.74-1.65-8.5 3.91-7.39z" fill="#ffa8a7"/><path d="M402.72 123.59l9.62.74a22.36 22.36 0 007.24 21.54l-4.29 17.28c.14 2.14 3.68 9.34 7.11 17.08-6.45 8.64-38.09 11.59-48.41 4 1.59-8.14 2.78-14.32 3.1-17.41l-.52-11.17c-12.94-10.61 2.67-26.78 8.42-31.92l7.22-.32z" fill="currentColor"/><path d="M443.23 209.68a5.39 5.39 0 01-2.06-4.64c.17-1.61.54-3.63 1.21-4.41s4.87-6.49 6.73-3.17c2 3.62 1.59 5.09 1.59 5.09z" fill="#f28f8f"/><path d="M430.27 286a25.84 25.84 0 01-12.75-3.86l-49.93-28.84c-7.71-4.45-14-15.32-14-24.23v-41.89a8 8 0 00-12-6.9l-17.48 10.12a2 2 0 11-2-3.46l17.53-10.13a12 12 0 0118 10.37v41.89c0 7.5 5.5 17 12 20.77l49.93 28.82c6.39 3.69 12.32 4.33 16.68 1.82s6.77-8 6.77-15.35v-53.48c0-9.06 5.79-19.5 13.18-23.77l12.19-7a2 2 0 112 3.46l-12.19 7c-6.06 3.5-11.18 12.79-11.18 20.3v53.48c0 8.86-3.12 15.54-8.77 18.81a15.68 15.68 0 01-7.98 2.07z" fill="#37474f"/><path d="M344 193.29v3.27c0 5.23-3.67 11.6-8.21 14.21l-30.28 17.49-30.79-17.78v-26.66L304.58 170c4.75-2.19 12.27-1.85 16.81.76l14.38 8.31c4.53 2.62 8.23 8.99 8.23 14.22z" fill="#37474f"/><path d="M304.58 170c4.75-2.19 12.27-1.85 16.81.76l14.38 8.31c4.53 2.61 4.35 6.51-.4 8.71l-29.89 13.8-30.8-17.77z" fill="#455a64"/><path d="M308.39 195.18a9.07 9.07 0 014.1 7.1v21.93l-7 4-30.79-17.78v-26.61l7.8-3.6z" opacity=".15"/><path d="M272.07 179.23a2.85 2.85 0 012.88.28L305.23 197a9.06 9.06 0 014.1 7.11v26.08a2.85 2.85 0 01-1.2 2.64c-.7.39-3.17 1.82-3.86 2.23a2.87 2.87 0 01-2.89-.28l-30.29-17.49a9.07 9.07 0 01-4.1-7.1v-26.08a2.89 2.89 0 011.19-2.65z" fill="#37474f"/><path d="M301.38 199.22l-30.29-17.48c-2.26-1.31-4.1-.25-4.1 2.37v26.08a9.07 9.07 0 004.1 7.1l30.29 17.49c2.26 1.31 4.1.25 4.1-2.37v-26.08a9.09 9.09 0 00-4.1-7.11z" fill="#455a64"/><path d="M265.51 229.58v4.51c0 1.83.63 3.1 1.67 3.7l2.72 1.57a4 4 0 004.1-.36l25-14.46v-17.8l-2.72-1.57-25.05 14.46a12.69 12.69 0 00-5.72 9.95zm11.33-5.58a4 4 0 01.16 1 8.5 8.5 0 01-3.85 6.67 3.62 3.62 0 01-1 .4 4.11 4.11 0 01-.14-1 8.51 8.51 0 013.85-6.67 4.74 4.74 0 01.98-.4z" fill="currentColor"/><path d="M269.76 239.26a1 1 0 00.17.12l-.1-.06-2.63-1.53c-1.05-.58-1.67-1.85-1.67-3.69v-4.51a11.64 11.64 0 011.69-5.67l2.72 1.57a11.8 11.8 0 00-1.68 5.67v4.51a4.59 4.59 0 001.05 3.22 2.45 2.45 0 00.45.37z" opacity=".2"/><path d="M299 206.74l-25 14.46a11.72 11.72 0 00-4.06 4.28l-2.72-1.57a11.78 11.78 0 014.05-4.28l25.05-14.46zM270 239.45h-.07.07z" fill="#fff" opacity=".5"/><path d="M270 239.43zM277 225a4.27 4.27 0 00-.14-1.07c1.65-.45 2.86.58 2.86 2.64a8.52 8.52 0 01-3.84 6.67c-1.78 1-3.3.49-3.72-1.17a3.27 3.27 0 001-.4A8.53 8.53 0 00277 225z" opacity=".2"/><path d="M242.42 216.24v4.52c0 1.82.63 3.1 1.67 3.69l2.72 1.58a4 4 0 004.08-.38l25-14.47v-17.77l-2.72-1.58-25 14.46a12.72 12.72 0 00-5.75 9.95zm11.33-5.61a4.33 4.33 0 01.14 1.07 8.51 8.51 0 01-3.85 6.67 4.6 4.6 0 01-1 .4 3.81 3.81 0 01-.14-1.05 8.52 8.52 0 013.84-6.67 3.91 3.91 0 011.01-.42z" fill="currentColor"/><path d="M246.93 226.1H246.81l-2.72-1.59c-1.05-.59-1.67-1.86-1.67-3.7v-4.51a9.88 9.88 0 01.45-2.84 13.48 13.48 0 011.23-2.82l2.72 1.56a12.69 12.69 0 00-.69 1.38 10.89 10.89 0 00-1 4.28v4.51a4.61 4.61 0 001 3.22 2.8 2.8 0 00.8.51z" opacity=".2"/><path d="M275.92 193.41l-25 14.46a11.64 11.64 0 00-4.06 4.28l-2.72-1.56a11.77 11.77 0 014-4.3l25-14.46zM247 226.12h-.07.05z" fill="#fff" opacity=".5"/><path d="M253.88 211.69a4.21 4.21 0 00-.13-1.06c1.64-.46 2.86.58 2.86 2.64a8.52 8.52 0 01-3.84 6.67c-1.79 1-3.3.49-3.72-1.18a3.12 3.12 0 001-.4 8.5 8.5 0 003.83-6.67z" opacity=".2"/><path d="M250 199.55a1.49 1.49 0 01-1.45-1.13l-4-15.68a1.5 1.5 0 012.91-.75l4 15.69a1.48 1.48 0 01-1.08 1.82 1.57 1.57 0 01-.38.05zM238.74 206.09a1.5 1.5 0 01-.93-.32L222.92 194a1.5 1.5 0 111.86-2.35l14.89 11.76a1.51 1.51 0 01.25 2.11 1.48 1.48 0 01-1.18.57zM218.22 220.45a1.5 1.5 0 01-.11-3l17.41-1.35a1.49 1.49 0 011.61 1.38 1.51 1.51 0 01-1.38 1.61l-17.41 1.35z" fill="#e0e0e0"/><path d="M456.84 193.36a98.58 98.58 0 00-1.64-11 148.11 148.11 0 00-9.06-24.81c-1.67-3.19-5.44-7.84-8.11-11.77-3.78-5.55-6.44-8.66-9.31-12.43-6.32-8.27-9-8.75-16.38-9-1 2.63-3.28 13.41 4.11 22.12l15.84 16.7c.9 1.45 10.58 18 12.19 24.11a4.52 4.52 0 01-.38 3.23 39 39 0 00-2.77 8.76c-.47 2.12-1.73 3.8-1.3 4.13a2.84 2.84 0 003.51-.28 15.05 15.05 0 002.73-4.12 2.15 2.15 0 012.29 2.19c.09 1.23-.47 2.94-.9 5.07-.08.42-.18.9-.37 2.06a13.42 13.42 0 00-.28 2.38c2 .37 4.09-.85 7.45-4 3.03-2.94 3.23-6.8 2.38-13.34z" fill="#ffa8a7"/><path d="M404.44 118.73c.06-2 6.56-6.68 9.85-11.66 3-4.62 7.31-17.32-2.79-21.38 0 0 2.18-6.09-1.78-9.68s-9.29-1.37-10.13.13a9 9 0 00-8.46-6.64 7.87 7.87 0 00-8.28 6.17 7.59 7.59 0 00-9.21 2.58c-3.71 5.11 1.21 9.79 1.21 9.79A4.25 4.25 0 00373 92c.29 2.16 2.36 2.69 2.6 3.77a1.06 1.06 0 01-1.81.94 2.18 2.18 0 002.56 1.73c2.33-.13 4.41-3.3 4.41-3.3z" fill="#263238"/><path d="M383.24 85.05c-3 1.3-5.83 5.87-5.79 18.65 0 10.83 3.36 13.57 5 14.38s4.95.39 8.14-.1v6.23s-5.77 7.16-.6 10.72c13.32-2.33 14.34-11.07 14.34-11.07l.24-13.58s1.83 1.92 5-.9c2.66-2.33 3.62-6.32 1.62-8.52s-4.56-2.37-6.88.08c0 0-4.25.28-10.64-3.73s-8.67-8.08-10.43-12.16z" fill="#ffa8a7"/><path d="M390.91 110.48a1.56 1.56 0 01-1.48 1.66 1.61 1.61 0 111.48-1.66z" fill="#b16668"/><path d="M382.68 100a1.44 1.44 0 11-1.5-1.45 1.47 1.47 0 011.5 1.45zM393.75 100.66a1.56 1.56 0 01-1.48 1.66 1.59 1.59 0 01-1.6-1.56 1.54 1.54 0 113.08-.1zM381.68 94.27l-3.05 1.79a1.7 1.7 0 002.4.65 1.83 1.83 0 00.65-2.44zM404.37 99.18v6a2.82 2.82 0 01-2.89-2.92 3.1 3.1 0 012.89-3.08z" fill="#263238"/><path fill="#f28f8f" d="M386.62 98.57l-.61 8.64-4.56-1.07 5.17-7.57zM390.63 118c3.35-.39 10.27-2.31 11.4-5.13a7.35 7.35 0 01-2.48 3.57c-2.09 1.8-8.93 3.66-8.93 3.66z"/></g><g><path d="M430.9 51.18a13.78 13.78 0 013.55 2 10.75 10.75 0 012.65 2.79 8.68 8.68 0 011.3 3.44 7.65 7.65 0 01-.47 3.87 7.9 7.9 0 01-1.59 2.66 9.5 9.5 0 01-2.13 1.68 14.81 14.81 0 01-2.4 1.1l-2.36.85a11.94 11.94 0 00-2 .94 3.57 3.57 0 00-1.45 1.34 1.76 1.76 0 01-.66.64 1 1 0 01-.86.05l-3.12-1.17a1.18 1.18 0 01-.67-.63 1 1 0 010-.89 7.71 7.71 0 011.74-2.56 10.91 10.91 0 012.25-1.63 15.6 15.6 0 012.46-1.06c.84-.28 1.62-.55 2.35-.83a10.4 10.4 0 001.89-.92 2.79 2.79 0 001.12-1.38 3.29 3.29 0 00-.34-3.06 6 6 0 00-3.11-2.31 5.73 5.73 0 00-6.43 1.55 2.59 2.59 0 01-.69.52 1.15 1.15 0 01-.84-.06l-3.32-1.25a.93.93 0 01-.54-.49.85.85 0 010-.75 6.67 6.67 0 012-2.6 10.71 10.71 0 013.27-1.86 13 13 0 014.07-.74 11.48 11.48 0 014.33.76zm-7.12 23.9a1.1 1.1 0 01.65.62 1.13 1.13 0 010 .9l-1.36 3.61a1.1 1.1 0 01-.62.65 1.13 1.13 0 01-.9 0l-3.53-1.33a1.1 1.1 0 01-.65-.62 1.13 1.13 0 010-.9l1.36-3.61a1.15 1.15 0 01.62-.66 1.17 1.17 0 01.9 0zM432.63 94.28a1.1 1.1 0 010 1.57l-2.5 2.64a1.08 1.08 0 01-.78.34 1.11 1.11 0 01-.8-.3L426 96.08a1.08 1.08 0 01-.34-.78 1.07 1.07 0 01.3-.8l2.51-2.63a1.12 1.12 0 011.57 0zm15-18.13a13.34 13.34 0 012.37 3 10.44 10.44 0 011.27 3.4 8.25 8.25 0 01-.17 3.45 8 8 0 01-4.19 4.91 8.77 8.77 0 01-2.46.68 14.86 14.86 0 01-2.49.07l-2.36-.13a12.13 12.13 0 00-2.11.07 3.4 3.4 0 00-1.72.7 1.56 1.56 0 01-.81.31.94.94 0 01-.77-.27l-2.27-2.16a1.15 1.15 0 01-.36-.79.92.92 0 01.32-.79 7.27 7.27 0 012.44-1.6 10.23 10.23 0 012.55-.59 15.29 15.29 0 012.53 0q1.25.11 2.34.15a9.53 9.53 0 002-.11 2.61 2.61 0 001.49-.78 3.13 3.13 0 00.82-2.78 5.62 5.62 0 00-1.85-3.15 5.42 5.42 0 00-6.16-1 2.3 2.3 0 01-.78.19 1 1 0 01-.71-.36l-2.42-2.31a.92.92 0 01-.3-.61.88.88 0 01.26-.67 6.45 6.45 0 012.68-1.53 10.2 10.2 0 013.52-.41 12.17 12.17 0 013.81.86 10.6 10.6 0 013.49 2.25z" fill="currentColor"/><path d="M432.63 94.28a1.1 1.1 0 010 1.57l-2.5 2.64a1.08 1.08 0 01-.78.34 1.11 1.11 0 01-.8-.3L426 96.08a1.08 1.08 0 01-.34-.78 1.07 1.07 0 01.3-.8l2.51-2.63a1.12 1.12 0 011.57 0zm15-18.13a13.34 13.34 0 012.37 3 10.44 10.44 0 011.27 3.4 8.25 8.25 0 01-.17 3.45 8 8 0 01-4.19 4.91 8.77 8.77 0 01-2.46.68 14.86 14.86 0 01-2.49.07l-2.36-.13a12.13 12.13 0 00-2.11.07 3.4 3.4 0 00-1.72.7 1.56 1.56 0 01-.81.31.94.94 0 01-.77-.27l-2.27-2.16a1.15 1.15 0 01-.36-.79.92.92 0 01.32-.79 7.27 7.27 0 012.44-1.6 10.23 10.23 0 012.55-.59 15.29 15.29 0 012.53 0q1.25.11 2.34.15a9.53 9.53 0 002-.11 2.61 2.61 0 001.49-.78 3.13 3.13 0 00.82-2.78 5.62 5.62 0 00-1.85-3.15 5.42 5.42 0 00-6.16-1 2.3 2.3 0 01-.78.19 1 1 0 01-.71-.36l-2.42-2.31a.92.92 0 01-.3-.61.88.88 0 01.26-.67 6.45 6.45 0 012.68-1.53 10.2 10.2 0 013.52-.41 12.17 12.17 0 013.81.86 10.6 10.6 0 013.49 2.25z" fill="#fff" opacity=".6"/></g><g><path d="M76.81 345.71c3.17 1.93 7.38 2 11.35.61l3-21-14-3.58z" fill="#ffa8a7"/><path d="M99.4 365.81c.35.36.14 3-.29 3.59s-2.84 2.59-7.28 2.67c-4.23.07-8-.7-10.36-2.41s-3.49-3.5-3.6-5.92.29-4.71-.75-6.62-2.29-3.51-2.61-4.45a12 12 0 010-5.17z" fill="#263238"/><path d="M89.13 345.44a11.56 11.56 0 00.38 2.7 24.77 24.77 0 002.29 5.42 22.77 22.77 0 001.63 2.57c1.19 1.59 2.71 2.92 4 4.45a8.61 8.61 0 012.33 5.32c0 3.32-3.65 4.25-6.44 4.59a20.33 20.33 0 01-9-.93 8.26 8.26 0 01-5.69-6.92c-.11-.93 0-1.86-.06-2.79a12.93 12.93 0 00-2.26-5.93 17 17 0 01-1.8-3.26c-.76-2.09.06-4.36.72-6.37.57-1.73 1-3.82 1.64-3.62v1.13c.18.39.62.67.73 1.13a8.59 8.59 0 00.43 1.39 3.6 3.6 0 001.38 1.7c.11-1.25.22-2.5.32-3.74a1.31 1.31 0 011.43-1.49 23.59 23.59 0 017.11-.21 1.36 1.36 0 01.93.47 1.44 1.44 0 01.11.85 32.79 32.79 0 00-.18 3.54z" fill="currentColor"/><path d="M89.13 345.44a11.56 11.56 0 00.38 2.7 24.77 24.77 0 002.29 5.42 22.77 22.77 0 001.63 2.57c1.19 1.59 2.71 2.92 4 4.45a8.61 8.61 0 012.33 5.32c0 3.32-3.65 4.25-6.44 4.59a20.33 20.33 0 01-9-.93 8.26 8.26 0 01-5.69-6.92c-.11-.93 0-1.86-.06-2.79a12.93 12.93 0 00-2.26-5.93 17 17 0 01-1.8-3.26c-.76-2.09.06-4.36.72-6.37.57-1.73 1-3.82 1.64-3.62v1.13c.18.39.62.67.73 1.13a8.59 8.59 0 00.43 1.39 3.6 3.6 0 001.38 1.7c.11-1.25.22-2.5.32-3.74a1.31 1.31 0 011.43-1.49 23.59 23.59 0 017.11-.21 1.36 1.36 0 01.93.47 1.44 1.44 0 01.11.85 32.79 32.79 0 00-.18 3.54z" opacity=".2"/><path d="M92.21 354.28c-1.18-1-3.94-1.15-5.46-1.07a9.22 9.22 0 00-4.37 1.27 1 1 0 01-1.25-.17.92.92 0 01.16-1.4 9.45 9.45 0 014.93-1.53C90 351.32 91 352 91 352s1.64 1.2 1.21 2.28zM94.86 357.82c-1.5-1-4.54-1-6.06-.95a8.6 8.6 0 00-4.25 1.36 1 1 0 01-1.26-.17.91.91 0 01.17-1.4 9.55 9.55 0 015-1.66c3.73-.07 4.66.75 4.66.75a3.07 3.07 0 011.74 2.07zM84.93 347.75a9.27 9.27 0 014.65.64c.63.41 1 1.46.57 1.7a8.66 8.66 0 00-4.15-.79 12.24 12.24 0 00-4.07.89c-.33.12-.71.3-1 .46a.87.87 0 01-1.2-.44.84.84 0 01.38-1 11.93 11.93 0 014.82-1.46z" fill="#455a64"/><path d="M116.27 337.82c2.44 6.11 6.26 4.23 12.08.71l.88-20.86-14.81-1.37z" fill="#ffa8a7"/><path d="M150.69 352.53a4 4 0 01-.32 2.93c-.46.84-5.09 3.14-11.14 2.4a25.22 25.22 0 01-12.32-5.06c-2.23-1.66-4.62-2.07-7.62-2.41s-5.25-1.38-5.84-2.82.31-3.82.31-3.82z" fill="#263238"/><path d="M127.13 333.3a11.94 11.94 0 001.61-.46 1.31 1.31 0 01.91 0c.38.17.5.63.59 1a13.86 13.86 0 00.52 2.67 6.54 6.54 0 001.6 1.88 27.83 27.83 0 004.69 3.39c1.93 1.18 3.83 2.11 5.82 3.14s4.78 1.77 6.36 3c2.29 1.74 2.38 5.79-.42 7.23-2.41 1.25-8.65 2.41-15 .23-3.47-1.19-7.17-5.16-11.77-5.77-2.93-.38-6.83-.84-8.6-3.17-.64-1-.08-3.9.44-7.29.47-3 .9-7.8 1.87-7.48l.07.92L117 334a10.07 10.07 0 00.82.78 14.84 14.84 0 001 1.06 3 3 0 001.69.71 1.56 1.56 0 00.85-.19c.5-.28.57-.94 1-1.35a4.88 4.88 0 011.64-1.11 9.61 9.61 0 012.66-.54 3.65 3.65 0 00.47-.06z" fill="currentColor"/><path d="M127.13 333.3a11.94 11.94 0 001.61-.46 1.31 1.31 0 01.91 0c.38.17.5.63.59 1a13.86 13.86 0 00.52 2.67 6.54 6.54 0 001.6 1.88 27.83 27.83 0 004.69 3.39c1.93 1.18 3.83 2.11 5.82 3.14s4.78 1.77 6.36 3c2.29 1.74 2.38 5.79-.42 7.23-2.41 1.25-8.65 2.41-15 .23-3.47-1.19-7.17-5.16-11.77-5.77-2.93-.38-6.83-.84-8.6-3.17-.64-1-.08-3.9.44-7.29.47-3 .9-7.8 1.87-7.48l.07.92L117 334a10.07 10.07 0 00.82.78 14.84 14.84 0 001 1.06 3 3 0 001.69.71 1.56 1.56 0 00.85-.19c.5-.28.57-.94 1-1.35a4.88 4.88 0 011.64-1.11 9.61 9.61 0 012.66-.54 3.65 3.65 0 00.47-.06z" opacity=".2"/><path d="M134.4 340a2.76 2.76 0 00-2.1-1.57c-1.32-.32-3.73.54-5.26 1.67a1.1 1.1 0 00.06 1.82 1.12 1.12 0 001.26-.07 8.32 8.32 0 016.04-1.85zM138.41 342.57a3.15 3.15 0 00-2.5-1.46 9.76 9.76 0 00-5.76 1.81 1.07 1.07 0 000 1.76 1.07 1.07 0 001.22-.08 8.42 8.42 0 017.04-2.03zM142.87 344.91a3.51 3.51 0 00-2.6-1.34 9.46 9.46 0 00-5.53 1.84 1.07 1.07 0 00.06 1.76 1 1 0 001.21-.07 8 8 0 016.86-2.19zM76.42 289.41c.45-10.07 2.59-15.31 2.89-18.1 0 0 .82-55.4 2.17-72.21l49.6-2.11c.73 18 1.39 66.36 1 72.76-.37 6.13-2.86 59.3-2.86 59.3-7 2.19-14.47-1-14.47-1s-4.16-30.25-4.84-36.92a85.23 85.23 0 01.46-18l-3.17-43.07s-3.83 33.4-5.72 46C99.32 290.47 90 335.14 90 335.14c-6.7 1.67-13.22-1.22-13.22-1.22s-1.02-29.92-.36-44.51z" fill="#455a64"/><path d="M107.21 230.1l.94-5.94c2.89-.43 10.21-5 14.54-9a34.5 34.5 0 01-12.16 11.21l-.15 46.78z" fill="#37474f"/><g><path d="M127.55 167.81c-3.37-7.69-9.17-20.64-9.17-20.64l-1.47-21a56.64 56.64 0 016.41.57c3.12.49 8.07 3.47 10.33 9.09 1.69 4.17 10.47 29.61 10.47 29.61l15.3-8.06c3.92-2.49 5.31-6.62 7.81-9s5-2.74 7.77-4.65 3.86-3.44 4.56-1.53-2.4 4.84-3 5.57-3.61 2.29.23 2.49 11.61-3.54 13.47-4.18 1.7 1.57.73 2.94-1.07 5.39-2.69 7.64c-1.72 2.4-2.65 3.43-6.34 4.86-3.43 1.32-10.54 1.66-14.2 3.77s-12.52 10.12-18.43 14.23c-7.93 5.52-12.39 5.88-15.5 1.48s-5.09-10.48-6.28-13.19z" fill="#ffa8a7"/><path d="M114.84 125.72c5.2-.4 11.33-.08 14.72 3 2.71 2.47 3.73 4 6.75 12.54 2 5.73 6.28 18.93 6.28 18.93a27.32 27.32 0 00-15 9.19l-9.75-21.91z" fill="#e0e0e0"/><path d="M144.12 165.39a13.09 13.09 0 00-6.47 5s.37-4 6-6.39z" fill="#f28f8f"/><path d="M102.39 124.78a24.57 24.57 0 00-7.31.8c-4.57 1.21-12.76 3.51-12.76 3.51-2.6 1.35-3.61 5.85-4.2 8.44-1.88 8.29 3 27 3.75 34.29S81 201.44 81 201.44c5.59 6.48 35.88 10.78 50.63 0 0 0 .64-51.27-.81-58.63-2.2-11.14-5.47-16.43-17-17.1z" fill="#f5f5f5"/><path d="M108.86 97.28l-3.29 1.81a2 2 0 01.77-2.62 1.83 1.83 0 012.52.81zM123.17 98.6l-3-2.31a1.82 1.82 0 012.61-.4 2 2 0 01.39 2.71zM93.74 86.81S90.2 87.3 88.9 90c-1.12 2.32-.72 7.92.79 14a56.45 56.45 0 003.8 11.46 4.7 4.7 0 002.7 2.17l-.19-7.26-.26-5s3.23-4.42 3.59-8c.46-4.66-.52-6.57-.52-6.57z" fill="#263238"/><path d="M99.45 93.81A13.53 13.53 0 00113 107.29c7.46 0 13.06-6.15 13-13.62s-5.66-13.45-13.13-13.42a13.52 13.52 0 00-13.42 13.56z" fill="#263238"/><path d="M96 104.52c-1.22 1.06-2.21-1.67-3.23-2.74s-4.37-2.51-6 1 1.46 8.6 4 9.58a3.65 3.65 0 004.31-1.36v16c3.85 6.94 10.64 6.72 14.27 6.33s4.42-4.16 1.77-7.2v-5a28.29 28.29 0 006.1.29c3.32-.52 5-3 6-6.63 1.6-5.79 2.25-15.51 0-26.74-3.72-2.88-16.62-2.4-24.43 2.55.61 10.07-1.63 12.87-2.79 13.92z" fill="#ffa8a7"/><path d="M123.88 82.49a24.39 24.39 0 00.67-6.37c0-.84-.21-1.86-1-2.18s-1.57.26-2.25.72c-2.88 1.94-6.46 2.49-9.93 2.68-6 .33-15.17-.06-18.22 6.51-.85 1.82-1 3.74.68 5a11.5 11.5 0 005 1.86c3 .53 6 1.19 9.08 1.6s6.74.81 9.79-.13c2.57-.79 5.18-1.47 7.09-3.51a9.78 9.78 0 002.62-6.54c0-.34-.07-.77-.41-.86a.78.78 0 00-.49.1z" fill="#263238"/><path d="M111.09 121.24s-7.42-1.47-10-2.84a8.57 8.57 0 01-3.6-3.54 11.65 11.65 0 002 4.18c1.91 2.42 11.58 4.17 11.58 4.17z" fill="#f28f8f"/><path d="M109.6 102.19a1.67 1.67 0 11-1.67-1.72 1.7 1.7 0 011.67 1.72z" fill="#263238"/><path d="M112 113.74a1.6 1.6 0 01-1.56 1.63 1.61 1.61 0 01-1.58-1.63 1.59 1.59 0 011.56-1.62 1.61 1.61 0 011.58 1.62z" fill="#b16668"/><path d="M107.5 96.94l-3.44 2.17a2.13 2.13 0 01.66-2.88 2 2 0 012.78.71zM119.64 94.82l3.62 1.62a1.9 1.9 0 01-2.56 1 2.09 2.09 0 01-1.06-2.62zM121.55 101.69a1.61 1.61 0 11-1.62-1.67 1.65 1.65 0 011.62 1.67z" fill="#263238"/><path fill="#f28f8f" d="M113.37 98.87l.6 11.12 5.27-1.4-5.87-9.72z"/><path d="M20 144.67c1.73.93 8.78 5.89 12.61 6.32 1.62.18 2.06-.08 2-.5-.08-.58-1.11-1.45-1.36-1.92-.43-.82-3-4.22-2-6s1.87-.08 4.25 2.25 4.82 3.14 6.91 5.85 2.79 7 6.25 10.11l13.78 9.41s6.42-29.79 9.24-33.3c3-3.69 15.87 4.35 13.17 16.08S76 185.06 71.72 189.05c-2.76 2.59-8.57.15-15.82-6.3-5.73-5.1-13.58-12.75-16.9-15.46s-10.22-4.17-13.39-6c-3.41-2-4.16-3.17-5.47-5.82-1.23-2.49-.68-6.48-1.41-8s-.49-3.73 1.27-2.8z" fill="#ffa8a7"/><path d="M82.32 129.09c4.42 2.76 4.64 6.78 4.86 11.38a47.57 47.57 0 01-2.52 15.84c-1.87 5.79-4.84 16.36-4.84 16.36s-11.54.27-17.48-4.86c0 0 2.71-11.4 4.62-20s4.23-17.75 15.36-18.72z" fill="#e0e0e0"/><path d="M62.39 170.21c3.15 1.75 5 5.51 5.57 7a11 11 0 00-5.24-8.53z" fill="#f28f8f"/></g><g><path fill="currentColor" d="M182.11 386.34l77.18 44.56v16.42l-77.18-44.56v-16.42z"/><path opacity=".35" d="M182.11 386.34l77.18 44.56v16.42l-77.18-44.56v-16.42z"/><path fill="currentColor" d="M336.47 386.34l-77.18 44.56v16.42l77.18-44.56v-16.42z"/><path opacity=".2" d="M336.47 386.34l-77.18 44.56v16.42l77.18-44.56v-16.42z"/><path fill="currentColor" d="M336.47 386.34l-77.18-44.55-77.18 44.55 77.18 44.56 77.18-44.56z"/><path opacity=".15" d="M336.47 386.34l-77.18-44.55-77.18 44.55 77.18 44.56 77.18-44.56z"/><path opacity=".1" d="M259.29 430.9v-4.69l-69.06-39.87h-8.12l77.18 44.56zM259.29 341.79v4.69l69.06 39.86h8.12l-77.18-44.55z"/><path fill="currentColor" d="M336.47 386.34h-8.12l-69.06 39.87v4.69l77.18-44.56zM259.29 341.79v4.69l-69.06 39.86h-8.12l77.18-44.55z"/><path d="M259.57 367l33.16 19.14c2.48 1.44 2.48 3.76 0 5.19L268 405.65a9.89 9.89 0 01-9 0l-33.16-19.14c-2.48-1.44-2.48-3.76 0-5.19L250.58 367a9.89 9.89 0 018.99 0z" fill="#37474f"/><path d="M268 405.65l23.91-13.81-32.34-18.67a10 10 0 00-9 0L226.66 387 259 405.65a9.89 9.89 0 009 0z" fill="#455a64"/><path d="M239.54 386.37l14.51-8.37a1.94 1.94 0 011.74 0l1.78 1c.48.27.48.72 0 1l-14.51 8.37a1.92 1.92 0 01-1.73 0l-1.78-1a.53.53 0 01-.01-1zM261.21 398.9l14.51-8.37a1.92 1.92 0 011.73 0l1.79 1c.48.27.48.72 0 1l-14.51 8.37a1.92 1.92 0 01-1.73 0l-1.78-1a.53.53 0 01-.01-1z" fill="#263238"/><path d="M225.76 418.77a9.64 9.64 0 00-4.38-7.57 3.05 3.05 0 00-3.09-.3l-1.89 1.1a3.06 3.06 0 00-1.28 2.82 9.69 9.69 0 004.37 7.58 3.07 3.07 0 003.1.3l1.88-1.13a3.08 3.08 0 001.29-2.8z" fill="currentColor"/><path d="M219.49 412.33a9.64 9.64 0 014.38 7.57c0 2.79-2 3.92-4.38 2.53a9.69 9.69 0 01-4.37-7.58c0-2.78 1.96-3.91 4.37-2.52z" opacity=".1"/></g><g><path d="M168.76 444.27a28.55 28.55 0 01-14-3.39l-51.2-29.56c-4.11-2.38-6.38-5.66-6.38-9.24s2.27-6.85 6.38-9.23l59.32-34.25c6.5-3.75 12-13.26 12-20.77V150.1a2 2 0 014 0v187.73c0 9.06-6.14 19.71-14 24.24l-59.32 34.25c-2.82 1.63-4.38 3.67-4.38 5.76s1.56 4.14 4.38 5.77l51.2 29.56c6.61 3.82 17.37 3.82 24 0l37.75-21.78a2 2 0 112 3.46l-37.75 21.79a28.5 28.5 0 01-14 3.39z" fill="#37474f"/></g><g><path d="M78.92 78.31a1 1 0 01.16-.79 1 1 0 01.68-.45l4.86-.94a1 1 0 01.8.16 1.09 1.09 0 01.45.68l.75 3.9a1 1 0 01-.17.79 1 1 0 01-.67.46l-4.87.93a1 1 0 01-.79-.16 1 1 0 01-.45-.68zm-4.06-21.06A1.06 1.06 0 0175.7 56l4.87-.94a1 1 0 01.79.17 1 1 0 01.45.67L85 72.57a1 1 0 01-.17.79 1 1 0 01-.67.45l-4.87.94a1 1 0 01-.79-.17 1 1 0 01-.45-.67zM70.8 94.63a1.06 1.06 0 01.08-1.5l3.69-3.31a1 1 0 01.77-.26 1 1 0 01.73.34l2.65 3a1.06 1.06 0 01-.08 1.5L75 97.67a1 1 0 01-.77.26 1 1 0 01-.73-.34zm-14.31-16a1.07 1.07 0 01-.27-.77 1 1 0 01.35-.73l3.69-3.31a1 1 0 01.77-.27 1 1 0 01.73.35l11.32 12.66a1 1 0 01.26.77 1 1 0 01-.34.73l-3.7 3.31a1 1 0 01-.76.26 1 1 0 01-.74-.34z" fill="currentColor"/><path d="M70.8 94.63a1.06 1.06 0 01.08-1.5l3.69-3.31a1 1 0 01.77-.26 1 1 0 01.73.34l2.65 3a1.06 1.06 0 01-.08 1.5L75 97.67a1 1 0 01-.77.26 1 1 0 01-.73-.34zm-14.31-16a1.07 1.07 0 01-.27-.77 1 1 0 01.35-.73l3.69-3.31a1 1 0 01.77-.27 1 1 0 01.73.35l11.32 12.66a1 1 0 01.26.77 1 1 0 01-.34.73l-3.7 3.31a1 1 0 01-.76.26 1 1 0 01-.74-.34z" fill="#fff" opacity=".6"/></g></g><g><path d="M166 48a7.12 7.12 0 014.77 1.45 5.24 5.24 0 011.7 4.23 5.34 5.34 0 01-1.7 4.26 7.07 7.07 0 01-4.77 1.5h-4v5.65a.66.66 0 01-.2.49.67.67 0 01-.48.2h-2.05a.68.68 0 01-.69-.69V48.71a.68.68 0 01.69-.68zm-4 8.27h3.93a4.11 4.11 0 002.31-.57 2.19 2.19 0 00.86-2 2.14 2.14 0 00-.86-2 4.29 4.29 0 00-2.31-.54H162zM174.76 55.84a3.58 3.58 0 01.47-1.14 4.24 4.24 0 011-1.14 5.61 5.61 0 011.65-.88 6.82 6.82 0 012.27-.34 7.36 7.36 0 012.33.34 5.13 5.13 0 011.79 1 4.38 4.38 0 011.15 1.66 6.13 6.13 0 01.41 2.3v7.45a.67.67 0 01-.21.49.66.66 0 01-.48.2h-1.92a.68.68 0 01-.69-.69v-.83a4.13 4.13 0 01-1.53 1.25 5.48 5.48 0 01-2.55.52 6.15 6.15 0 01-2-.29 4 4 0 01-1.45-.82 3.74 3.74 0 01-.9-1.26 3.91 3.91 0 01-.31-1.58 3.3 3.3 0 011.24-2.74 7.21 7.21 0 013.41-1.34l4.13-.71a1.62 1.62 0 00-.68-1.47 3.22 3.22 0 00-1.7-.43 2.24 2.24 0 00-1 .18 2.81 2.81 0 00-.68.48 1.6 1.6 0 01-.42.28 1.16 1.16 0 01-.42.08h-2.36a.6.6 0 01-.43-.16.39.39 0 01-.12-.41zM179 63a4.55 4.55 0 001.53-.24 3.46 3.46 0 001.13-.65 2.68 2.68 0 00.68-.91 2.42 2.42 0 00.23-1v-.3l-3.45.6a3.67 3.67 0 00-1.49.52 1.09 1.09 0 00-.46.93.83.83 0 00.55.79A3.14 3.14 0 00179 63zM195 68.31a3.89 3.89 0 001.09-.16 2.94 2.94 0 001-.52 3 3 0 00.75-.9 2.75 2.75 0 00.29-1.28v-.94a4.37 4.37 0 01-.53.49 4.06 4.06 0 01-.8.5 5.54 5.54 0 01-1.09.38 6.1 6.1 0 01-1.41.15 5.2 5.2 0 01-2.29-.49 5.36 5.36 0 01-1.75-1.32 6.17 6.17 0 01-1.14-1.9 6.9 6.9 0 01-.47-2.25v-.88-.89a6.9 6.9 0 01.47-2.25 6.29 6.29 0 011.14-1.9 5.36 5.36 0 011.74-1.32 5.2 5.2 0 012.29-.49 6.1 6.1 0 011.41.15 5.54 5.54 0 011.09.38 4.06 4.06 0 01.8.5 4.37 4.37 0 01.53.49v-.58a.7.7 0 01.68-.69h1.93a.72.72 0 01.69.69v11.89a6 6 0 01-1.72 4.63 6.74 6.74 0 01-4.7 1.56 8 8 0 01-2.4-.35 6.51 6.51 0 01-1.91-.91 4.84 4.84 0 01-1.3-1.29 3 3 0 01-.53-1.49.57.57 0 01.19-.48.71.71 0 01.49-.2h1.73a.85.85 0 01.61.24c.17.16.31.31.43.44a2.39 2.39 0 00.9.71 4.21 4.21 0 001.79.28zm-3-9.83a6.74 6.74 0 000 1.42 3.37 3.37 0 001 2.37 3.15 3.15 0 002.1.72 3.07 3.07 0 002.09-.74 3 3 0 001-2.1 8.31 8.31 0 00.05-1 8.5 8.5 0 00-.05-1 3 3 0 00-3.07-2.84 3.15 3.15 0 00-2.1.72 3.37 3.37 0 00-1.02 2.45zM204.27 59.19a8.14 8.14 0 01.44-2.68 6.32 6.32 0 011.29-2.18 5.82 5.82 0 012-1.46 6.58 6.58 0 015.15 0 6.06 6.06 0 012 1.39 6.35 6.35 0 011.26 2.06 7.06 7.06 0 01.44 2.47v1.14a.62.62 0 01-.2.48.64.64 0 01-.48.21h-8.47a2.11 2.11 0 00.24 1 2.24 2.24 0 00.65.75 2.84 2.84 0 00.94.46 3.93 3.93 0 001.09.15 4.25 4.25 0 001.31-.17 3 3 0 00.84-.41 2.34 2.34 0 01.46-.28 1.26 1.26 0 01.48-.08h2a.71.71 0 01.49.2.55.55 0 01.19.49 2.19 2.19 0 01-.39.91 4.55 4.55 0 01-1.1 1.09 7.19 7.19 0 01-1.82.91 7.53 7.53 0 01-2.49.38 6.49 6.49 0 01-2.58-.49 5.44 5.44 0 01-2-1.41 6.19 6.19 0 01-1.3-2.12 8.38 8.38 0 01-.44-2.81zm6.22-3.81a3.19 3.19 0 00-1.26.22 2.56 2.56 0 00-.87.56 2.38 2.38 0 00-.55.76 2.59 2.59 0 00-.24.8h5.73a4.2 4.2 0 00-.19-.8 2 2 0 00-.46-.76 2.29 2.29 0 00-.83-.56 3.39 3.39 0 00-1.33-.22zM237.64 65.09a.68.68 0 01-.68.69H235a.66.66 0 01-.48-.2.67.67 0 01-.21-.49v-6.44a3.82 3.82 0 00-.71-2.43 2.58 2.58 0 00-2.18-.91 2.61 2.61 0 00-2.11.91 3.62 3.62 0 00-.78 2.43v6.44a.66.66 0 01-.2.49.67.67 0 01-.48.2H226a.67.67 0 01-.48-.2.66.66 0 01-.2-.49V53.28a.62.62 0 01.2-.48.64.64 0 01.48-.21h1.93a.64.64 0 01.48.21.62.62 0 01.2.48v.58a5.77 5.77 0 011.55-1.09 4.71 4.71 0 012.1-.43 6.14 6.14 0 012.59.48 4.44 4.44 0 011.67 1.29 4.87 4.87 0 01.9 1.89 9.35 9.35 0 01.27 2.27zM246.82 52.34a6.94 6.94 0 012.52.43 6.19 6.19 0 011.95 1.23 5.94 5.94 0 011.32 1.78 5.72 5.72 0 01.57 2.18 4.51 4.51 0 010 .58v1.41a4.45 4.45 0 010 .57 6.4 6.4 0 01-.58 2.18 5.55 5.55 0 01-1.31 1.76 6.16 6.16 0 01-1.95 1.18 7.6 7.6 0 01-5 0 6.1 6.1 0 01-2-1.18 5.52 5.52 0 01-1.3-1.76 6.41 6.41 0 01-.59-2.18v-.57-.7-.71-.58a6.11 6.11 0 01.57-2.18 5.94 5.94 0 011.32-1.78 6.13 6.13 0 012-1.19 6.94 6.94 0 012.48-.47zm3.07 5.7a3.38 3.38 0 00-.37-1.3 2.52 2.52 0 00-.72-.82 2.41 2.41 0 00-.94-.42 4.55 4.55 0 00-1-.12 4.48 4.48 0 00-1 .12 2.41 2.41 0 00-.94.42 2.31 2.31 0 00-.72.82 3.21 3.21 0 00-.37 1.3v1.79a4.81 4.81 0 000 .5 3.17 3.17 0 00.37 1.3 2.44 2.44 0 00.72.83 2.74 2.74 0 00.94.42 5.1 5.1 0 001 .11 5.17 5.17 0 001-.11 2.74 2.74 0 00.94-.42 2.67 2.67 0 00.72-.83 3.33 3.33 0 00.37-1.3v-1.78c.02-.2.01-.37 0-.55zM260.38 61a1.83 1.83 0 00.42 1.26 2.06 2.06 0 001.56.47h1.55a.65.65 0 01.48.2.66.66 0 01.2.48v1.67a.68.68 0 01-.68.69H262a5.27 5.27 0 01-3.66-1.16 4.47 4.47 0 01-1.28-3.53v-5.44h-1.68a.7.7 0 01-.68-.69v-1.67a.7.7 0 01.68-.69h1.68v-4.13a.68.68 0 01.68-.68h1.93a.65.65 0 01.48.2.66.66 0 01.2.48v4.13h3.27a.72.72 0 01.69.69V55a.72.72 0 01-.69.69h-3.27zM277 52.59h3.52a.67.67 0 01.49.21.66.66 0 01.2.48V55a.66.66 0 01-.2.48.67.67 0 01-.49.21H277v9.45a.68.68 0 01-.68.69h-1.93a.66.66 0 01-.48-.2.67.67 0 01-.21-.49v-9.5H272a.63.63 0 01-.48-.21.62.62 0 01-.2-.48v-1.67a.62.62 0 01.2-.48.63.63 0 01.48-.21h1.67v-1.14a5.24 5.24 0 01.36-2.06 3.41 3.41 0 011-1.39 4.2 4.2 0 011.59-.76 8.68 8.68 0 012.06-.22h2.06a.65.65 0 01.48.2.66.66 0 01.2.48v1.67a.68.68 0 01-.68.69H279a2.58 2.58 0 00-1.47.34 1.47 1.47 0 00-.51 1.31zM289 52.34a6.94 6.94 0 012.52.43 6.19 6.19 0 011.91 1.23 5.94 5.94 0 011.32 1.78 5.72 5.72 0 01.57 2.18 4.51 4.51 0 010 .58v1.41a4.45 4.45 0 010 .57 6.4 6.4 0 01-.58 2.18 5.55 5.55 0 01-1.31 1.76 6.16 6.16 0 01-1.95 1.18A6.94 6.94 0 01289 66a7 7 0 01-2.53-.43 6.16 6.16 0 01-1.95-1.18 5.52 5.52 0 01-1.3-1.76 6.19 6.19 0 01-.59-2.18v-.57-.7-.71-.58a5.72 5.72 0 01.57-2.18 5.94 5.94 0 011.28-1.71 6.19 6.19 0 011.95-1.19 7 7 0 012.57-.47zm3 5.66a3.38 3.38 0 00-.36-1.3 2.43 2.43 0 00-.73-.82 2.37 2.37 0 00-.93-.42 4.55 4.55 0 00-1-.12 4.42 4.42 0 00-1 .12 2.41 2.41 0 00-.94.42 2.31 2.31 0 00-.72.82 3.21 3.21 0 00-.37 1.3v1.79a4.81 4.81 0 000 .5 3.17 3.17 0 00.37 1.3 2.44 2.44 0 00.72.83 2.74 2.74 0 00.94.42 5 5 0 001 .11 5.17 5.17 0 001-.11 2.7 2.7 0 00.93-.42 2.57 2.57 0 00.73-.83 3.33 3.33 0 00.36-1.3 3.25 3.25 0 000-.5v-1.24a3.68 3.68 0 000-.55zM298.11 53.28a.7.7 0 01.68-.69h1.93a.72.72 0 01.69.69v6.44a4.14 4.14 0 00.64 2.43 2.42 2.42 0 002.12.92 2.44 2.44 0 002.05-.92 3.82 3.82 0 00.71-2.43v-6.44a.72.72 0 01.69-.69h1.92a.67.67 0 01.49.21.66.66 0 01.2.48v11.81a.68.68 0 01-.69.69h-1.92a.66.66 0 01-.48-.2.67.67 0 01-.21-.49v-.58a6.22 6.22 0 01-1.44 1 4.23 4.23 0 01-2.08.47 6 6 0 01-2.56-.48 4.19 4.19 0 01-1.62-1.29 5 5 0 01-.87-1.89 9.87 9.87 0 01-.25-2.27zM326.1 65.09a.68.68 0 01-.68.69h-1.93a.66.66 0 01-.48-.2.67.67 0 01-.21-.49v-6.44a3.82 3.82 0 00-.71-2.43 2.58 2.58 0 00-2.18-.91 2.61 2.61 0 00-2.11.91 3.62 3.62 0 00-.78 2.43v6.44a.66.66 0 01-.2.49.67.67 0 01-.48.2h-1.93a.67.67 0 01-.48-.2.66.66 0 01-.2-.49V53.28a.62.62 0 01.2-.48.64.64 0 01.48-.21h1.93a.64.64 0 01.48.21.62.62 0 01.2.48v.58a5.77 5.77 0 011.55-1.09 4.71 4.71 0 012.1-.43 6.14 6.14 0 012.59.48 4.44 4.44 0 011.67 1.29 4.87 4.87 0 01.9 1.89 9.35 9.35 0 01.27 2.27zM334.54 52.34a6 6 0 011.41.15 5.54 5.54 0 011.09.38 4.06 4.06 0 01.8.5 3.73 3.73 0 01.53.49v-5.4a.68.68 0 01.69-.68H341a.62.62 0 01.48.2.63.63 0 01.21.48v16.63a.67.67 0 01-.21.49.66.66 0 01-.48.2h-1.92a.68.68 0 01-.69-.69v-.58a3.73 3.73 0 01-.53.49 4.06 4.06 0 01-.8.5 5.54 5.54 0 01-1.09.38 6 6 0 01-1.41.15 5.2 5.2 0 01-2.29-.49 5.36 5.36 0 01-1.75-1.32 6.37 6.37 0 01-1.14-1.9 6.9 6.9 0 01-.47-2.25v-.88-.89a6.9 6.9 0 01.47-2.25 6.49 6.49 0 011.14-1.9 5.36 5.36 0 011.75-1.32 5.2 5.2 0 012.27-.49zm-2.36 6.14a6.74 6.74 0 000 1.42 3.41 3.41 0 001 2.37 3.17 3.17 0 002.1.72 3.11 3.11 0 002.1-.74 3 3 0 001-2.1 8.31 8.31 0 00.05-1 8.5 8.5 0 00-.05-1 3 3 0 00-3.07-2.84 3.17 3.17 0 00-2.1.72 3.41 3.41 0 00-1.03 2.45z" fill="#455a64"/><path d="M196.44 90.76a.59.59 0 01.44.19.6.6 0 01.18.44v2.46a.6.6 0 01-.18.44.59.59 0 01-.44.19h-11a.58.58 0 01-.44-.19.61.61 0 01-.19-.44v-15a.61.61 0 01.19-.44.58.58 0 01.44-.19h10.83a.65.65 0 01.63.63v2.45a.61.61 0 01-.19.45.6.6 0 01-.44.18h-7.16v2.51h6.65a.58.58 0 01.44.19.61.61 0 01.19.44v2.46a.61.61 0 01-.19.44.58.58 0 01-.44.19h-6.65v2.6zM206.66 78.21a10.39 10.39 0 012.74.33 6 6 0 012.06 1 4.3 4.3 0 011.3 1.67 5.73 5.73 0 01.45 2.35 5.37 5.37 0 01-.72 2.9 4.61 4.61 0 01-2 1.75l2.88 5.53a.56.56 0 01.07.26.51.51 0 01-.51.51h-3.16a.87.87 0 01-.66-.21 2 2 0 01-.32-.42l-2.58-5H204v5a.6.6 0 01-.18.44.59.59 0 01-.44.19h-3a.59.59 0 01-.44-.19.6.6 0 01-.18-.44v-15a.6.6 0 01.18-.44.59.59 0 01.44-.19zm-2.63 7h2.63a3.18 3.18 0 001.62-.37 1.43 1.43 0 00.63-1.35 1.41 1.41 0 00-.63-1.34 3.18 3.18 0 00-1.62-.37H204zM222.81 78.21a10.39 10.39 0 012.74.33 6 6 0 012.06 1 4.21 4.21 0 011.3 1.67 5.73 5.73 0 01.45 2.35 5.37 5.37 0 01-.72 2.9 4.61 4.61 0 01-2 1.75l2.88 5.53a.56.56 0 01.07.26.51.51 0 01-.51.51h-3.16a.87.87 0 01-.66-.21 2 2 0 01-.32-.42l-2.58-5h-2.13v5a.61.61 0 01-.19.44.59.59 0 01-.44.19h-3.05a.59.59 0 01-.44-.19.6.6 0 01-.18-.44v-15a.6.6 0 01.18-.44.59.59 0 01.44-.19zm-2.62 7h2.62a3.18 3.18 0 001.62-.37 1.43 1.43 0 00.63-1.35 1.41 1.41 0 00-.63-1.34 3.18 3.18 0 00-1.62-.37h-2.62zM231.5 84.28a7.14 7.14 0 01.57-2.45 5.8 5.8 0 011.42-2 6.62 6.62 0 012.23-1.35 9.4 9.4 0 016.05 0 6.72 6.72 0 012.23 1.35 5.77 5.77 0 011.41 2 6.71 6.71 0 01.57 2.45c0 .66.05 1.36.05 2.09s0 1.41-.05 2a6.81 6.81 0 01-.57 2.46 6.1 6.1 0 01-1.41 2 6.54 6.54 0 01-2.25 1.34 9.58 9.58 0 01-6.05 0 6.45 6.45 0 01-2.23-1.34 6.13 6.13 0 01-1.42-2 7.25 7.25 0 01-.57-2.46v-2c0-.69-.01-1.43.02-2.09zm10.2 4c0-.28 0-.59.05-.93s0-.69 0-1.05v-1c0-.3 0-.63-.05-.9a3.57 3.57 0 00-.24-1.06 2.47 2.47 0 00-.56-.85 2.62 2.62 0 00-.89-.57 3.47 3.47 0 00-1.26-.21 3.37 3.37 0 00-1.25.21 2.57 2.57 0 00-.9.57 2.62 2.62 0 00-.56.85 3.57 3.57 0 00-.24 1.06q0 .4-.06.9v2.05c0 .34 0 .65.06.93a3.13 3.13 0 00.8 1.93 2.79 2.79 0 002.15.76 2.75 2.75 0 002.14-.76 3.26 3.26 0 00.81-1.91zM255.65 78.21a10.45 10.45 0 012.74.33 5.91 5.91 0 012.05 1 4.32 4.32 0 011.31 1.67 5.73 5.73 0 01.45 2.35 5.37 5.37 0 01-.72 2.9 4.58 4.58 0 01-2.05 1.75l2.88 5.53a.59.59 0 01.08.26.5.5 0 01-.16.36.49.49 0 01-.36.15h-3.16a.87.87 0 01-.66-.21 1.89 1.89 0 01-.31-.42l-2.58-5H253v5a.65.65 0 01-.63.63h-3a.65.65 0 01-.63-.63v-15a.65.65 0 01.63-.63zm-2.63 7h2.63a3.14 3.14 0 001.61-.37 1.41 1.41 0 00.64-1.35 1.4 1.4 0 00-.64-1.34 3.14 3.14 0 00-1.61-.37H253zM277.47 94.48a.61.61 0 01-.45-.19.6.6 0 01-.18-.44v-2.37h-7.32a.6.6 0 01-.44-.18.61.61 0 01-.19-.45v-2.46a1.63 1.63 0 01.3-1l6.95-8.76a1.38 1.38 0 01.38-.34 1.26 1.26 0 01.48-.08h3.51a.58.58 0 01.44.19.61.61 0 01.19.44v8.92h2a.63.63 0 01.44.19.6.6 0 01.18.44v2.46a.64.64 0 01-.18.45.62.62 0 01-.44.18h-2v2.37a.61.61 0 01-.19.44.58.58 0 01-.44.19zm-.47-6.72v-4.27l-3.39 4.27zM292.5 78a8 8 0 013 .51 6 6 0 012.08 1.37 5.62 5.62 0 011.23 2 8.82 8.82 0 01.48 2.41v4.09a8.82 8.82 0 01-.48 2.41 5.46 5.46 0 01-1.23 2 6 6 0 01-2.08 1.37 9.08 9.08 0 01-6 0 6 6 0 01-2.08-1.37 5.46 5.46 0 01-1.23-2 8.82 8.82 0 01-.48-2.41v-2-2.09a8.82 8.82 0 01.48-2.41 5.62 5.62 0 011.23-2 6 6 0 012.08-1.37 8 8 0 013-.51zm2.5 10.3c.06-1.32.06-2.62 0-3.91a5.58 5.58 0 00-.18-1.06 2.31 2.31 0 00-.4-.85 2 2 0 00-.75-.57 2.8 2.8 0 00-1.16-.21 2.83 2.83 0 00-1.16.21 2 2 0 00-.75.57 2.49 2.49 0 00-.41.85 4.35 4.35 0 00-.17 1.06 36.43 36.43 0 000 3.91 5.75 5.75 0 00.15 1.07 2.42 2.42 0 00.42.85 2 2 0 00.76.56 2.83 2.83 0 001.16.21 2.8 2.8 0 001.16-.21 2 2 0 00.76-.56 2.42 2.42 0 00.42-.85 5.75 5.75 0 00.15-1.07zM309.79 94.48a.65.65 0 01-.63-.63v-2.37h-7.32a.6.6 0 01-.44-.18.65.65 0 01-.19-.45v-2.46a1.57 1.57 0 01.31-1l6.94-8.76a1.27 1.27 0 01.39-.34 1.17 1.17 0 01.47-.08h3.51a.65.65 0 01.63.63v8.92h2a.65.65 0 01.63.63v2.46a.65.65 0 01-.19.45.6.6 0 01-.44.18h-2v2.37a.65.65 0 01-.63.63zm-.51-6.72v-4.27l-3.39 4.27z" fill="currentColor"/></g></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/service-error.svg b/ruoyi-plus-soybean/src/assets/svg-icon/service-error.svg
new file mode 100755
index 0000000..0120f1e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/service-error.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><ellipse cx="250" cy="378.78" rx="230.68" ry="112.11" fill="#f5f5f5"/><g fill="#e6e6e6"><path d="M152.14 433.48a2 2 0 01-2-1.63l-.45-2.42a24.58 24.58 0 01-3.07-.75h-.07l-4.26 2a2.47 2.47 0 01-1 .21 2.42 2.42 0 01-1.21-.31l-4.24-2.45a1.68 1.68 0 01-.15-2.88l2.57-1.84a9.54 9.54 0 01-.64-1l-5.41-.34a1.94 1.94 0 01-1.81-1.93V417a1.94 1.94 0 011.82-1.93l5.4-.34a9.54 9.54 0 01.64-1l-2.57-1.84a1.74 1.74 0 01-.74-1.48 1.71 1.71 0 01.89-1.4l4.24-2.45a2.42 2.42 0 011.21-.31 2.31 2.31 0 011 .21l4.26 2h.05a26.67 26.67 0 013.11-.77l.45-2.41a2.05 2.05 0 012-1.64h6.24a2 2 0 012 1.64l.46 2.41a26.48 26.48 0 013.07.76h.07l4.26-2a2.31 2.31 0 011-.21 2.42 2.42 0 011.21.31l4.24 2.45a1.71 1.71 0 01.89 1.4 1.76 1.76 0 01-.74 1.48l-2.57 1.84a9.54 9.54 0 01.64 1l5.4.34a1.94 1.94 0 011.82 1.93v3.16a1.94 1.94 0 01-1.82 1.93l-5.4.34a9.54 9.54 0 01-.64 1l2.57 1.84a1.76 1.76 0 01.74 1.48 1.72 1.72 0 01-.89 1.4l-4.24 2.45a2.42 2.42 0 01-1.21.31 2.47 2.47 0 01-1-.21l-4.26-2a26 26 0 01-3.1.76l-.46 2.42a2 2 0 01-2 1.63zm3.12-19.39a13.16 13.16 0 00-6.48 1.53c-1.45.84-2.28 1.91-2.28 2.95s.83 2.11 2.28 2.95a14.49 14.49 0 0013 0c1.45-.84 2.28-1.91 2.28-2.95s-.83-2.11-2.28-2.95a13.16 13.16 0 00-6.52-1.53zM200.64 405.59a2.25 2.25 0 01-1.6-.7l-1.69-1.77h-2.49l-1.8 1.89a2 2 0 01-1.46.58 2.5 2.5 0 01-.54-.07l-4.2-1a1.84 1.84 0 01-1.24-1 1.6 1.6 0 010-1.37l.67-1.32c-.36-.2-.7-.41-1-.63l-4 .66a1.39 1.39 0 01-.3 0 2.14 2.14 0 01-1.69-.89l-1.59-2.21a1.84 1.84 0 01-.35-1 1.62 1.62 0 011.14-1.57l3.06-1a.66.66 0 010-.14l-2.95-.94a1.71 1.71 0 01-1.26-1.5v-.11a1.53 1.53 0 01.3-.92l1.73-2.42a2 2 0 011.63-.77 2 2 0 01.35 0l3.91.66c.28-.19.59-.38.91-.56l.12-.07-.62-1.23a1.62 1.62 0 01-.19-.69v-.32a1.65 1.65 0 011.25-1.41l4.39-1a1.49 1.49 0 01.43-.05 2.22 2.22 0 011.59.7l1.7 1.77h2.48l1.8-1.89a2 2 0 011.46-.58 1.88 1.88 0 01.53.07l4.21 1a1.71 1.71 0 011.38 1.63 1.41 1.41 0 01-.15.69l-.68 1.35.14.08a10.26 10.26 0 01.88.55l4-.66a1.36 1.36 0 01.29 0 2.17 2.17 0 011.7.88l1.59 2.22a1.73 1.73 0 01.35 1v.3a1.62 1.62 0 01-1.1 1.3l-3.08 1v.14l3 .93a1.72 1.72 0 011 2.53c-.51.79-1.1 1.53-1.64 2.29l-.09.13a2 2 0 01-1.63.77h-.36l-3.9-.65c-.33.22-.67.43-1 .63l.62 1.23a1.67 1.67 0 010 1.51 1.63 1.63 0 01-1.13.91l-4.36 1.05a2.06 2.06 0 01-.52-.01zM190 395a3.29 3.29 0 00.83.84 4.32 4.32 0 00.5.33 7.8 7.8 0 002.12.81 11.25 11.25 0 002.62.3 9.87 9.87 0 004.73-1.1 5.65 5.65 0 00.51-.33 3.16 3.16 0 00.76-.74 1.35 1.35 0 00.13-1.6l-.08-.12-.1-.15-.11-.13a6.51 6.51 0 00-3.11-1.56 11.32 11.32 0 00-2.62-.3c-2.62 0-5.09.89-6 2.17a1.37 1.37 0 00-.23 1.38v.11l.11.19zM257.82 424.33l35.88-20.72a1.17 1.17 0 000-2.22l-8.8-5.08a4.28 4.28 0 00-3.85 0L245.18 417a1.17 1.17 0 000 2.22l4.39 2.54-45 26c-1.06.61-.61.82-.12 1.11s.87.54 1.93-.07l45-26 2.59 1.5a4.28 4.28 0 003.85.03zM244.29 384.57a16.13 16.13 0 01-8-1.95c-.25-.15-.49-.3-.72-.46l-1.32-.93 11.29-1.66 1-2.13-3.05-1.79-10.76 1.58.35-1a7 7 0 013.31-3.56 16.27 16.27 0 017.9-1.88 18.75 18.75 0 013.62.35l36.81-21.25a4.43 4.43 0 01-.31-3 6.67 6.67 0 013.42-4 16.34 16.34 0 017.91-1.88 16.14 16.14 0 018 2c.26.15.49.3.72.46l1.33.93-11.29 1.65-1 2.14 3.05 1.78 10.76-1.57-.35 1a7 7 0 01-3.31 3.55 16.27 16.27 0 01-7.9 1.88 18.83 18.83 0 01-3.62-.34l-36.81 21.25a4.35 4.35 0 01.31 3 6.64 6.64 0 01-3.42 3.95 16.17 16.17 0 01-7.92 1.88z"/><ellipse cx="387.02" cy="400.95" rx="57.04" ry="32.93"/><path d="M143.07 320.2l62.06 35.83 57.88-33.53-62.06-35.82-57.88 33.52zM119.51 407l83.68-48.31-73.22-42.27-83.67 48.31L119.51 407z"/></g><path fill="currentColor" d="M205.12 353.37l-58.75-33.93v-2.56l54-31.18 58.75 33.92v2.57l-54 31.18z"/><path fill="currentColor" d="M146.37 316.88l58.75 33.92 54-31.18-58.75-33.92-54 31.18z"/><path opacity=".15" d="M146.37 316.88l58.75 33.92 54-31.18-58.75-33.92-54 31.18z"/><path d="M219.68 331a4.34 4.34 0 003.94 0 1.2 1.2 0 000-2.27 4.34 4.34 0 00-3.94 0 1.2 1.2 0 000 2.27z" fill="#37474f"/><path opacity=".3" d="M205.12 350.8v2.57l-58.75-33.93v-2.56l58.75 33.92z"/><path d="M128.34 115.9l65.46 37.79a4.85 4.85 0 012.19 3.79v198.81a4.85 4.85 0 01-2.19 3.8L123 401a4.83 4.83 0 01-4.38 0l-65.46-37.83a4.83 4.83 0 01-2.16-3.79V160.57a4.85 4.85 0 012.19-3.8L124 115.9a4.83 4.83 0 014.34 0z" fill="currentColor"/><path d="M51 160.56v198.82a4.87 4.87 0 002.19 3.79L118.62 401a4.36 4.36 0 002.19.53V197.62a4.49 4.49 0 01-2.19-.53L53.16 159.3l-.6-.5-.78-.64a4.57 4.57 0 00-.78 2.4z" fill="currentColor"/><path d="M51 160.56v198.82a4.87 4.87 0 002.19 3.79L118.62 401a4.36 4.36 0 002.19.53V197.62a4.49 4.49 0 01-2.19-.53L53.16 159.3l-.6-.5-.78-.64a4.57 4.57 0 00-.78 2.4z" opacity=".3"/><path d="M53.16 156.77L124 115.9a4.81 4.81 0 014.37 0l65.46 37.79a1.34 1.34 0 010 2.53L123 197.1a4.89 4.89 0 01-4.38 0l-65.46-37.8a1.34 1.34 0 010-2.53z" fill="currentColor"/><path d="M53.16 156.77L124 115.9a4.81 4.81 0 014.37 0l65.46 37.79a1.34 1.34 0 010 2.53L123 197.1a4.89 4.89 0 01-4.38 0l-65.46-37.8a1.34 1.34 0 010-2.53z" opacity=".15"/><path fill="#455a64" d="M188.13 291.62l-58.75 33.92v62.35l58.75-33.92v-62.35z"/><path d="M159.26 336.16a.62.62 0 00-.61.06L134.75 350a1.79 1.79 0 00-.61.64l-1.6-.93a1.77 1.77 0 01.6-.64l23.4-13.45a1.85 1.85 0 011.71 0z" fill="#263238"/><path d="M133.89 351.46v26.77c0 .55.39.77.86.5l23.9-13.73a1.9 1.9 0 00.86-1.48v-26.8c0-.55-.39-.77-.86-.5L134.75 350a1.89 1.89 0 00-.86 1.46z" fill="#37474f"/><path d="M134.12 378.78l-1-.57a1.89 1.89 0 01-.85-1.48v-26.2a1.7 1.7 0 01.25-.85l1.6.93a1.81 1.81 0 00-.25.85v26.77a.65.65 0 00.25.55z" fill="#263238"/><path d="M135.8 376.32a.46.46 0 01-.2-.05.51.51 0 01-.22-.46V352a1.34 1.34 0 01.61-1l21.31-12.25a.5.5 0 01.51 0 .51.51 0 01.22.46V363a1.32 1.32 0 01-.6 1.05l-21.31 12.25a.64.64 0 01-.32.02zm21.8-37.44a.41.41 0 00-.18.05l-21.32 12.26a1.08 1.08 0 00-.48.83v23.79c0 .12 0 .21.09.24a.26.26 0 00.26 0l21.31-12.25a1.08 1.08 0 00.48-.83v-23.83c0-.12 0-.21-.09-.24a.12.12 0 00-.07-.02z" fill="currentColor"/><path d="M137.48 373.19a.21.21 0 01-.12 0 .26.26 0 01-.13-.25v-20.22a.72.72 0 01.29-.5l18.21-10.49a.28.28 0 01.29 0 .29.29 0 01.13.26v20.22a.64.64 0 01-.29.51l-18.19 10.45a.32.32 0 01-.19.02zm0-.24zm18.4-31l-18.22 10.5a.5.5 0 00-.16.28V373l18.18-10.45a.39.39 0 00.17-.29v-20.22a.06.06 0 00.04-.11zM138.71 346.89a.25.25 0 01-.24-.25v-5.7a.24.24 0 11.48 0v5.7a.25.25 0 01-.24.25zM142.7 344.6a.24.24 0 01-.24-.24v-5.71a.24.24 0 01.24-.24.24.24 0 01.24.24v5.71a.24.24 0 01-.24.24zM146.69 342.31a.24.24 0 01-.25-.24v-5.7a.25.25 0 01.49 0v5.7a.24.24 0 01-.24.24zM150.67 340a.24.24 0 01-.24-.24v-5.71a.25.25 0 01.49 0v5.71a.25.25 0 01-.25.24zM154.66 337.74a.24.24 0 01-.24-.24v-5.7a.24.24 0 01.24-.24.23.23 0 01.24.24v5.7a.23.23 0 01-.24.24z" fill="currentColor"/><path d="M159.26 315.1a.6.6 0 00-.61.06l-23.9 13.75a1.84 1.84 0 00-.61.63l-1.6-.92a1.69 1.69 0 01.6-.64l23.4-13.46a1.91 1.91 0 011.71 0z" fill="#263238"/><path d="M133.89 330.39v13.5c0 .55.39.77.86.5l23.9-13.75a1.9 1.9 0 00.86-1.48v-13.51c0-.54-.39-.76-.86-.49l-23.9 13.75a1.88 1.88 0 00-.86 1.48z" fill="#37474f"/><path d="M134.12 344.44l-1-.57a1.89 1.89 0 01-.85-1.48v-12.93a1.7 1.7 0 01.25-.84l1.6.92a1.84 1.84 0 00-.25.85v13.51a.64.64 0 00.25.54z" fill="#263238"/><path d="M135.8 342a.46.46 0 01-.2-.05.51.51 0 01-.22-.46V331a1.34 1.34 0 01.61-1l21.31-12.26a.46.46 0 01.73.43v10.51a1.32 1.32 0 01-.6 1.05l-21.31 12.25a.64.64 0 01-.32.02zm21.8-24.17a.32.32 0 00-.18.06l-21.32 12.24a1.08 1.08 0 00-.48.83v10.51c0 .12 0 .21.09.24a.26.26 0 00.26 0l21.31-12.25a1.08 1.08 0 00.48-.83v-10.55a.29.29 0 00-.09-.25z" fill="currentColor"/><path d="M137.48 338.86a.21.21 0 01-.12 0 .26.26 0 01-.13-.25v-6.91a.76.76 0 01.29-.51l18.21-10.49a.3.3 0 01.29 0 .27.27 0 01.13.25v7a.64.64 0 01-.29.51l-18.19 10.45a.33.33 0 01-.19-.05zm0-.25zm18.4-17.75l-18.22 10.49a.56.56 0 00-.16.29v6.95l18.18-10.45a.39.39 0 00.17-.29v-7s.04.02.04.01zM164.09 312.35a3.14 3.14 0 00-1.42 2.45c0 .9.64 1.26 1.42.81a3.14 3.14 0 001.41-2.44c0-.9-.64-1.27-1.41-.82zM164.09 336.35a3.14 3.14 0 00-1.42 2.45c0 .9.64 1.26 1.42.81a3.14 3.14 0 001.41-2.44c0-.9-.64-1.27-1.41-.82zM164.09 342.81a3.12 3.12 0 00-1.42 2.44c0 .9.64 1.26 1.42.81a3.12 3.12 0 001.41-2.44c0-.9-.64-1.26-1.41-.81zM164.09 349.26a3.14 3.14 0 00-1.42 2.45c0 .89.64 1.26 1.42.81a3.14 3.14 0 001.41-2.44c0-.9-.64-1.27-1.41-.82zM164.09 355.72a3.12 3.12 0 00-1.42 2.44c0 .9.64 1.26 1.42.81a3.14 3.14 0 001.41-2.44c0-.9-.64-1.26-1.41-.81zM164.09 318.18a3.14 3.14 0 00-1.42 2.45c0 .9.64 1.26 1.42.81a3.14 3.14 0 001.41-2.44c0-.9-.64-1.27-1.41-.82zM164.09 324a3.14 3.14 0 00-1.42 2.45c0 .9.64 1.26 1.42.81a3.14 3.14 0 001.41-2.44c0-.89-.64-1.26-1.41-.82z" fill="currentColor"/><path d="M179.52 315.4l-.14-1.31a.14.14 0 00-.18-.13 2.94 2.94 0 01-.65.07.3.3 0 00-.26.13l-.29.43 1.54.88a.14.14 0 01-.02-.07zm1.11-9.18a3 3 0 010 .31 5 5 0 01-2.28 4l-.28.15c.14 1.22 1.1 1.67 2.26 1a5 5 0 002.28-3.95c.02-1.4-.85-2-1.98-1.51zm4.94-1.16l-1.55-.9-.23.47a.33.33 0 000 .29 3 3 0 01.27.6.14.14 0 00.2.09l1.2-.53a.12.12 0 01.1 0zm-3-3.42l-.87-.51h-.13l-.71 1.07-.9-.52-.65-.38h-.07l-.93.54-.42.24h-.05v.06a.41.41 0 000 .08l-.13 1.51a.17.17 0 010 .05.11.11 0 010 .05.61.61 0 01-.11.18 8.13 8.13 0 00-.76.79.14.14 0 01-.11.07.26.26 0 01-.15 0l-.87-.3h-.2l-.41.71-.43.75-.15.26a.21.21 0 000 .13.23.23 0 00.07.13l.7.6.05.07a.23.23 0 010 .11.19.19 0 010 .08 8.41 8.41 0 00-.32 1.05.51.51 0 01-.08.17l-.09.1-1.23.86s-.06.06-.09.1a.34.34 0 000 .17v1.56a.12.12 0 000 .1l1.57.9-.57 1.15a.18.18 0 00.07.23l1.9 1.1h.08a.46.46 0 01-.07-.07.2.2 0 010-.17l.8-1.61a.14.14 0 000-.07.29.29 0 000-.22 3.29 3.29 0 01-.26-.6.14.14 0 00-.2-.09l-.61.27-.6.27a.09.09 0 01-.1 0 .1.1 0 01-.05-.1v-1.56a.33.33 0 01.05-.16s0-.08.09-.1l1.23-.87a.32.32 0 00.1-.1.37.37 0 00.07-.16 8.41 8.41 0 01.32-1.05.25.25 0 000-.09.27.27 0 00-.08-.18l-.7-.6a.16.16 0 01-.06-.12.2.2 0 010-.13l.29-.49.34-.61.36-.62a.25.25 0 01.16-.1h.09l.86.3a.22.22 0 00.16 0 .26.26 0 00.11-.07 8.22 8.22 0 01.75-.8.42.42 0 00.11-.17.23.23 0 000-.11l.14-1.5a.24.24 0 010-.08.34.34 0 01.14-.17l.25-.15.54-.31.56-.32a.1.1 0 01.09 0s.05.05.06.09l.07.69.06.62a.14.14 0 00.18.13 3 3 0 01.65-.07.24.24 0 00.19-.06.16.16 0 00.07-.07l1-1.5a.17.17 0 01.23-.05z" fill="#263238"/><path d="M179.54 303.46a.24.24 0 000 .08l-.14 1.5a.23.23 0 010 .11l-2-1.15v-.05a.17.17 0 000-.05l.13-1.51a.41.41 0 010-.08z" opacity=".1"/><path d="M178.38 306.19a.22.22 0 01-.16 0l-.86-.3h-.09l-2-1.13h.06l.87.3a.26.26 0 00.15 0zM176.94 308.73l-2-1.14v-.1l-.05-.07-.7-.6a.23.23 0 01-.07-.13l2 1.15a.16.16 0 00.06.12l.7.6a.27.27 0 01.06.17zM176.93 313.29a.14.14 0 010 .07l-.8 1.61a.2.2 0 000 .17l-1.89-1.1a.18.18 0 01-.07-.23l.57-1.15.42.24a.09.09 0 00.1 0l.6-.27zM179.52 315.4a.14.14 0 000 .07l-1.54-.88.29-.43a.3.3 0 01.26-.13 2.94 2.94 0 00.65-.07.14.14 0 01.18.13z" opacity=".15"/><path d="M183.64 302.28a.17.17 0 00-.23.05l-1 1.5a.16.16 0 01-.07.07l-.56-.32-.52-.3-.07-.69s0-.08-.06-.09l-.4-.24.71-1.07a.22.22 0 01.11 0l.87.51z" opacity=".1"/><path d="M176.54 310a.32.32 0 01-.1.1l-1.23.87s-.06.06-.09.1l-2-1.15s.05-.08.09-.1l1.23-.86.09-.1z" opacity=".15"/><path d="M185.48 305.07l-1.2.54a.14.14 0 01-.2-.09 3 3 0 00-.27-.6.33.33 0 010-.29l.8-1.61a.19.19 0 00-.07-.24l-.87-.5a.18.18 0 00-.24.05l-1 1.5a.3.3 0 01-.26.13 3 3 0 00-.65.07.14.14 0 01-.18-.13l-.13-1.31a.1.1 0 00-.16-.08l-1.35.78a.33.33 0 00-.16.25l-.14 1.5a.47.47 0 01-.14.28 8.22 8.22 0 00-.75.8.27.27 0 01-.27.07l-.86-.3a.22.22 0 00-.25.09l-1 1.72a.21.21 0 000 .25l.7.6a.27.27 0 01.07.27 8.41 8.41 0 00-.32 1.05.41.41 0 01-.17.26l-1.23.87a.34.34 0 00-.14.26v1.56a.1.1 0 00.15.1l1.21-.54a.14.14 0 01.2.09 3.29 3.29 0 00.26.6.3.3 0 010 .29l-.8 1.61a.19.19 0 00.07.24l.87.5a.18.18 0 00.24 0l1-1.5a.3.3 0 01.26-.13 2.94 2.94 0 00.65-.07.14.14 0 01.18.13l.14 1.31c0 .09.07.13.16.08l1.35-.78a.38.38 0 00.16-.25l.13-1.5a.47.47 0 01.14-.28 7.16 7.16 0 00.75-.8.27.27 0 01.27-.07l.87.3a.21.21 0 00.24-.09l1-1.72a.2.2 0 000-.25l-.7-.6a.27.27 0 01-.07-.27 8.41 8.41 0 00.32-1.05.46.46 0 01.17-.26l1.23-.87a.34.34 0 00.14-.26v-1.56a.1.1 0 00-.15-.15zm-5.13 6.56c-1.26.73-2.28.14-2.28-1.32a5 5 0 012.28-3.95c1.26-.73 2.28-.14 2.28 1.32a5 5 0 01-2.28 3.95z" fill="#37474f"/><path d="M176.19 318.62h-.06l-.32-.18-.91-.53h-.2l-.85.08h-.08l-.46 1.23a.23.23 0 01-.07.1l-.11.08-.28.14-.88-.51a.16.16 0 00-.09 0l-.66.7-.27.28a.13.13 0 000 .06v.06a.11.11 0 000 .05l.26.88v.11a.42.42 0 010 .1 6.17 6.17 0 00-.43.74.24.24 0 01-.1.1.24.24 0 01-.1.05l-.8.2h-.06a.1.1 0 00-.05 0 .07.07 0 000 .06l-.3 1-.1.36v.1l.53.31h.07l.29.16v.36a.28.28 0 01-.07.21l-.81 1a.34.34 0 000 .11.12.12 0 000 .1l.38.84 1.61.94h.08v-.08l-.19-.41-.16-.36a.2.2 0 010-.11.14.14 0 01.05-.09l.81-1a.31.31 0 000-.1.3.3 0 000-.12 4.09 4.09 0 010-.7.12.12 0 00-.12-.12h-.79a.09.09 0 010-.09l.18-.61.2-.7v-.06a.26.26 0 01.15-.09l.8-.2a.16.16 0 00.1-.05.19.19 0 00.1-.1 7.89 7.89 0 01.43-.74.3.3 0 000-.1.22.22 0 000-.11l-.26-.88a.17.17 0 010-.1.17.17 0 01.06-.1l.63-.67.3-.32s.1-.06.13 0l.43.66h.18a4.26 4.26 0 01.53-.31.27.27 0 00.1-.06.26.26 0 00.08-.11l.46-1.24v-.06l.31-.08.68-.07h.07zm-3.6 9a.13.13 0 00-.08-.15 1.51 1.51 0 01-.37-.22.18.18 0 00-.2 0l-.86.79a.21.21 0 01-.1 0l.85.49.53.31a.09.09 0 010-.08zm3.8-8.92a.1.1 0 010 .09l-.27 1.21a.14.14 0 00.09.15 2 2 0 01.37.22h.19l.86-.8c.05 0 .1-.05.13 0zm-1.13 9l-.43-.66c0-.05-.11-.05-.18 0l-.26.16.89.52zm.69-5.51a.85.85 0 00-.67-.53h-.1a1.39 1.39 0 00-.6.13 1.69 1.69 0 010 .23 3.74 3.74 0 01-1.71 2.95l-.19.11a1.24 1.24 0 00.11.44.84.84 0 00.8.54h.33a2.42 2.42 0 001.1-.72 3.23 3.23 0 00.98-3.15zm1.34 0v.34a.12.12 0 00.13.12h.78z" fill="#263238"/><path d="M175.26 327.7l-.89-.52.26-.16c.07-.05.15-.05.18 0z" opacity=".15"/><path d="M172.32 328.88a.09.09 0 000 .08l-.53-.31-.85-.49a.21.21 0 00.1 0l.86-.79a.18.18 0 01.2 0 1.51 1.51 0 00.37.22.13.13 0 01.08.15zM171.46 326a.28.28 0 01-.06.09l-.82 1a.18.18 0 000 .1l-1.71-1a.51.51 0 010-.12l.81-1s0-.06.05-.08z" opacity=".1"/><path d="M171.47 325.17v.7a.26.26 0 010 .12l-1.72-1a.21.21 0 000-.13v-.36l.8.47h.79a.12.12 0 01.13.2z" opacity=".05"/><path d="M172 323.15a.19.19 0 01-.11 0l-.8.2a.23.23 0 00-.15.1l-1.71-1h.11l.8-.19a.27.27 0 00.1-.06zM172.59 322.09a.22.22 0 010 .11l-1.71-1a.24.24 0 000-.12l-.26-.88v-.05l1.72 1a.38.38 0 000 .1zM175.32 318.88v.06l-.45 1.23a.31.31 0 01-.09.12l-1.72-1 .07-.09.47-1.24z" opacity=".1"/><path d="M175.32 318.88a.27.27 0 01.14-.07h.17" fill="none"/><path d="M178.18 322.71h-.78a.12.12 0 01-.13-.12v-.34-.36a.34.34 0 01.08-.21l.82-1a.22.22 0 000-.2l-.38-.85h-.13l-.86.8c-.07.05-.15.07-.19 0a2 2 0 00-.37-.22.14.14 0 01-.09-.15l.27-1.21a.1.1 0 000-.09.11.11 0 00-.08 0l-.68.07h-.17a.27.27 0 00-.14.07v.06l-.46 1.24a.26.26 0 01-.08.11.27.27 0 01-.1.06 4.26 4.26 0 00-.53.31H174l-.43-.66h-.13l-.3.32-.63.67a.26.26 0 00-.07.1.17.17 0 000 .1l.26.88a.22.22 0 010 .11.3.3 0 010 .1 7.89 7.89 0 00-.43.74.19.19 0 01-.1.1.16.16 0 01-.1.05l-.8.2a.26.26 0 00-.15.09v.06l-.2.7-.18.61a.09.09 0 000 .09h.79a.12.12 0 01.12.12 4.09 4.09 0 000 .7.3.3 0 010 .12.31.31 0 010 .1l-.81 1a.23.23 0 00-.05.2l.16.36v.05l.05.11.07.14v.19a.21.21 0 00.1 0l.86-.79a.18.18 0 01.2 0 1.51 1.51 0 00.37.22.13.13 0 01.08.15l-.27 1.22a.09.09 0 000 .08.11.11 0 00.08 0l.85-.08a.27.27 0 00.19-.13l.46-1.23a.32.32 0 01.18-.18l.27-.15.26-.16c.07-.05.15-.05.18 0l.43.66a.12.12 0 00.14 0l.93-1a.21.21 0 00.07-.2l-.26-.88a.34.34 0 01.05-.21 7.89 7.89 0 00.43-.74.28.28 0 01.2-.15l.8-.19a.31.31 0 00.18-.15l.4-1.36a.08.08 0 00-.07-.05zm-4.25 3.3h-.33a.84.84 0 01-.8-.54 1.24 1.24 0 01-.11-.44 2.11 2.11 0 010-.25 3.83 3.83 0 011.05-2.41 2.75 2.75 0 01.65-.52l.21-.11a1.39 1.39 0 01.6-.13h.1a.85.85 0 01.67.53 3.23 3.23 0 01-.92 3.1 2.42 2.42 0 01-1.12.76z" fill="#37474f"/><path d="M175.34 341.7a6.41 6.41 0 00-3.62 3 5.94 5.94 0 00.19 5.64 6.5 6.5 0 00-1 3.49 7.74 7.74 0 005.1 6.89l.29.1.87-.5a8.29 8.29 0 01-4.68-4.3 5.85 5.85 0 01.15-4.59 7.35 7.35 0 001.66 1.57c1.74 1.14 5.13 2 6.72.17a2.84 2.84 0 00-.13-3.71 5.56 5.56 0 00-4-1.87 5.62 5.62 0 00-4.16 1.75 5.46 5.46 0 01-.41-2.58 4.92 4.92 0 013.52-4.13c1.44 2.24 4.62 4.28 7.07 2.37s-1.46-3.64-3.1-3.79a10.71 10.71 0 00-3.39.19 3.68 3.68 0 01-.22-2 4.39 4.39 0 011.67-2.6 5.6 5.6 0 016.6-.4.36.36 0 10.46-.56c-3.93-3.6-11.65.81-9.59 5.86zm-.62 7.42a4.17 4.17 0 013.69-.17c1 .45 2.2 1.4 2 2.66-.28 2.19-3.23 1.58-4.61 1a5.79 5.79 0 01-2.59-2.24 4.77 4.77 0 011.51-1.25zm3-6.91a7.28 7.28 0 013.08.22c.47.15 1.81.6 1.74 1.28 0 .42-.65.74-1 .88a2.54 2.54 0 01-1.61.08 5.49 5.49 0 01-2.78-2l-.23-.33a6.84 6.84 0 01.77-.13z" fill="currentColor"/><path d="M175.34 341.7a6.41 6.41 0 00-3.62 3 5.94 5.94 0 00.19 5.64 6.5 6.5 0 00-1 3.49 7.74 7.74 0 005.1 6.89l.29.1.87-.5a8.29 8.29 0 01-4.68-4.3 5.85 5.85 0 01.15-4.59 7.35 7.35 0 001.66 1.57c1.74 1.14 5.13 2 6.72.17a2.84 2.84 0 00-.13-3.71 5.56 5.56 0 00-4-1.87 5.62 5.62 0 00-4.16 1.75 5.46 5.46 0 01-.41-2.58 4.92 4.92 0 013.52-4.13c1.44 2.24 4.62 4.28 7.07 2.37s-1.46-3.64-3.1-3.79a10.71 10.71 0 00-3.39.19 3.68 3.68 0 01-.22-2 4.39 4.39 0 011.67-2.6 5.6 5.6 0 016.6-.4.36.36 0 10.46-.56c-3.93-3.6-11.65.81-9.59 5.86zm-.62 7.42a4.17 4.17 0 013.69-.17c1 .45 2.2 1.4 2 2.66-.28 2.19-3.23 1.58-4.61 1a5.79 5.79 0 01-2.59-2.24 4.77 4.77 0 011.51-1.25zm3-6.91a7.28 7.28 0 013.08.22c.47.15 1.81.6 1.74 1.28 0 .42-.65.74-1 .88a2.54 2.54 0 01-1.61.08 5.49 5.49 0 01-2.78-2l-.23-.33a6.84 6.84 0 01.77-.13z" fill="#fff" opacity=".7"/><path d="M172.27 352.4a5.43 5.43 0 01.35-1 3.86 3.86 0 01-.71-1.09 7 7 0 00-.54 1.06 8.6 8.6 0 00.9 1.03zM174.6 343.21a7.24 7.24 0 011.22-.59 5.7 5.7 0 01-.48-.92 7.31 7.31 0 00-1.36.63 6.94 6.94 0 00.62.88zM176.28 360.81l.87-.5a10.72 10.72 0 01-1.08-.52l-.93.53a7.2 7.2 0 00.85.39zM199.46 344.67a6.45 6.45 0 00-4.52-1.29 6 6 0 00-4.54 3.36 6.44 6.44 0 00-2.28.5 7.83 7.83 0 00-1.17.64 7.58 7.58 0 00-2.93 7.2c0 .31.09.61.15.91l.06.22.77-.44v-.24a7.81 7.81 0 011.08-5.11 5.7 5.7 0 012.07-1.86 6 6 0 011.79-.62 6.89 6.89 0 00-.33 2.28c0 2.08 1.26 5.36 3.67 5.63a2.84 2.84 0 003-2.22 5.47 5.47 0 00-.74-4.36 5.63 5.63 0 00-3.79-2.44 5.4 5.4 0 011.88-1.8 4.92 4.92 0 015.4.56c-1 2.46-.9 6.22 2.06 7.15s2.16-3.26 1.37-4.69a11.14 11.14 0 00-2.08-2.69 3.64 3.64 0 011.54-1.33 4.36 4.36 0 013.08-.1 5.62 5.62 0 014.08 5.21.36.36 0 10.72.06c.67-5.2-7.35-9.09-10.34-4.53zm-6.46 3.7a4.16 4.16 0 012.23 2.94c.21 1.1.1 2.61-1 3.19-2 1-3.14-1.77-3.45-3.24a5.87 5.87 0 01.38-3.4 4.84 4.84 0 011.84.51zm7.37-1.48a7.41 7.41 0 011.57 2.66c.14.47.54 1.84-.08 2.16-.36.2-1-.11-1.28-.31a2.47 2.47 0 01-1-1.28 5.44 5.44 0 01.08-3.43 3 3 0 01.13-.39c.21.2.41.39.58.59z" fill="currentColor"/><path d="M199.46 344.67a6.45 6.45 0 00-4.52-1.29 6 6 0 00-4.54 3.36 6.44 6.44 0 00-2.28.5 7.83 7.83 0 00-1.17.64 7.58 7.58 0 00-2.93 7.2c0 .31.09.61.15.91l.06.22.77-.44v-.24a7.81 7.81 0 011.08-5.11 5.7 5.7 0 012.07-1.86 6 6 0 011.79-.62 6.89 6.89 0 00-.33 2.28c0 2.08 1.26 5.36 3.67 5.63a2.84 2.84 0 003-2.22 5.47 5.47 0 00-.74-4.36 5.63 5.63 0 00-3.79-2.44 5.4 5.4 0 011.88-1.8 4.92 4.92 0 015.4.56c-1 2.46-.9 6.22 2.06 7.15s2.16-3.26 1.37-4.69a11.14 11.14 0 00-2.08-2.69 3.64 3.64 0 011.54-1.33 4.36 4.36 0 013.08-.1 5.62 5.62 0 014.08 5.21.36.36 0 10.72.06c.67-5.2-7.35-9.09-10.34-4.53zm-6.46 3.7a4.16 4.16 0 012.23 2.94c.21 1.1.1 2.61-1 3.19-2 1-3.14-1.77-3.45-3.24a5.87 5.87 0 01.38-3.4 4.84 4.84 0 011.84.51zm7.37-1.48a7.41 7.41 0 011.57 2.66c.14.47.54 1.84-.08 2.16-.36.2-1-.11-1.28-.31a2.47 2.47 0 01-1-1.28 5.44 5.44 0 01.08-3.43 3 3 0 01.13-.39c.21.2.41.39.58.59z" fill="#fff" opacity=".7"/><path d="M188.91 348.21a5.68 5.68 0 011-.27 4.22 4.22 0 01.49-1.2 6.85 6.85 0 00-1.18.17 8.27 8.27 0 00-.31 1.3zM197.79 344.92a6 6 0 011.18.67 6.92 6.92 0 01.49-.93 7.66 7.66 0 00-1.31-.73 5.86 5.86 0 00-.36.99zM184.23 356.21l-.06-.22c-.06-.3-.11-.6-.15-.91l.95.45v.24z" fill="currentColor"/><path d="M185.94 245l-54.38 31.4a4.84 4.84 0 00-2.18 3.79v18.66c0 1.4 1 2 2.19 1.27l54.37-31.39a4.87 4.87 0 002.19-3.79v-18.67c0-1.39-.98-1.96-2.19-1.27z" fill="#455a64"/><path d="M184 253.23a4.7 4.7 0 01-2.19 3.64l-46.16 26.65c-1.21.69-2.19.2-2.19-1.11a4.7 4.7 0 012.19-3.64l46.16-26.65c1.19-.69 2.19-.2 2.19 1.11z" fill="currentColor"/><path d="M184 262.72a4.7 4.7 0 01-2.19 3.64L135.67 293c-1.21.7-2.19.21-2.19-1.1a4.7 4.7 0 012.19-3.64l46.16-26.65c1.17-.7 2.17-.2 2.17 1.11z" fill="#263238"/><path d="M181.83 261.61l-19.59 11.31v4.74l19.59-11.3a4.7 4.7 0 002.19-3.64c-.02-1.31-1.02-1.81-2.19-1.11zM110.54 201.07c-2.27-1.31-4.11-.12-4.11 2.68v100.38a9.4 9.4 0 004.11 7.44c2.27 1.32 4.11.12 4.11-2.67V208.51a9.43 9.43 0 00-4.11-7.44z" fill="currentColor"/><path d="M110.54 201.07c-2.27-1.31-4.11-.12-4.11 2.68v100.38a9.4 9.4 0 004.11 7.44c2.27 1.32 4.11.12 4.11-2.67V208.51a9.43 9.43 0 00-4.11-7.44z" opacity=".5"/><path d="M110.54 309.17a4.7 4.7 0 01-2.05-3.71V204.77c0-1.4.92-2 2.05-1.35a4.69 4.69 0 012 3.72v100.69c.05 1.4-.87 2-2 1.34z" fill="#37474f"/><path d="M110.54 309.17a4.7 4.7 0 01-2.05-3.71v-49.2l4.1 2.37v49.2c0 1.4-.92 2-2.05 1.34zM98.22 194c-2.27-1.32-4.11-.12-4.11 2.67V297a9.43 9.43 0 004.11 7.44c2.26 1.31 4.1.11 4.1-2.68V201.39a9.41 9.41 0 00-4.1-7.39z" fill="currentColor"/><path d="M98.22 194c-2.27-1.32-4.11-.12-4.11 2.67V297a9.43 9.43 0 004.11 7.44c2.26 1.31 4.1.11 4.1-2.68V201.39a9.41 9.41 0 00-4.1-7.39z" opacity=".5"/><path d="M98.22 302.06a4.7 4.7 0 01-2.06-3.72V197.65c0-1.4.92-2 2.06-1.34a4.7 4.7 0 012 3.71v100.69c.05 1.4-.87 2-2 1.35zM85.89 186.84c-2.27-1.31-4.11-.12-4.11 2.68V289.9a9.4 9.4 0 004.11 7.44c2.27 1.32 4.11.12 4.11-2.67V194.28a9.43 9.43 0 00-4.11-7.44z" fill="currentColor"/><path d="M85.89 186.84c-2.27-1.31-4.11-.12-4.11 2.68V289.9a9.4 9.4 0 004.11 7.44c2.27 1.32 4.11.12 4.11-2.67V194.28a9.43 9.43 0 00-4.11-7.44z" opacity=".5"/><path d="M85.89 294.94a4.7 4.7 0 01-2-3.71V190.54c0-1.4.92-2 2-1.35a4.7 4.7 0 012.11 3.72V293.6c-.06 1.4-1 2-2.11 1.34z" fill="#37474f"/><path d="M85.89 294.94a4.7 4.7 0 01-2-3.71v-73.12l4.11 2.38v73.11c-.06 1.4-1 2-2.11 1.34zM73.57 179.73c-2.27-1.32-4.11-.12-4.11 2.67v100.39a9.43 9.43 0 004.11 7.44c2.26 1.31 4.1.11 4.1-2.68V187.16a9.39 9.39 0 00-4.1-7.43z" fill="currentColor"/><path d="M73.57 179.73c-2.27-1.32-4.11-.12-4.11 2.67v100.39a9.43 9.43 0 004.11 7.44c2.26 1.31 4.1.11 4.1-2.68V187.16a9.39 9.39 0 00-4.1-7.43z" opacity=".5"/><path d="M73.57 287.83a4.7 4.7 0 01-2.06-3.72V183.42c0-1.4.92-2 2.06-1.34a4.7 4.7 0 012.05 3.71v100.69c0 1.4-.92 2-2.05 1.35z" fill="#37474f"/><path d="M73.57 287.83a4.7 4.7 0 01-2.06-3.72v-18.36l4.11 2.37v18.36c0 1.4-.92 2-2.05 1.35zM61.24 172.61c-2.27-1.32-4.11-.12-4.11 2.68v100.38a9.4 9.4 0 004.11 7.44c2.27 1.32 4.11.12 4.11-2.67V180.05a9.43 9.43 0 00-4.11-7.44z" fill="currentColor"/><path d="M61.24 172.61c-2.27-1.32-4.11-.12-4.11 2.68v100.38a9.4 9.4 0 004.11 7.44c2.27 1.32 4.11.12 4.11-2.67V180.05a9.43 9.43 0 00-4.11-7.44z" opacity=".5"/><path d="M61.24 280.71a4.7 4.7 0 01-2.05-3.71V176.3c0-1.39.92-2 2.05-1.34a4.7 4.7 0 012.06 3.72v100.69c0 1.39-.92 2-2.06 1.34z" fill="#37474f"/><path d="M61.24 280.71a4.7 4.7 0 01-2.05-3.71v-53.95l4.11 2.38v53.94c0 1.39-.92 2-2.06 1.34z" fill="currentColor"/><path d="M185.94 169.32l-54.38 31.39a4.82 4.82 0 00-2.18 3.79V267c0 1.39 1 2 2.19 1.26l54.37-31.39a4.87 4.87 0 002.19-3.79v-62.5c0-1.4-.98-1.96-2.19-1.26z" fill="#37474f"/><path d="M130 202.43a4.29 4.29 0 00-.59 2.07V267c0 1.39 1 2 2.19 1.26l54.37-31.39a4.58 4.58 0 001.59-1.72z" fill="#263238"/><path d="M131.56 200.71a4.82 4.82 0 00-2.18 3.79V267l54.37-31.39a4.87 4.87 0 002.18-3.8v-62.49z" fill="#455a64"/><g fill="currentColor"><path d="M149.47 217.14a1.1 1.1 0 01-.78-.31l-7.23-7a1.12 1.12 0 111.56-1.62l7.23 6.95a1.13 1.13 0 01-.78 1.94z"/><path d="M142.24 221.31a1.05 1.05 0 01-.48-.11 1.13 1.13 0 01-.54-1.5l7.23-15.3a1.12 1.12 0 011.5-.53 1.13 1.13 0 01.54 1.5l-7.23 15.3a1.13 1.13 0 01-1.02.64zM173.07 203.51a1.12 1.12 0 01-.78-.31l-7.23-6.95a1.13 1.13 0 011.57-1.63l7.22 6.95a1.14 1.14 0 010 1.6 1.1 1.1 0 01-.78.34z"/><path d="M165.84 207.69a1.2 1.2 0 01-.48-.11 1.14 1.14 0 01-.54-1.5l7.23-15.3a1.13 1.13 0 012 1L166.86 207a1.13 1.13 0 01-1.02.69zM141.92 238.65a1.14 1.14 0 01-1-.57 1.13 1.13 0 01.42-1.54l31.47-18.17a1.13 1.13 0 011.54.41 1.14 1.14 0 01-.42 1.55l-31.47 18.16a1 1 0 01-.54.16z"/><path d="M163.85 239.16a3.11 3.11 0 01-1.58-.42 4.17 4.17 0 01-1.81-3.79v-8.79a1.14 1.14 0 011.13-1.13 1.14 1.14 0 011.13 1.13V235a2.07 2.07 0 00.68 1.83 1.65 1.65 0 001.56-.24 7.85 7.85 0 003.37-6.14v-8.78a1.13 1.13 0 112.25 0v8.78a10.09 10.09 0 01-4.49 8.09 4.46 4.46 0 01-2.24.62z"/><path d="M165.52 233.5a1.13 1.13 0 01-1.13-1.13v-8.48a1.13 1.13 0 012.26 0v8.48a1.12 1.12 0 01-1.13 1.13z"/></g><path d="M66.16 363.4a2.11 2.11 0 011 1.67c0 .62-.43.87-1 .56a2.16 2.16 0 01-1-1.67c.03-.62.47-.87 1-.56zM74.24 368.07a2.11 2.11 0 011 1.67c0 .61-.43.86-1 .56a2.13 2.13 0 01-1-1.68c.03-.62.46-.86 1-.55zM82.17 372.65a2.1 2.1 0 011 1.66c0 .62-.43.87-1 .56a2.13 2.13 0 01-1-1.67c.04-.61.47-.86 1-.55zM90.18 377.27a2.13 2.13 0 011 1.66c0 .61-.43.86-1 .56a2.13 2.13 0 01-1-1.66c.04-.62.47-.83 1-.56zM56.74 315.92l8.26 4.71a5.17 5.17 0 012.32 4v4.71c0 1.48-1 2.09-2.32 1.34L56.74 326a5.16 5.16 0 01-2.33-4v-4.71c0-1.51 1.05-2.11 2.33-1.37z" fill="#37474f"/><path d="M59 321.23l1.07-.62a1.41 1.41 0 011.42.14 4.48 4.48 0 012 3.51 1.4 1.4 0 01-.59 1.3l-1 .57-.18-.3a1.81 1.81 0 01-.58-.22 4.46 4.46 0 01-2-3.5 2.06 2.06 0 01.06-.52z" fill="currentColor"/><path d="M59 321.23l1.07-.62a1.41 1.41 0 011.42.14 4.48 4.48 0 012 3.51 1.4 1.4 0 01-.59 1.3l-1 .57-.18-.3a1.81 1.81 0 01-.58-.22 4.46 4.46 0 01-2-3.5 2.06 2.06 0 01.06-.52z" opacity=".2"/><path d="M60.42 321.34a4.49 4.49 0 012 3.51c0 1.28-.91 1.81-2 1.16a4.47 4.47 0 01-2-3.5c-.02-1.29.89-1.81 2-1.17z" fill="currentColor"/><path d="M56.74 293.25L65 298a5.14 5.14 0 012.32 4v4.7c0 1.49-1 2.09-2.32 1.35l-8.21-4.72a5.15 5.15 0 01-2.33-4v-4.71c-.05-1.51 1-2.11 2.28-1.37z" fill="#455a64"/><path d="M59 298.56l1.07-.62a1.42 1.42 0 011.42.15 4.45 4.45 0 012 3.5 1.43 1.43 0 01-.59 1.31l-1 .57-.18-.3a2.12 2.12 0 01-.58-.23 4.45 4.45 0 01-2-3.5 2.12 2.12 0 01.06-.52z" fill="currentColor"/><path d="M59 298.56l1.07-.62a1.42 1.42 0 011.42.15 4.45 4.45 0 012 3.5 1.43 1.43 0 01-.59 1.31l-1 .57-.18-.3a2.12 2.12 0 01-.58-.23 4.45 4.45 0 01-2-3.5 2.12 2.12 0 01.06-.52z" opacity=".2"/><path d="M60.42 298.68a4.46 4.46 0 012 3.5c0 1.29-.91 1.81-2 1.17a4.51 4.51 0 01-2-3.51c-.02-1.29.89-1.84 2-1.16z" fill="currentColor"/><path d="M185.57 276.07l-38 21.95a5.66 5.66 0 00-2.55 4.43v5.17c0 1.62 1.14 2.28 2.55 1.47l38-21.95a5.67 5.67 0 002.56-4.43v-5.16c0-1.63-1.13-2.29-2.56-1.48z" fill="#455a64"/><path d="M185.43 280.45V283a1.13 1.13 0 01-.51.89l-.57.33c-.29.16-.52 0-.52-.29v-2.51a1.13 1.13 0 01.52-.89l.57-.33c.28-.2.51-.08.51.25zM182.85 281.94v2.5a1.13 1.13 0 01-.51.89l-.58.33c-.28.16-.51 0-.51-.29v-2.51a1.15 1.15 0 01.51-.89l.58-.33c.28-.16.51-.03.51.3zM180.27 283.42v2.51a1.15 1.15 0 01-.51.89l-.58.33c-.28.16-.51 0-.51-.3v-2.5a1.13 1.13 0 01.51-.89l.58-.33c.24-.13.51-.03.51.29zM177.69 284.91v2.51a1.11 1.11 0 01-.51.88l-.58.34c-.28.16-.51 0-.51-.3v-2.51a1.13 1.13 0 01.51-.88l.58-.33c.28-.17.51-.03.51.29z" fill="currentColor"/><path d="M175.1 286.4v2.51a1.1 1.1 0 01-.51.88l-.57.33c-.28.17-.51 0-.51-.29v-2.51a1.13 1.13 0 01.51-.88l.57-.33c.29-.17.51-.03.51.29zM172.52 287.89v2.51a1.13 1.13 0 01-.51.88l-.57.33c-.29.17-.51 0-.51-.29v-2.51a1.1 1.1 0 01.51-.88l.57-.34c.28-.16.51-.03.51.3z" fill="#37474f"/><path d="M169.94 289.38v2.5a1.13 1.13 0 01-.51.89l-.58.33c-.28.16-.51 0-.51-.29v-2.51a1.15 1.15 0 01.51-.89l.58-.33c.28-.16.51-.03.51.3zM167.36 290.87v2.5a1.13 1.13 0 01-.51.89l-.58.33c-.28.16-.51 0-.51-.3v-2.5a1.15 1.15 0 01.51-.89l.58-.33c.28-.16.51-.03.51.3zM164.78 292.35v2.51a1.15 1.15 0 01-.51.89l-.58.33c-.28.16-.51 0-.51-.3v-2.5a1.13 1.13 0 01.51-.89l.58-.33c.28-.16.51-.06.51.29z" fill="currentColor"/><path d="M162.2 293.84v2.51a1.1 1.1 0 01-.52.88l-.57.34c-.28.16-.51 0-.51-.3v-2.51a1.13 1.13 0 01.51-.88l.57-.33c.32-.17.52-.03.52.29z" fill="#37474f"/><path d="M159.61 295.33v2.51a1.13 1.13 0 01-.51.88l-.57.33c-.28.17-.51 0-.51-.29v-2.51a1.13 1.13 0 01.51-.88l.57-.33c.28-.17.51-.04.51.29zM157 296.82v2.51a1.13 1.13 0 01-.51.88l-.57.33c-.29.17-.51 0-.51-.29v-2.51a1.1 1.1 0 01.51-.88l.57-.34c.31-.16.51-.03.51.3z" fill="currentColor"/><path d="M154.45 298.31v2.5a1.13 1.13 0 01-.51.89l-.58.33c-.28.16-.51 0-.51-.29v-2.51a1.15 1.15 0 01.51-.89l.58-.33c.28-.16.51-.01.51.3z" fill="#37474f"/><path d="M151.87 299.8v2.5a1.13 1.13 0 01-.51.89l-.58.33c-.28.16-.51 0-.51-.3v-2.5a1.15 1.15 0 01.51-.89l.58-.33c.28-.16.51-.03.51.3z" fill="currentColor"/><path d="M149.29 301.28v2.51a1.15 1.15 0 01-.51.89l-.58.33c-.28.16-.51 0-.51-.3v-2.5a1.13 1.13 0 01.51-.89l.58-.33c.28-.16.51.01.51.29z" fill="#37474f"/><path d="M141.15 306.31l-1.06-.62a1.41 1.41 0 00-1.42.14 4.49 4.49 0 00-2 3.51 1.42 1.42 0 00.59 1.3l1 .56.18-.3a2 2 0 00.58-.22 4.49 4.49 0 002-3.5 2.66 2.66 0 00-.06-.52z" fill="#455a64"/><path d="M139.68 306.42a4.51 4.51 0 00-2 3.51c0 1.29.91 1.81 2 1.16a4.45 4.45 0 002-3.5c.02-1.29-.88-1.81-2-1.17z" fill="currentColor"/><path d="M139.68 306.42a4.51 4.51 0 00-2 3.51c0 1.29.91 1.81 2 1.16a4.45 4.45 0 002-3.5c.02-1.29-.88-1.81-2-1.17z" fill="#fff" opacity=".8"/><path d="M132.38 311.37l-1.07-.61a1.44 1.44 0 00-1.42.15 4.45 4.45 0 00-2 3.5 1.41 1.41 0 00.59 1.31l1 .57.18-.3a1.94 1.94 0 00.58-.23 4.45 4.45 0 002-3.5 2 2 0 00-.06-.51z" fill="#455a64"/><path d="M130.9 311.49a4.47 4.47 0 00-2 3.5c0 1.29.91 1.81 2 1.17a4.47 4.47 0 002-3.5c.03-1.29-.9-1.82-2-1.17z" fill="currentColor"/><path d="M130.9 311.49a4.47 4.47 0 00-2 3.5c0 1.29.91 1.81 2 1.17a4.47 4.47 0 002-3.5c.03-1.29-.9-1.82-2-1.17z" fill="#fff" opacity=".8"/><path d="M73.87 301.3l39.79 23a2.85 2.85 0 011.29 2.23v11c0 .82-.58 1.15-1.29.74l-39.79-23a2.83 2.83 0 01-1.29-2.27v-11c0-.78.57-1.11 1.29-.7z" fill="#455a64"/><path d="M75 306.14a2.74 2.74 0 001.29 2.14l34.95 20.17c.71.41 1.29.11 1.29-.66a2.77 2.77 0 00-1.29-2.14l-34.95-20.16c-.72-.41-1.29-.12-1.29.65z" fill="currentColor"/><path d="M75 311.73a2.74 2.74 0 001.29 2.14L111.24 334c.71.41 1.29.12 1.29-.65a2.77 2.77 0 00-1.29-2.14l-34.95-20.13c-.72-.41-1.29-.08-1.29.65z" fill="#263238"/><path d="M76.29 311.08l19.31 11.13V325l-19.31-11.13a2.74 2.74 0 01-1.29-2.14c0-.73.57-1.06 1.29-.65z" fill="currentColor"/><path d="M56.74 293.25L65 298a5.14 5.14 0 012.32 4v4.7c0 1.49-1 2.09-2.32 1.35l-8.21-4.72a5.15 5.15 0 01-2.33-4v-4.71c-.05-1.51 1-2.11 2.28-1.37z" fill="#37474f"/><path d="M59 298.56l1.07-.62a1.42 1.42 0 011.42.15 4.45 4.45 0 012 3.5 1.43 1.43 0 01-.59 1.31l-1 .57-.18-.3a2.12 2.12 0 01-.58-.23 4.45 4.45 0 01-2-3.5 2.12 2.12 0 01.06-.52z" fill="currentColor"/><path d="M59 298.56l1.07-.62a1.42 1.42 0 011.42.15 4.45 4.45 0 012 3.5 1.43 1.43 0 01-.59 1.31l-1 .57-.18-.3a2.12 2.12 0 01-.58-.23 4.45 4.45 0 01-2-3.5 2.12 2.12 0 01.06-.52z" opacity=".2"/><path d="M60.42 298.68a4.46 4.46 0 012 3.5c0 1.29-.91 1.81-2 1.17a4.51 4.51 0 01-2-3.51c-.02-1.29.89-1.84 2-1.16z" fill="currentColor"/><path d="M73.87 301.3l39.79 23a2.85 2.85 0 011.29 2.23v11c0 .82-.58 1.15-1.29.74l-39.79-23a2.83 2.83 0 01-1.29-2.27v-11c0-.78.57-1.11 1.29-.7z" fill="#37474f"/><path d="M75 306.14a2.74 2.74 0 001.29 2.14l34.95 20.17c.71.41 1.29.11 1.29-.66a2.77 2.77 0 00-1.29-2.14l-34.95-20.16c-.72-.41-1.29-.12-1.29.65z" fill="currentColor"/><path d="M75 311.73a2.74 2.74 0 001.29 2.14L111.24 334c.71.41 1.29.12 1.29-.65a2.77 2.77 0 00-1.29-2.14l-34.95-20.13c-.72-.41-1.29-.08-1.29.65z" fill="#263238"/><path d="M76.29 311.08l19.31 11.13V325l-19.31-11.13a2.74 2.74 0 01-1.29-2.14c0-.73.57-1.06 1.29-.65z" fill="currentColor"/><path d="M73.87 324l39.79 23a2.89 2.89 0 011.29 2.24v11c0 .82-.58 1.15-1.29.74L73.87 338a2.84 2.84 0 01-1.29-2.24v-11c0-.76.57-1.14 1.29-.76z" fill="#37474f"/><path d="M75 328.87a2.74 2.74 0 001.29 2.13l34.95 20.16c.71.42 1.29.12 1.29-.65a2.77 2.77 0 00-1.29-2.14l-34.95-20.15c-.72-.41-1.29-.12-1.29.65z" fill="currentColor"/><path d="M75 334.46a2.74 2.74 0 001.29 2.14l34.95 20.16c.71.41 1.29.12 1.29-.65a2.77 2.77 0 00-1.29-2.14l-34.95-20.16c-.72-.41-1.29-.12-1.29.65z" fill="#263238"/><path d="M76.29 333.81l24.15 13.92v2.79L76.29 336.6a2.74 2.74 0 01-1.29-2.14c0-.77.57-1.06 1.29-.65z" fill="currentColor"/><path d="M56.74 338.72l8.26 4.71a5.15 5.15 0 012.32 4v4.71c0 1.48-1 2.08-2.32 1.34l-8.21-4.71a5.18 5.18 0 01-2.33-4v-4.71c-.05-1.48 1-2.06 2.28-1.34z" fill="#455a64"/><path d="M59 344l1.07-.62a1.41 1.41 0 011.42.14 4.46 4.46 0 012 3.5 1.41 1.41 0 01-.59 1.31l-1 .57-.18-.3a2.08 2.08 0 01-.58-.22 4.48 4.48 0 01-2-3.51 2 2 0 01.06-.51z" fill="currentColor"/><path d="M59 344l1.07-.62a1.41 1.41 0 011.42.14 4.46 4.46 0 012 3.5 1.41 1.41 0 01-.59 1.31l-1 .57-.18-.3a2.08 2.08 0 01-.58-.22 4.48 4.48 0 01-2-3.51 2 2 0 01.06-.51z" opacity=".2"/><path d="M60.42 344.14a4.46 4.46 0 012 3.5c0 1.29-.91 1.81-2 1.17a4.47 4.47 0 01-2-3.5c-.02-1.31.89-1.81 2-1.17z" fill="currentColor"/><path d="M56.74 338.72l8.26 4.71a5.15 5.15 0 012.32 4v4.71c0 1.48-1 2.08-2.32 1.34l-8.21-4.71a5.18 5.18 0 01-2.33-4v-4.71c-.05-1.48 1-2.06 2.28-1.34z" fill="#37474f"/><path d="M59 344l1.07-.62a1.41 1.41 0 011.42.14 4.46 4.46 0 012 3.5 1.41 1.41 0 01-.59 1.31l-1 .57-.18-.3a2.08 2.08 0 01-.58-.22 4.48 4.48 0 01-2-3.51 2 2 0 01.06-.51z" fill="currentColor"/><path d="M59 344l1.07-.62a1.41 1.41 0 011.42.14 4.46 4.46 0 012 3.5 1.41 1.41 0 01-.59 1.31l-1 .57-.18-.3a2.08 2.08 0 01-.58-.22 4.48 4.48 0 01-2-3.51 2 2 0 01.06-.51z" opacity=".2"/><path d="M60.42 344.14a4.46 4.46 0 012 3.5c0 1.29-.91 1.81-2 1.17a4.47 4.47 0 01-2-3.5c-.02-1.31.89-1.81 2-1.17z" fill="currentColor"/><path d="M73.87 346.77l39.79 23A2.85 2.85 0 01115 372v11c0 .83-.58 1.16-1.29.75l-39.79-23a2.83 2.83 0 01-1.29-2.23v-11c-.05-.83.52-1.17 1.24-.75z" fill="#37474f"/><path d="M75 351.61a2.74 2.74 0 001.29 2.14l34.95 20.16c.71.41 1.29.12 1.29-.65a2.77 2.77 0 00-1.29-2.14L76.29 351c-.72-.46-1.29-.16-1.29.61z" fill="currentColor"/><path d="M75 357.19a2.76 2.76 0 001.29 2.15l34.95 20.16c.71.41 1.29.12 1.29-.65a2.79 2.79 0 00-1.29-2.15l-34.95-20.16c-.72-.41-1.29-.12-1.29.65z" fill="#263238"/><path d="M76.29 356.54l19.31 11.13v2.8l-19.31-11.13a2.76 2.76 0 01-1.29-2.15c0-.77.57-1.06 1.29-.65z" fill="currentColor"/><path d="M177.29 158.41l-53.07 30.64a3.07 3.07 0 01-2.74 0l-53.07-30.64a.84.84 0 010-1.59l53.07-29.58a3.07 3.07 0 012.74 0l53.07 29.58a.84.84 0 010 1.59z" opacity=".1"/><path d="M124.16 187.37l50.78-29.32a2.9 2.9 0 001.32-2.27v-1.71a2.91 2.91 0 00-1.32-2.28l-50.78-29.32a2.88 2.88 0 00-2.62 0l-50.78 29.32a2.91 2.91 0 00-1.32 2.28v1.71a2.9 2.9 0 001.32 2.27l50.78 29.32a2.88 2.88 0 002.62 0z" fill="currentColor"/><path d="M124.16 187.37l50.78-29.32a2.9 2.9 0 001.32-2.27v-1.71a2.91 2.91 0 00-1.32-2.28l-50.78-29.32a2.88 2.88 0 00-2.62 0l-50.78 29.32a2.91 2.91 0 00-1.32 2.28v1.71a2.9 2.9 0 001.32 2.27l50.78 29.32a2.88 2.88 0 002.62 0z" fill="#fff" opacity=".5"/><path d="M124.16 187.37l50.78-29.32a2.9 2.9 0 001.32-2.27v-1.71a2.91 2.91 0 00-1.32-2.28l-50.78-29.32a2.88 2.88 0 00-2.62 0l-50.78 29.32a2.91 2.91 0 00-1.32 2.28v1.71a2.9 2.9 0 001.32 2.27l50.78 29.32a2.88 2.88 0 002.62 0z" opacity=".05"/><path d="M174.94 153.31l-50.78 29.31a2.88 2.88 0 01-2.62 0l-50.78-29.31a.8.8 0 010-1.52l50.78-28.3a2.88 2.88 0 012.62 0l50.78 28.3a.8.8 0 010 1.52z" fill="currentColor"/><path d="M174.94 153.31l-50.78 29.31a2.88 2.88 0 01-2.62 0l-50.78-29.31a.8.8 0 010-1.52l50.78-28.3a2.88 2.88 0 012.62 0l50.78 28.3a.8.8 0 010 1.52z" fill="#fff" opacity=".7"/><path d="M122.85 182.94v4.74a2.67 2.67 0 001.31-.31l50.78-29.32a2.91 2.91 0 001.32-2.28v-1.71a2.92 2.92 0 00-1.07-2.1c.47.41.39 1-.25 1.34l-50.78 29.32a2.57 2.57 0 01-1.31.32z" fill="currentColor"/><path d="M122.85 182.94v4.74a2.67 2.67 0 001.31-.31l50.78-29.32a2.91 2.91 0 001.32-2.28v-1.71a2.92 2.92 0 00-1.07-2.1c.47.41.39 1-.25 1.34l-50.78 29.32a2.57 2.57 0 01-1.31.32z" fill="#fff" opacity=".6"/><path d="M124.16 173.14l34.35-19.83a2.89 2.89 0 001.31-2.28v-1.71a2.88 2.88 0 00-1.31-2.27l-22-12.72a8.71 8.71 0 01-2.22-2l-10.52-14.17a1.74 1.74 0 00-2.21-.46l-34.37 19.86a2.89 2.89 0 00-1.31 2.27V151a2.92 2.92 0 001.31 2.28l34.35 19.83a2.88 2.88 0 002.62.03z" fill="#263238"/><path d="M121.54 168.39l-22-12.71a7.19 7.19 0 01-1.15-.85 7.87 7.87 0 01-1.07-1.13l-10.54-14.17a1.31 1.31 0 01.41-2l34.35-19.83a1.74 1.74 0 012.21.46l10.52 14.17a8.28 8.28 0 002 2l22.2 12.64a.79.79 0 010 1.51l-34.35 19.83a2.88 2.88 0 01-2.58.08z" fill="#455a64"/><path d="M87.19 137.56l34.35-19.83a1.74 1.74 0 012.21.46l10.52 14.17a8 8 0 001.08 1.12s-37 21.35-37 21.34a7.84 7.84 0 01-1.07-1.12l-10.5-14.17a1.31 1.31 0 01.41-1.97z" fill="#37474f"/><path d="M150.18 147.39a3.41 3.41 0 00-3.08 0c-.86.5-.86 1.29 0 1.78a3.41 3.41 0 003.08 0 .94.94 0 000-1.78zM144 151a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78z" fill="currentColor"/><path d="M137.85 154.51a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78zM131.69 158.07a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78z" fill="#263238"/><path d="M145.56 144.73a3.37 3.37 0 00-3.09 0 .93.93 0 000 1.77 3.37 3.37 0 003.09 0 .93.93 0 000-1.77z" fill="currentColor"/><path d="M139.39 148.28a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78z" fill="#263238"/><ellipse cx="131.69" cy="152.73" rx="2.18" ry="1.26" fill="currentColor"/><path d="M127.07 155.4a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78zM140.93 142.06a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78z" fill="#263238"/><path d="M134.77 145.62a3.35 3.35 0 00-3.08 0 .93.93 0 000 1.77 3.35 3.35 0 003.08 0 .93.93 0 000-1.77z" fill="currentColor"/><path d="M128.61 149.17a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78z" fill="#263238"/><path d="M125.49 161.55a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78z" fill="currentColor"/><ellipse cx="119.33" cy="159.77" rx="2.18" ry="1.26" fill="#263238"/><path d="M122.45 152.73a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78z" fill="#263238"/><path d="M136.31 139.39a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78z" fill="currentColor"/><path d="M130.15 143a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78z" fill="#263238"/><path d="M124 146.51a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78z" fill="currentColor"/><path d="M117.83 150.06a3.41 3.41 0 00-3.08 0c-.86.5-.86 1.29 0 1.78a3.41 3.41 0 003.08 0 .94.94 0 000-1.78z" fill="#263238"/><path d="M116.25 156.22a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78z" fill="currentColor"/><path d="M111.63 153.55a3.41 3.41 0 00-3.08 0 .94.94 0 000 1.78 3.41 3.41 0 003.08 0 .94.94 0 000-1.78z" fill="#263238"/><path d="M122.78 168.71v4.73a2.72 2.72 0 001.38-.3l34.35-19.84a2.86 2.86 0 001.31-2.27v-1.71a2.89 2.89 0 00-1.07-2.1c.48.41.4 1-.24 1.34l-34.35 19.83a2.63 2.63 0 01-1.38.32z" fill="#37474f"/><path d="M129.73 131.76a1.17 1.17 0 00-1.48-.31l-29.92 17.22a.89.89 0 00-.27 1.32 1.18 1.18 0 001.48.31l29.88-17.26a.88.88 0 00.31-1.28z" fill="#263238"/><path d="M129.73 131.76a1.17 1.17 0 00-1.48-.31L106 144.24l1.24 1.67 22.21-12.83a.88.88 0 00.28-1.32z" fill="currentColor"/><path d="M127.26 128.45a1.16 1.16 0 00-1.48-.31l-29.92 17.22a.87.87 0 00-.27 1.31 1.16 1.16 0 001.48.31L127 129.77a.88.88 0 00.26-1.32z" fill="#263238"/><path d="M127.26 128.45a1.16 1.16 0 00-1.48-.31l-7.43 4.29 1.24 1.66 7.42-4.28a.88.88 0 00.25-1.36z" fill="currentColor"/><path d="M124.79 125.13a1.16 1.16 0 00-1.48-.31L93.39 142a.89.89 0 00-.27 1.32 1.16 1.16 0 001.48.3l29.88-17.25a.88.88 0 00.31-1.24z" fill="#263238"/><path d="M124.79 125.13a1.16 1.16 0 00-1.48-.31l-15.57 9 1.26 1.58 15.56-9a.88.88 0 00.23-1.27z" fill="currentColor"/><path d="M122.32 121.81a1.16 1.16 0 00-1.48-.3l-29.92 17.21a.88.88 0 00-.27 1.32 1.16 1.16 0 001.48.31L122 123.13a.88.88 0 00.32-1.32z" fill="#263238"/><path d="M122.32 121.81a1.16 1.16 0 00-1.48-.3l-27.16 15.62 1.24 1.66L122 123.13a.88.88 0 00.32-1.32z" fill="currentColor"/><g><path d="M132.4 412.71v7.54a.56.56 0 00.51.55l3.93.24v5.54a.33.33 0 00.19.28l4.15 2.39a1 1 0 00.52.11.83.83 0 00.39-.08l4.2-1.94a1.58 1.58 0 011-.06 24.6 24.6 0 003.26.78.78.78 0 01.61.59l.52 2.76a.69.69 0 00.23.37.62.62 0 00.39.14h6.09a.55.55 0 00.34-.13.19.19 0 00.08-.07.73.73 0 00.09-.08v-.06c0-.06.09-.1.1-.17l.53-2.76a.77.77 0 01.6-.59 24.23 24.23 0 003.26-.78 1.58 1.58 0 011 .06l4.2 1.94a.71.71 0 00.26 0h.15a1 1 0 00.4-.1l4.14-2.39a.41.41 0 00.15-.14.35.35 0 000-.11V421l3.86-.25h.07a.93.93 0 00.1-.05.53.53 0 00.26-.13.41.41 0 00.08-.19c0-.06.08-.11.08-.18v-10.11a.56.56 0 00-.52-.55l-3.93-.25v-5.53a.34.34 0 00-.19-.3l-4.14-2.39a1.14 1.14 0 00-.92 0l-4.2 1.94a1.5 1.5 0 01-1 .06 24.21 24.21 0 00-3.26-.77.79.79 0 01-.6-.59L159 399a.65.65 0 00-.61-.51h-6.09a.66.66 0 00-.62.51l-.52 2.77a.8.8 0 01-.61.59 24.58 24.58 0 00-3.26.77 1.51 1.51 0 01-1-.06l-4.2-1.94a1.11 1.11 0 00-.91 0l-4.14 2.39a.35.35 0 00-.2.3v5.52l-3.93.25a.56.56 0 00-.51.55v2.46a.41.41 0 000 .11zm15.91 2a15.48 15.48 0 0114 0q.44.25.81.51a8.14 8.14 0 01-.81.54 15.46 15.46 0 01-14 0 7.58 7.58 0 01-.81-.53 8.52 8.52 0 01.81-.56z" fill="currentColor"/><path d="M132.39 412.71v7.54a.55.55 0 00.51.54l3.93.25v5.54a.33.33 0 00.19.28l4.15 2.39a1 1 0 00.52.11.83.83 0 00.39-.08l4.2-1.94a1.46 1.46 0 01.4-.1 1.47 1.47 0 01.56 0 24.6 24.6 0 003.26.78.64.64 0 01.26.1.79.79 0 01.35.46l.52 2.76a.63.63 0 00.23.36.62.62 0 00.39.14h6.2a.69.69 0 00.4-.27v-.06c0-.06.09-.1.1-.17l.53-2.76a.7.7 0 01.24-.4.77.77 0 01.36-.18 24.23 24.23 0 003.26-.78 1.69 1.69 0 01.64 0 1.27 1.27 0 01.32.07l4.2 1.94a.71.71 0 00.26 0 1.07 1.07 0 00.65-.1l4.14-2.39.09-.06.07-.09a.27.27 0 000-.12v-5.54l.72-.05 3.14-.2h.07a.55.55 0 00.52-.54v-7a.55.55 0 01-.52.54l-5.52.35a1 1 0 00-.74.5 8.28 8.28 0 01-1.18 1.63.43.43 0 00.07.67l3.28 2.36a.31.31 0 010 .56l-4.14 2.39a1.1 1.1 0 01-.92 0l-4.2-1.94a1.57 1.57 0 00-1-.06 23.87 23.87 0 01-3.26.78.79.79 0 00-.6.59l-.53 2.76a.65.65 0 01-.61.51h-6.09a.66.66 0 01-.62-.51l-.52-2.76a.81.81 0 00-.61-.59 24.6 24.6 0 01-3.26-.78 1.58 1.58 0 00-1 .06l-4.2 1.94a1.07 1.07 0 01-.91 0l-4.08-2.31a.31.31 0 010-.56l3.28-2.36a.45.45 0 00.06-.68 7.89 7.89 0 01-1.17-1.62 1 1 0 00-.75-.5l-5.52-.34a.56.56 0 01-.51-.55v-.62a.43.43 0 000 .11zM136.85 403.82v5.52l1.59-.1a1 1 0 00.75-.5 8.17 8.17 0 011.17-1.62.45.45 0 00-.06-.68l-3.3-2.36a.37.37 0 01-.17-.26z" opacity=".3"/><path d="M147.51 415.19l.35-.25.41-.25a15.48 15.48 0 0114 0l.37.22c.15.1.3.19.44.3 3-2.25 2.76-5.49-.81-7.55a15.4 15.4 0 00-14 0c-3.53 2.04-3.8 5.34-.76 7.53z" opacity=".4"/><path d="M170.26 407.12a8.23 8.23 0 011.18 1.62 1 1 0 00.74.5l1.59.1v-5.53a.39.39 0 01-.17.27l-3.28 2.36a.44.44 0 00-.06.68z" opacity=".3"/><path d="M168.52 401.09l-4.2 1.94a1.5 1.5 0 01-1 .06 24.21 24.21 0 00-3.26-.77.79.79 0 01-.6-.59L159 399a.65.65 0 00-.61-.51h-6.09a.66.66 0 00-.62.51l-.52 2.77a.8.8 0 01-.61.59 24.58 24.58 0 00-3.26.77 1.51 1.51 0 01-1-.06l-4.2-1.94a1.11 1.11 0 00-.91 0l-4.14 2.39a.31.31 0 000 .56l3.28 2.36a.45.45 0 01.06.68 8.17 8.17 0 00-1.17 1.62 1 1 0 01-.75.5l-5.52.35a.56.56 0 00-.51.55v3.08a.56.56 0 00.51.55l5.52.34a1 1 0 01.75.5 8.22 8.22 0 001.17 1.63.44.44 0 01-.06.67l-3.32 2.36a.31.31 0 000 .56l4.14 2.39a1.07 1.07 0 00.91 0l4.2-1.94a1.58 1.58 0 011-.06 24.6 24.6 0 003.26.78.78.78 0 01.61.59l.52 2.76a.66.66 0 00.62.51h6.09a.65.65 0 00.61-.51l.53-2.76a.77.77 0 01.6-.59 24.23 24.23 0 003.26-.78 1.57 1.57 0 011 .06l4.2 1.94a1.1 1.1 0 00.92 0l4.14-2.39a.31.31 0 000-.56l-3.28-2.36a.42.42 0 01-.06-.67 8.22 8.22 0 001.17-1.63 1 1 0 01.74-.5l5.52-.34a.56.56 0 00.52-.55v-3.08a.56.56 0 00-.52-.55l-5.52-.35a1 1 0 01-.74-.5 8.17 8.17 0 00-1.17-1.62.43.43 0 01.06-.68l3.28-2.36a.31.31 0 000-.56l-4.14-2.39a1.14 1.14 0 00-.95-.04zm-6.21 6.55c3.87 2.23 3.87 5.85 0 8.08a15.46 15.46 0 01-14 0c-3.87-2.23-3.87-5.85 0-8.08a15.46 15.46 0 0114 0z" fill="currentColor"/><path d="M173.77 419.55a.35.35 0 00-.16-.28l-1.61-1.19v-3.91a.9.9 0 01.22-.06l5.52-.35a.55.55 0 00.52-.54v7a.55.55 0 01-.52.54h-.07l-3.14.2-.72.05s-.03-1.36-.04-1.46z" fill="currentColor"/><path d="M173.77 419.55a.35.35 0 00-.16-.28l-1.61-1.19v-3.91a.9.9 0 01.22-.06l5.52-.35a.55.55 0 00.52-.54v7a.55.55 0 01-.52.54h-.07l-3.14.2-.72.05s-.03-1.36-.04-1.46z" opacity=".2"/><path d="M164 420.23a1.26 1.26 0 01.31.09l4.2 1.94a1.06 1.06 0 00.54.07v7a.88.88 0 01-.28 0 .71.71 0 01-.26 0l-4.2-1.94a1.29 1.29 0 00-.31-.07z" fill="currentColor"/><path d="M164 420.23a1.26 1.26 0 01.31.09l4.2 1.94a1.06 1.06 0 00.54.07v7a.88.88 0 01-.28 0 .71.71 0 01-.26 0l-4.2-1.94a1.29 1.29 0 00-.31-.07z" opacity=".2"/><path d="M158.89 424.59a.5.5 0 00.08-.2l.53-2.76a.49.49 0 01.08-.21v7a.54.54 0 00-.08.21l-.53 2.76c0 .06-.05.09-.08.14z" fill="currentColor"/><path d="M158.89 424.59a.5.5 0 00.08-.2l.53-2.76a.49.49 0 01.08-.21v7a.54.54 0 00-.08.21l-.53 2.76c0 .06-.05.09-.08.14z" opacity=".2"/><path d="M151.05 421.42a.66.66 0 01.08.21l.52 2.76a1.19 1.19 0 00.08.2v7a1.19 1.19 0 01-.08-.2l-.52-2.76a.64.64 0 00-.08-.19zM141.56 422.33a1.06 1.06 0 00.54-.07l4.2-1.94a1.35 1.35 0 01.32-.09v7a1 1 0 00-.32.08l-4.2 1.94a.83.83 0 01-.39.08h-.15zM132.92 413.77l5.52.34a1 1 0 01.23.06v3.91l-1.67 1.19a.36.36 0 00-.17.28V421l-3.93-.25a.55.55 0 01-.51-.54v-7.54a.43.43 0 010-.11v.62a.56.56 0 00.53.59z" opacity=".15"/><path d="M213.17 391.05v-5.1a.4.4 0 00-.09-.24l-1.55-2.16a.71.71 0 00-.63-.3l-4.06.68a1.3 1.3 0 01-.77-.22 9.1 9.1 0 00-.8-.51V380c0-.13-.12-.25-.32-.3l-4.12-1a.79.79 0 00-.66.12l-2 2.07a.88.88 0 01-.64.2 21.77 21.77 0 00-2.68 0 1.1 1.1 0 01-.73-.32l-1.76-1.77a.79.79 0 00-.68-.25l-4.28 1a.27.27 0 00-.22.28v3.21a9.23 9.23 0 00-1 .61 1.12 1.12 0 01-.7.12l-3.93-.66a.69.69 0 00-.62.18l-1.7 2.37a.15.15 0 000 .11v6.16a.29.29 0 000 .1v5a.41.41 0 00.09.26l1.55 2.16a.74.74 0 00.63.3l4-.69a1.26 1.26 0 01.78.23c.25.17.52.34.8.5v3.21c0 .14.12.26.32.31l4.11 1a.77.77 0 00.67-.13l2-2.07a.88.88 0 01.64-.2 24 24 0 002.68 0 1 1 0 01.72.31l1.77 1.85a.82.82 0 00.68.26l4.28-1a.29.29 0 00.22-.23v-3.26a11.2 11.2 0 001-.6 1 1 0 01.7-.13l3.93.67a.67.67 0 00.62-.19l1.7-2.37a.17.17 0 000-.11v-6.17zm-22.89.13c.19-.13.39-.26.61-.39a11.8 11.8 0 0110.64 0c.21.13.41.25.59.38a5.89 5.89 0 01-.64.42 11.82 11.82 0 01-8.16.92 9.18 9.18 0 01-2.41-.92c-.22-.13-.44-.27-.63-.41z" fill="currentColor"/><path d="M213.17 391.05v-5.1a.4.4 0 00-.09-.24l-1.55-2.16a.71.71 0 00-.63-.3l-4.06.68a1.3 1.3 0 01-.77-.22 9.1 9.1 0 00-.8-.51V380c0-.13-.12-.25-.32-.3l-4.12-1a.79.79 0 00-.66.12l-2 2.07a.88.88 0 01-.64.2 21.77 21.77 0 00-2.68 0 1.1 1.1 0 01-.73-.32l-1.76-1.77a.79.79 0 00-.68-.25l-4.28 1a.27.27 0 00-.22.28v3.21a9.23 9.23 0 00-1 .61 1.12 1.12 0 01-.7.12l-3.93-.66a.69.69 0 00-.62.18l-1.7 2.37a.15.15 0 000 .11v6.16a.29.29 0 000 .1v5a.41.41 0 00.09.26l1.55 2.16a.74.74 0 00.63.3l4-.69a1.26 1.26 0 01.78.23c.25.17.52.34.8.5v3.21c0 .14.12.26.32.31l4.11 1a.77.77 0 00.67-.13l2-2.07a.88.88 0 01.64-.2 24 24 0 002.68 0 1 1 0 01.72.31l1.77 1.85a.82.82 0 00.68.26l4.28-1a.29.29 0 00.22-.23v-3.26a11.2 11.2 0 001-.6 1 1 0 01.7-.13l3.93.67a.67.67 0 00.62-.19l1.7-2.37a.17.17 0 000-.11v-6.17zm-22.89.13c.19-.13.39-.26.61-.39a11.8 11.8 0 0110.64 0c.21.13.41.25.59.38a5.89 5.89 0 01-.64.42 11.82 11.82 0 01-8.16.92 9.18 9.18 0 01-2.41-.92c-.22-.13-.44-.27-.63-.41z" opacity=".3"/><path d="M179.23 386z" fill="currentColor"/><path d="M188.12 382.74c-.33.15-.64.31-.94.48V380a.28.28 0 000 .17l1 2a.38.38 0 01-.06.57z" opacity=".3"/><path d="M183.41 387.81a4 4 0 000 1.34.42.42 0 01-.27.47l-3.7 1.17a.26.26 0 00-.18.24v1.2-.09V386c0 .12.12.23.3.28l3.49 1.11a.49.49 0 01.36.42zM185.72 393v6.18h-.15l-4 .68a.47.47 0 01-.2 0v-6.18a.5.5 0 00.21 0l4.05-.68z" opacity=".15"/><path d="M187.15 400c-.28-.16-.55-.33-.8-.5a1.51 1.51 0 00-.63-.23V393a1.42 1.42 0 01.64.23 12.15 12.15 0 001.64.94c.24.12.37.33.29.49l-1.11 2.2a.3.3 0 000 .1c-.02.16-.03 3.04-.03 3.04z" opacity=".25"/><path d="M194.47 395.94v6.18a.55.55 0 00-.25.14l-2 2.07a.54.54 0 01-.36.14v-6.18a.55.55 0 00.37-.14l2-2.07a.76.76 0 01.24-.14z" opacity=".15"/><path d="M197.8 395.92v6.18a.61.61 0 00-.26 0 24 24 0 01-2.68 0 1 1 0 00-.39.06v-6.18a1.08 1.08 0 01.4-.06 21.78 21.78 0 002.68 0 .59.59 0 01.25 0z" opacity=".25"/><path d="M200.42 398.27v6.18a.86.86 0 01-.39-.23l-1.76-1.85a1.06 1.06 0 00-.47-.27v-6.18a1.16 1.16 0 01.47.27L200 398a.83.83 0 00.42.27z" opacity=".15"/><path d="M205.21 397.05v6.21a.29.29 0 01-.22.23l-4.28 1a.49.49 0 01-.29 0v-6.18a.54.54 0 00.3 0l4.28-1a.25.25 0 00.21-.26z" fill="currentColor"/><path d="M205.21 397.05v6.21a.29.29 0 01-.22.23l-4.28 1a.49.49 0 01-.29 0v-6.18a.54.54 0 00.3 0l4.28-1a.25.25 0 00.21-.26z" opacity=".2"/><path d="M206.59 393.05v6.18a.91.91 0 00-.42.13 11.2 11.2 0 01-1 .6v-3a.41.41 0 00-.05-.17l-1-2a.36.36 0 01.12-.51 11.52 11.52 0 001.9-1.09.87.87 0 01.45-.14z" opacity=".15"/><path d="M213.17 391.06v6.17a.17.17 0 010 .11l-1.7 2.37a.52.52 0 01-.29.17v-6.17a.66.66 0 00.3-.17l1.7-2.38a.21.21 0 00-.01-.1z" fill="currentColor"/><path d="M213.17 391.06v6.17a.17.17 0 010 .11l-1.7 2.37a.52.52 0 01-.29.17v-6.17a.66.66 0 00.3-.17l1.7-2.38a.21.21 0 00-.01-.1z" fill="currentColor"/><path d="M213.17 391.06v6.17a.17.17 0 010 .11l-1.7 2.37a.52.52 0 01-.29.17v-6.17a.66.66 0 00.3-.17l1.7-2.38a.21.21 0 00-.01-.1z" opacity=".2"/><path d="M187.18 380a.37.37 0 000 .16l1 2a.37.37 0 01-.13.52c-.33.15-.64.31-.94.48a9.23 9.23 0 00-1 .61 1.12 1.12 0 01-.7.12l-3.93-.66a.69.69 0 00-.62.18l-1.7 2.37a.15.15 0 000 .11c0 .11.12.21.3.27l3.49 1.11a.49.49 0 01.39.46 4 4 0 000 1.34.42.42 0 01-.27.47l-3.7 1.17a.25.25 0 00-.17.24.49.49 0 00.08.24l1.55 2.16a.76.76 0 00.64.3l4.05-.68a1.2 1.2 0 01.78.22 12.15 12.15 0 001.64.94c.24.12.37.33.29.49l-1.11 2.2c-.08.16.06.33.3.4l4.11 1a.77.77 0 00.67-.13l2-2.07a1 1 0 01.64-.2 24 24 0 002.68 0 1 1 0 01.72.31L200 398a.82.82 0 00.68.26l4.28-1a.29.29 0 00.17-.45l-1-2a.35.35 0 01.13-.51 11.84 11.84 0 001.89-1.09 1 1 0 01.71-.12l3.92.66a.69.69 0 00.63-.19l1.7-2.37a.14.14 0 000-.1v-.1a.47.47 0 00-.27-.19l-3.5-1.1c-.24-.08-.41-.29-.38-.47a4.56 4.56 0 000-1.33.43.43 0 01.27-.48l3.7-1.17a.23.23 0 00.17-.22.4.4 0 00-.09-.24l-1.55-2.16a.71.71 0 00-.63-.3l-4.06.68a1.3 1.3 0 01-.77-.22 9.1 9.1 0 00-.8-.51c-.27-.15-.55-.3-.84-.44s-.38-.33-.3-.48l1.12-2.21a.29.29 0 000-.09c0-.13-.12-.25-.32-.3l-4.12-1a.79.79 0 00-.66.12l-2 2.07a.88.88 0 01-.64.2 21.77 21.77 0 00-2.68 0 1.1 1.1 0 01-.73-.32l-1.67-1.83a.79.79 0 00-.68-.25l-4.28 1a.27.27 0 00-.22.25zm15.55 6.35a1.14 1.14 0 01.11.11l.07.07.15.19c.06.07.11.15.16.22l.09.15v.06a2.65 2.65 0 01-.21 3 4.45 4.45 0 01-1 1 5.89 5.89 0 01-.64.42 11.82 11.82 0 01-8.16.92 9.18 9.18 0 01-2.41-.92c-.22-.13-.44-.27-.63-.41a4.26 4.26 0 01-1.28-1.43.2.2 0 010-.08.36.36 0 010-.09.36.36 0 010-.09 2.66 2.66 0 01.38-2.63c1.59-2.21 6-3.27 9.82-2.35a7.61 7.61 0 013.55 1.87z" fill="currentColor"/></g><g><path d="M236.85 372.18c-.85.49-2.44.47-2.94 1.05v2.08l9.68-1.41 3.66 2.14-1.39 2.96-9.78-.65v2.08a6.19 6.19 0 00.66.42 16.48 16.48 0 0014.92.07c2.09-1.19 3.14-2.76 3.14-4.33v-2.08l-.58.09-.06-.11 36.63-21.15a16.63 16.63 0 0011-1.38 6.13 6.13 0 002.94-3.13v-2.13l-9.68 3.49-3.66-2.14 1.39-2.91 9.78-1.43v-2.09s-11.43-.76-15.58 1.6a6.89 6.89 0 00-2.59 2.39l-.55-.2v2.07a4 4 0 00.64 2.17l-36.61 21.15a16.6 16.6 0 00-11.02 1.38z" fill="#37474f"/><path fill="#37474f" d="M290.79 353.29l.01-2.09-36.64 21.15v2.09l36.63-21.15z"/><path d="M284.51 347.57l-36.63 21.15a16.57 16.57 0 00-11 1.38 6.1 6.1 0 00-2.94 3.13l9.68-1.42 3.66 2.15-1.39 2.91-9.78 1.43c.21.14.42.28.66.42a16.48 16.48 0 0014.92.06c3-1.73 3.87-4.26 2.5-6.43l36.63-21.15a16.57 16.57 0 0011-1.38 6.12 6.12 0 002.94-3.12l-9.68 1.41-3.66-2.11 1.39-2.9 9.78-1.44-.66-.42a16.51 16.51 0 00-14.92-.06c-3.01 1.69-3.87 4.22-2.5 6.39z" fill="#455a64"/><path fill="#263238" d="M292.2 346.42l-.78-.46 1.39-2.9 9.78-1.44v2.09l-9.78 1.43-.61 1.28zM246.47 375.59l.78-1.63-3.66-2.15-9.68 1.42v2.08l9.68-1.41 2.88 1.69z"/></g><g><path d="M259.26 403l7.66 13.26 25.83-14.91c1.06-.53 1.73-1.77 1.73-3.58a12 12 0 00-5.42-9.38 3.82 3.82 0 00-3.84-.38z" fill="currentColor"/><path d="M257.72 405.71c-.11 2-3 2.45-3 2.45l5.41 9.38s1.81-2.22 3.62-1.34c2.67 1.16 4.74-.28 4.74-3.46a12 12 0 00-5.42-9.39c-2.74-1.6-5.01-.53-5.35 2.36z" fill="currentColor"/><path d="M257.72 405.71c-.11 2-3 2.45-3 2.45l5.41 9.38s1.81-2.22 3.62-1.34c2.67 1.16 4.74-.28 4.74-3.46a12 12 0 00-5.42-9.39c-2.74-1.6-5.01-.53-5.35 2.36z" opacity=".05"/><path d="M256 407.76a5.79 5.79 0 01-1.29.4l5.41 9.38a6 6 0 011-.91 12.1 12.1 0 00-5.12-8.87z" opacity=".05"/><path d="M249.11 408.82l.45.79a5.69 5.69 0 00-.23 1.68 12 12 0 005.42 9.38 5.36 5.36 0 001.57.64l.45.78 1.81-1a3.81 3.81 0 001.58-3.51 12 12 0 00-5.41-9.38 3.81 3.81 0 00-3.83-.38z" fill="currentColor"/><path d="M258.36 418.59a12 12 0 00-5.42-9.39c-3-1.73-5.42-.32-5.42 3.13a12 12 0 005.42 9.38c2.99 1.73 5.42.29 5.42-3.12z" fill="currentColor"/><path d="M258.36 418.59a12 12 0 00-5.42-9.39c-3-1.73-5.42-.32-5.42 3.13a12 12 0 005.42 9.38c2.99 1.73 5.42.29 5.42-3.12z" opacity=".15"/><path d="M250.83 420a10.68 10.68 0 001.34 1.2l1.27-3.61-1.53-1.13z" opacity=".15"/><path d="M290.07 394a1.49 1.49 0 01-.55 2l-18.45 10.65a1.49 1.49 0 01-2-.55 1.49 1.49 0 01.55-2L288 393.47a1.49 1.49 0 012.07.53zM286.74 390.05a.87.87 0 01-.07 1.53l-18.79 10.85a2.06 2.06 0 01-2.18-.23.88.88 0 01.07-1.54l18.8-10.85a2 2 0 012.17.24zM291.84 398.89a.87.87 0 00-1.36-.71L271.68 409a2.06 2.06 0 00-.88 2 .87.87 0 001.36.71L291 400.89a2.05 2.05 0 00.84-2z" opacity=".2"/><path d="M205.43 440.47v3.61l9-2.93s1.81-3.32 3.61-4.37L252.39 417l1.05-3-1.05-.61-34.32 19.81a3.61 3.61 0 01-3.61-.19z" fill="#455a64"/><path d="M206.49 441.08v3.61l9-2.93s1.81-3.32 3.61-4.36l34.31-19.81V414l-34.31 19.81a3.59 3.59 0 01-3.61-.19z" fill="#37474f"/><path fill="#263238" d="M206.49 444.69l-1.06-.61v-3.61l1.06.61v3.61z"/><path fill="#455a64" d="M205.43 440.47l1.06.61 9.03-7.49-1.06-.61-9.03 7.49z"/></g><g><path d="M357.56 210c5.84-6 16.41-19.63 16.41-19.63l6-20.71c.15.06-3.05.07-4 0-6.46.43-8.36 1-12.38 5.38-3.19 3.49-15.36 20.12-19.6 25.71l-11.23-14.31c-2.69-4.07-2.25-8.62-3.65-11.94s-3.65-4.79-5.46-7.8-2.24-5-3.72-3.43.23 5.68.45 6.62 2.48 3.75-1.3 2.29-9.62-8.37-11.13-9.79-2.3.78-2 2.51-1.28 5.63-.69 8.49c.62 3 1.07 4.43 4 7.38 2.72 2.75 9.4 6.11 12 9.72s8.18 14.81 11.59 21.56c4.32 8.58 8 11.41 12.88 8.52s9.66-8.34 11.83-10.57z" fill="#ffa8a7"/><path d="M378.05 169.46a21.46 21.46 0 00-9.74 1.33c-3.51 1.58-5.89 5-9.31 9.56S347 196 347 196a25.79 25.79 0 0110.42 14.65l20.9-23.1z" fill="#e0e0e0"/><path d="M344 200.76a13.6 13.6 0 014 7.54s1.26-4.15-3.11-8.82z" fill="#f28f8f"/><path d="M398.16 169.39c2.27.55 8.37 1.33 12.39 2.48 3 .85 6 4.53 6.58 7.25 2 8.71.22 14.19-.52 21.82s-.79 45.7-.79 45.7c-4.23 8.26-36.28 9.94-51.79-1.39 0 0 1.9-50.68 1.9-58.57s4.38-17.36 16.48-18.07z" fill="#f5f5f5"/><path d="M384.46 144.43a1.68 1.68 0 101.67-1.74 1.71 1.71 0 00-1.67 1.74zM386.13 138.77l3.46 1.91a2.07 2.07 0 00-.8-2.75 1.93 1.93 0 00-2.66.84z" fill="#263238"/><path d="M384.88 154.65L381 156.1a2 2 0 002.63 1.29 2.17 2.17 0 001.25-2.74z" fill="#f28f8f"/><path d="M371.1 140.16l3.15-2.43a1.91 1.91 0 00-2.74-.42 2.09 2.09 0 00-.41 2.85zM371.81 144.43a1.62 1.62 0 101.62-1.68 1.65 1.65 0 00-1.62 1.68z" fill="#263238"/><path fill="#f28f8f" d="M380.58 141.64l-.55 10.18-5.32-1.81 5.87-8.37z"/><path d="M384.48 142.09l23.81-1.76c4.61 3.49 13.17 10.83 13.16 12.86v1.41a.48.48 0 01-.05.18c-.84 1.6-3.88 2.59-8.61 2.67-5.61.09-14.32-.42-16.61-2.39-3.44-3-11.7-11.63-11.7-11.63l1.2-.09z" fill="currentColor"/><path d="M384.48 142.09l23.81-1.76c4.61 3.49 13.17 10.83 13.16 12.86v1.41a.48.48 0 01-.05.18c-.84 1.6-3.88 2.59-8.61 2.67-5.61.09-14.32-.42-16.61-2.39-3.44-3-11.7-11.63-11.7-11.63l1.2-.09z" opacity=".2"/><path d="M408.29 141.67c4.81 3.65 14 11.5 13.11 13.11s-3.88 2.59-8.61 2.67c-5.61.09-14.32-.42-16.61-2.39-3.44-3-11.7-11.63-11.7-11.63z" fill="currentColor"/><path d="M408.29 141.67c4.81 3.65 14 11.5 13.11 13.11s-3.88 2.59-8.61 2.67c-5.61.09-14.32-.42-16.61-2.39-3.44-3-11.7-11.63-11.7-11.63z" opacity=".4"/><path d="M408.29 141.67c.08.69-2.35 2.13-2.35 2.13l.68-4.18z" fill="currentColor"/><path d="M408.29 141.67c.08.69-2.35 2.13-2.35 2.13l.68-4.18z" opacity=".3"/><path d="M402 127.76s3.72.52 5.09 3.32c1.18 2.44.76 8.33-.83 14.75a59.07 59.07 0 01-4 12 4.9 4.9 0 01-2.84 2.28l.19-7.62.28-5.3s-3.41-4.65-3.78-8.46c-.49-4.91.55-6.91.55-6.91z" fill="#263238"/><path d="M396 135.13a14.22 14.22 0 01-14.26 14.17c-7.85 0-13.74-6.47-13.71-14.32s5.95-14.14 13.8-14.11A14.22 14.22 0 01396 135.13z" fill="#263238"/><path d="M399.71 146.39c1.28 1.11 2.32-1.76 3.4-2.88s4.59-2.64 6.32 1.05-1.53 9-4.23 10.07a3.84 3.84 0 01-4.58-1.4v16.87c-4 7.29-11.18 7.07-15 6.65s-4.65-4.37-1.86-7.57v-5.22a30.05 30.05 0 01-6.41.31c-3.49-.55-5.32-3.19-6.36-7-1.69-6.08-2.36-16.3 0-28.11 3.91-3 17.48-2.53 25.69 2.68-.59 10.5 1.76 13.44 3.03 14.55z" fill="#ffa8a7"/><path d="M372.46 124a16.75 16.75 0 015.77-4 14.62 14.62 0 015.55-1.18c2.48 0 5-.37 7.43-.29 4.3.14 9.5 1.89 11.44 6.07.89 1.92 1 3.94-.72 5.26a12.14 12.14 0 01-5.24 2c-3.19.55-6.35 1.25-9.56 1.68a28.57 28.57 0 01-10.35-.15 13.61 13.61 0 01-4.5-1.92 12 12 0 01-1.94-1.62c-.23-.23-.84-.71-.91-1s.34-1 .51-1.26a15.87 15.87 0 012.52-3.59z" fill="#263238"/><path d="M383.79 164s7.81-1.54 10.54-3a9 9 0 003.78-3.72 12.37 12.37 0 01-2.15 4.39c-2 2.54-12.18 4.39-12.18 4.39z" fill="#f28f8f"/><path d="M385.9 143.76a1.65 1.65 0 101.65-1.71 1.68 1.68 0 00-1.65 1.71z" fill="#263238"/><path d="M382.14 156.42a1.65 1.65 0 101.65-1.71 1.67 1.67 0 00-1.65 1.71z" fill="#f28f8f"/><path d="M388.15 137.39l3.17 2.48A2.12 2.12 0 00391 137a2 2 0 00-2.85.39zM371.31 139.92l3.06-2.66a2 2 0 00-2.82-.27 2.14 2.14 0 00-.24 2.93zM372.35 143.27a1.65 1.65 0 101.65-1.71 1.68 1.68 0 00-1.65 1.71z" fill="#263238"/><path fill="#f28f8f" d="M381.39 140.45l-.62 11.68-5.54-1.46 6.16-10.22z"/><path d="M409.31 135.88a20.63 20.63 0 01-1 5.91 7.23 7.23 0 00-1.46-1.65 35 35 0 00-10.72-6.14c-9-3.48-19.5-4-27.89-1.06a18.87 18.87 0 011.12-5c3.06-8 9.25-12.15 19.13-11.12 11.57 1.18 21.13 7.6 20.82 19.06z" fill="currentColor"/><path d="M388.1 128.83c-2.69-.91-9.55-1.44-11.55-.81a12.35 12.35 0 01-2.41-2.66c-.94-1.49-.62-3.36.26-3.75s.94.78 4.35 2.47a5.68 5.68 0 01-.28-3.45c.56-1.38 1.73 1.1 4.27 2.71s5.04 2.41 5.36 5.49z" fill="#263238"/><path d="M423.2 162.72c2.24-1.84 15.82 14.25 10.14 18.88s-12.62-7-11.68-12.74 1.54-6.14 1.54-6.14z" fill="#37474f"/><path d="M434.12 153.68c-3.4-4.17-12.43-14.19-14.43-16s-3.2-1.57-4.44-2a10.28 10.28 0 01-3.71-2.32 22.53 22.53 0 01-1.79-2.1A32.4 32.4 0 00403 125c-2.68-1.85-5.62-3.22-8.36-5-1.64-1.05-3.42-2.82-1.56-4.63a4.48 4.48 0 012-1.07c4.65-1.22 8.81.22 13.45 1.2a39.46 39.46 0 015.93 1.73 10.13 10.13 0 012.65 1.38 36.9 36.9 0 014.35 4.4c3.19 3.91 5.89 5.12 9 7.83 3.67 3.17 20.12 14.8 23.59 18.26 2.88 2.87 2 5.24 1.62 6.06-2.05 4.71-4.49 6.46-7.37 9.44-.95 1-1.91 2-2.87 3-3.74 3.79-7.5 7.36-11.4 11-4.16 3.88-5.86 5.74-9.57 9.48a29.41 29.41 0 00-5.6 8.27c-6.71 1.53-11.29-12.67-8.89-20.9a10.64 10.64 0 013.9-5.13c6.84-5.46 20.25-16.64 20.25-16.64z" fill="#ffa8a7"/><path d="M434.12 153.68a17.86 17.86 0 008.51 5.34s-4.27 1.28-10.4-3.77z" fill="#f28f8f"/><path d="M410.55 171.87c1.53-1.2 5.25-3.76 12.65-9.15 0 0-1 2.56 1.21 8.7s6.42 9.88 8.93 10.18c0 0-4.11 3.66-7.85 7.74s-6.85 7-8.88 11.6c0 0-5.77-1.12-7.62-10.66-1.31-6.7-1.24-14.65 1.56-18.41z" fill="#e0e0e0"/><path d="M397.62 415.78c-.36.38-.13 3.14.32 3.77s3 2.73 7.64 2.81c4.45.08 8.38-.73 10.9-2.53s3.67-3.68 3.78-6.22-.3-5 .8-7c1-1.85 2.4-3.68 2.74-4.68a12.73 12.73 0 000-5.43z" fill="#263238"/><path d="M408.42 394.36a11.7 11.7 0 01-.39 2.84 25.51 25.51 0 01-2.42 5.7 23.18 23.18 0 01-1.7 2.71c-1.26 1.67-2.86 3.07-4.19 4.67a9.08 9.08 0 00-2.45 5.59c0 3.49 3.84 4.47 6.77 4.83a21.34 21.34 0 009.42-1 8.69 8.69 0 006-7.27c.11-1 0-2 .06-2.94a13.73 13.73 0 012.37-6.23 18.23 18.23 0 001.9-3.42c.79-2.2-.06-4.59-.76-6.7-.6-1.83-1.08-4-1.72-3.82v1.19c-.2.42-.66.72-.77 1.19a8.22 8.22 0 01-.45 1.46 3.77 3.77 0 01-1.45 1.79c-.12-1.31-.23-2.62-.35-3.93a1.79 1.79 0 00-.36-1.14 1.81 1.81 0 00-1.14-.42 24.67 24.67 0 00-7.47-.22 1.35 1.35 0 00-1 .49 1.44 1.44 0 00-.11.9 36 36 0 01.21 3.73z" fill="currentColor"/><path d="M408.42 394.36a11.7 11.7 0 01-.39 2.84 25.51 25.51 0 01-2.42 5.7 23.18 23.18 0 01-1.7 2.71c-1.26 1.67-2.86 3.07-4.19 4.67a9.08 9.08 0 00-2.45 5.59c0 3.49 3.84 4.47 6.77 4.83a21.34 21.34 0 009.42-1 8.69 8.69 0 006-7.27c.11-1 0-2 .06-2.94a13.73 13.73 0 012.37-6.23 18.23 18.23 0 001.9-3.42c.79-2.2-.06-4.59-.76-6.7-.6-1.83-1.08-4-1.72-3.82v1.19c-.2.42-.66.72-.77 1.19a8.22 8.22 0 01-.45 1.46 3.77 3.77 0 01-1.45 1.79c-.12-1.31-.23-2.62-.35-3.93a1.79 1.79 0 00-.36-1.14 1.81 1.81 0 00-1.14-.42 24.67 24.67 0 00-7.47-.22 1.35 1.35 0 00-1 .49 1.44 1.44 0 00-.11.9 36 36 0 01.21 3.73z" opacity=".2"/><path d="M405.18 403.66c1.25-1 4.15-1.21 5.74-1.13a9.74 9.74 0 014.6 1.34 1 1 0 001.32-.18 1 1 0 00-.18-1.47 9.77 9.77 0 00-5.17-1.61c-3.92-.06-5 .67-5 .67s-1.75 1.24-1.31 2.38zM402.4 407.38c1.57-1.05 4.77-1.08 6.37-1a9.15 9.15 0 014.48 1.43 1 1 0 001.32-.17 1 1 0 00-.18-1.48 10.2 10.2 0 00-5.29-1.75c-3.92-.06-4.9.79-4.9.79a3.24 3.24 0 00-1.8 2.18zM412.84 396.79a9.65 9.65 0 00-4.88.68c-.67.42-1.07 1.53-.61 1.79a9 9 0 014.32-.84 13 13 0 014.28.94 11.77 11.77 0 011.11.49.93.93 0 001.26-.47.87.87 0 00-.4-1.1 12.54 12.54 0 00-5.08-1.49z" fill="#455a64"/><path d="M343.7 401.82a4.22 4.22 0 00.34 3.08c.48.88 5.36 3.3 11.71 2.52a26.56 26.56 0 0012.95-5.32c2.35-1.74 4.86-2.18 8-2.53s5.52-1.45 6.13-3-.32-4-.32-4z" fill="#263238"/><path d="M368.47 381.6a13.6 13.6 0 01-1.69-.48 1.42 1.42 0 00-.95-.06 1.4 1.4 0 00-.62 1.1 16.25 16.25 0 01-.55 2.81 6.92 6.92 0 01-1.69 2 28.79 28.79 0 01-4.93 3.57c-2 1.23-4 2.21-6.12 3.3s-5 1.86-6.68 3.11c-2.41 1.82-2.51 6.08.43 7.6 2.54 1.32 9.1 2.53 15.79.24 3.65-1.24 7.54-5.42 12.38-6.06 3.07-.41 7.17-.89 9-3.34.67-1.08.08-4.09-.47-7.66-.49-3.2-.94-8.2-2-7.87 0 0-.38 1.18-.45 1.31a9.65 9.65 0 01-.85 1.14 11.13 11.13 0 01-.87.82 12.79 12.79 0 01-1.09 1.12 3.11 3.11 0 01-1.78.74 1.71 1.71 0 01-.89-.19c-.53-.3-.6-1-1-1.42a5.07 5.07 0 00-1.72-1.12 9.48 9.48 0 00-2.79-.56z" fill="currentColor"/><path d="M368.47 381.6a13.6 13.6 0 01-1.69-.48 1.42 1.42 0 00-.95-.06 1.4 1.4 0 00-.62 1.1 16.25 16.25 0 01-.55 2.81 6.92 6.92 0 01-1.69 2 28.79 28.79 0 01-4.93 3.57c-2 1.23-4 2.21-6.12 3.3s-5 1.86-6.68 3.11c-2.41 1.82-2.51 6.08.43 7.6 2.54 1.32 9.1 2.53 15.79.24 3.65-1.24 7.54-5.42 12.38-6.06 3.07-.41 7.17-.89 9-3.34.67-1.08.08-4.09-.47-7.66-.49-3.2-.94-8.2-2-7.87 0 0-.38 1.18-.45 1.31a9.65 9.65 0 01-.85 1.14 11.13 11.13 0 01-.87.82 12.79 12.79 0 01-1.09 1.12 3.11 3.11 0 01-1.78.74 1.71 1.71 0 01-.89-.19c-.53-.3-.6-1-1-1.42a5.07 5.07 0 00-1.72-1.12 9.48 9.48 0 00-2.79-.56z" opacity=".2"/><path d="M360.83 388.68A2.87 2.87 0 01363 387c1.39-.34 3.93.56 5.54 1.75a1.15 1.15 0 01-.06 1.91 1.14 1.14 0 01-1.32-.07 8.79 8.79 0 00-6.33-1.91zM356.61 391.35a3.3 3.3 0 012.63-1.54s3.67-.09 6.06 1.9a1.13 1.13 0 01-.06 1.85 1.08 1.08 0 01-1.27-.08 8.9 8.9 0 00-7.36-2.13zM351.92 393.81a3.69 3.69 0 012.74-1.41 9.86 9.86 0 015.81 1.94 1.13 1.13 0 01-.06 1.85 1.12 1.12 0 01-1.27-.08 8.42 8.42 0 00-7.22-2.3zM362.17 243.55c-.17 14.61.77 63.89 1.09 71.24.28 6.44 1.4 69.82 1.4 69.82 7.38 2.29 17.48-.08 17.48-.08s5.14-39.53 5.47-46.53a119.81 119.81 0 00-1-19.28l3.78-40.33s4.13 30.2 6.12 43.42c2.28 15.15 10.81 73.17 10.81 73.17 6.79 2.64 16.3-1.11 16.3-1.11s-.49-45.14-1.17-60.48c-.48-10.59-3.43-15.74-3.75-18.68 0 0-1.47-55-1.63-68.94-.1-9.34-1.11-19.6-1.11-19.6s-10.2.82-11.1-2.15 0-31.52.38-37.07c0 0-21.44-4.77-40-1.25.01-.04-2.89 43.24-3.07 57.85z" fill="#455a64"/><path d="M405.31 232c-.11 9.71 1.6 15.74 4.18 17.84s7.66 1.94 7.66 1.94v-1.45s-4.8.71-6.95-1.54-4.11-5.49-4.89-16.79zM372.31 196s11.13 0 21.66.72c0 0 .07 8.13-.84 11.09s-7.92 7.39-10.83 7.38c-2.52 0-9.53-5-10.08-7.62s.09-11.57.09-11.57zM390.37 278.35l-1-6.24c-3-.45-10.73-5.25-15.29-9.45a36.22 36.22 0 0012.79 11.78l.32 39.8zM410.55 171.87c-2-.51-3.13-.09-4.14 2.44s-2.17 10.75-2.56 18.44c0 0-4.63.84-7.84-.52 0 0 .39-10.1 1.47-15.31a18.61 18.61 0 011.5-4.44 4.64 4.64 0 015-2.53l7.32 1.34z" fill="#37474f"/><path d="M401.79 189.35a1.82 1.82 0 11-1.82-1.82 1.82 1.82 0 011.82 1.82z" fill="currentColor"/><path d="M383.76 169.18a8.43 8.43 0 00-7.44 5.13c-1.51 3.84-1.52 12.36-1.61 16.59a10.1 10.1 0 01-7.19 0s0-13.41 2.29-17.85 6.7-4.71 9-4.88a41.71 41.71 0 014.93 0z" fill="#37474f"/><path d="M373.13 187.93a1.82 1.82 0 11-1.82-1.82 1.82 1.82 0 011.82 1.82z" fill="currentColor"/></g><g><path d="M220.56 123.12c.27.39.58.79.91 1.2a3.65 3.65 0 001.2 1 3.49 3.49 0 001.71.33 5.26 5.26 0 002.35-.81 10.8 10.8 0 002.34-1.86 13.47 13.47 0 002-2.64 14.18 14.18 0 001.38-3.24 13.43 13.43 0 00.5-3.72c0-2-.52-3.42-1.56-4.12s-2.46-.52-4.25.52a6.7 6.7 0 00-1.56 1.26c-.49.52-.94 1-1.35 1.54l-1.14 1.42a3.47 3.47 0 01-.89.87l-9.15 5.29a.91.91 0 01-1.18 0 1.85 1.85 0 01-.41-1.48l1.76-33.29a5.72 5.72 0 01.5-2 2.89 2.89 0 011.09-1.39l25.12-14.51a.86.86 0 011.12.08 1.94 1.94 0 01.47 1.45v9.58a4.71 4.71 0 01-.47 2 2.91 2.91 0 01-1.12 1.36l-17.08 9.84-.41 8.28a21.2 21.2 0 012.44-2.9 14.35 14.35 0 013-2.33 15.18 15.18 0 016.31-2.2 7.62 7.62 0 015 1.24 9.55 9.55 0 013.35 4.76 24.94 24.94 0 011.2 8.43 39.65 39.65 0 01-1.32 10.34 40.32 40.32 0 01-3.64 9.11 35.8 35.8 0 01-5.43 7.43 30.63 30.63 0 01-6.69 5.31 17.17 17.17 0 01-7.14 2.53 8.28 8.28 0 01-5.31-1.18 9 9 0 01-3.35-4.21 17.42 17.42 0 01-1.23-6.47 3.8 3.8 0 01.38-1.62 2.52 2.52 0 01.91-1.12l7.46-4.3c.62-.36 1.1-.46 1.44-.29a1.84 1.84 0 01.74.51zM266.23 51.38q4.41-2.54 7.57-2.38a7.59 7.59 0 015.26 2.3 13.3 13.3 0 013.11 6 38.92 38.92 0 011.2 8.66q.12 3.83.12 8.07t-.12 8a60.14 60.14 0 01-1.2 10.05 43.56 43.56 0 01-3.11 9.61 37.46 37.46 0 01-5.26 8.37 27.9 27.9 0 01-7.57 6.36c-2.93 1.69-5.46 2.49-7.57 2.38a7.51 7.51 0 01-5.25-2.3 13.25 13.25 0 01-3.12-6 40 40 0 01-1.2-8.66Q249 98.23 249 94t.12-8.21a62.05 62.05 0 011.2-10 43.67 43.67 0 013.12-9.62 37.06 37.06 0 015.25-8.37 28.05 28.05 0 017.54-6.42zm6.28 36.51q.24-7.81 0-15.19a27.71 27.71 0 00-.44-3.9 6.86 6.86 0 00-1-2.71 2.6 2.6 0 00-1.9-1.09 5.06 5.06 0 00-2.94.89 10 10 0 00-2.93 2.5 12.82 12.82 0 00-1.88 3.3 20.17 20.17 0 00-1.06 3.91A32.37 32.37 0 00260 80q-.3 7.67 0 15.18a25.69 25.69 0 00.38 3.94 6.45 6.45 0 001.06 2.68 2.66 2.66 0 001.91 1.12 5 5 0 002.93-.88 10.28 10.28 0 002.94-2.51 13.43 13.43 0 001.91-3.32 17.28 17.28 0 001.05-3.91 33.37 33.37 0 00.33-4.41zM306.62 28.07c2.93-1.7 5.46-2.49 7.57-2.39a7.66 7.66 0 015.26 2.3 13.55 13.55 0 013.11 6 39.51 39.51 0 011.2 8.66q.12 3.83.12 8.07c0 2.83 0 5.51-.12 8a61.2 61.2 0 01-1.2 10 44.64 44.64 0 01-3.11 9.61 37.36 37.36 0 01-5.26 8.37 27.87 27.87 0 01-7.57 6.36c-2.94 1.7-5.46 2.49-7.57 2.39a7.64 7.64 0 01-5.26-2.3 13.45 13.45 0 01-3.11-6 39.51 39.51 0 01-1.2-8.66q-.12-3.64-.12-7.89c0-2.83 0-5.57.12-8.2a61.2 61.2 0 011.2-10.05 44.35 44.35 0 013.11-9.62 37.17 37.17 0 015.26-8.36 27.55 27.55 0 017.57-6.29zm6.28 36.5q.24-7.81 0-15.19a29.28 29.28 0 00-.44-3.9 6.75 6.75 0 00-1-2.7 2.55 2.55 0 00-1.88-1.13 5 5 0 00-2.93.88 10.12 10.12 0 00-2.97 2.47 13.08 13.08 0 00-1.88 3.3 19.88 19.88 0 00-1.05 3.9 32.37 32.37 0 00-.41 4.4q-.3 7.67 0 15.18a24.49 24.49 0 00.38 3.94 6.36 6.36 0 001.06 2.69 2.68 2.68 0 001.9 1.11 5 5 0 002.94-.88 10.08 10.08 0 002.93-2.51 13.23 13.23 0 001.91-3.31 18 18 0 001.06-3.82 35.52 35.52 0 00.38-4.43z" fill="currentColor"/><path d="M243.26 179.32a.29.29 0 01.39 0 .68.68 0 01.17.51v3.4a1.64 1.64 0 01-.17.7 1.06 1.06 0 01-.39.49l-9.87 5.69a.29.29 0 01-.39 0 .68.68 0 01-.17-.51v-20.7a1.64 1.64 0 01.17-.7 1.06 1.06 0 01.39-.49l9.7-5.6a.32.32 0 01.4 0 .69.69 0 01.16.51v3.4a1.62 1.62 0 01-.16.7 1.14 1.14 0 01-.4.49l-6.41 3.7v3.46l5.95-3.44a.33.33 0 01.4 0 .7.7 0 01.16.52v3.39a1.66 1.66 0 01-.16.71 1 1 0 01-.4.48l-5.95 3.44v3.59zM252.41 156.73a7.56 7.56 0 012.46-1 2.66 2.66 0 011.84.29 2.87 2.87 0 011.17 1.61 9.39 9.39 0 01.4 3 13.87 13.87 0 01-.64 4.36 11.55 11.55 0 01-1.83 3.48l2.58 6.13a.91.91 0 01.06.32 1.33 1.33 0 01-.14.57.84.84 0 01-.32.4l-2.83 1.63c-.28.16-.47.18-.59.06a1.62 1.62 0 01-.28-.42l-2.29-5.47-1.92 1.11v6.85a1.54 1.54 0 01-.17.71 1.08 1.08 0 01-.39.48l-2.73 1.58a.31.31 0 01-.39 0 .71.71 0 01-.17-.52v-20.72a1.68 1.68 0 01.17-.71 1.08 1.08 0 01.39-.48zm-2.35 11.07l2.35-1.36a4.46 4.46 0 001.45-1.35 3.83 3.83 0 00.57-2.19c0-.89-.19-1.4-.57-1.52a1.81 1.81 0 00-1.45.32l-2.35 1.36zM266.88 148.38a7.43 7.43 0 012.45-1 2.65 2.65 0 011.84.29 2.87 2.87 0 011.17 1.61 9.36 9.36 0 01.41 3 13.86 13.86 0 01-.65 4.36 11.33 11.33 0 01-1.83 3.48l2.58 6.13a.91.91 0 01.06.32 1.31 1.31 0 01-.13.57.8.8 0 01-.33.4l-2.83 1.63c-.27.16-.47.18-.59.06a1.62 1.62 0 01-.28-.42l-2.31-5.52-1.92 1.11v6.85a1.66 1.66 0 01-.16.71 1 1 0 01-.4.48l-2.72 1.57a.31.31 0 01-.4 0 .71.71 0 01-.17-.52v-20.66a1.68 1.68 0 01.17-.71 1 1 0 01.4-.48zm-2.36 11.07l2.36-1.36a4.42 4.42 0 001.44-1.35 3.83 3.83 0 00.57-2.19c0-.89-.19-1.4-.57-1.52a1.79 1.79 0 00-1.44.32l-2.36 1.36zM274.66 152.25a17.82 17.82 0 01.51-3.67 16.31 16.31 0 011.27-3.49 14.33 14.33 0 012-3 10.27 10.27 0 012.71-2.26 5.43 5.43 0 012.71-.87 3 3 0 012 .69 4.47 4.47 0 011.28 2 11.08 11.08 0 01.5 3.08c0 .91.05 1.86.05 2.86s0 1.95-.05 2.85a18.61 18.61 0 01-.5 3.67 16.62 16.62 0 01-1.28 3.5 13.54 13.54 0 01-2 3 10.55 10.55 0 01-2.71 2.24 5.61 5.61 0 01-2.71.89 2.91 2.91 0 01-2-.7 4.6 4.6 0 01-1.27-2 10.78 10.78 0 01-.51-3.09v-2.79c0-1-.03-1.98 0-2.91zm9.14.27v-1.31-1.45-1.42-1.2a5.57 5.57 0 00-.23-1.35 2 2 0 00-.49-.88 1.16 1.16 0 00-.8-.33 2.41 2.41 0 00-1.13.36 4.41 4.41 0 00-1.12.94 5.43 5.43 0 00-.8 1.25 6.79 6.79 0 00-.5 1.46 9.21 9.21 0 00-.22 1.6c0 .38-.05.8-.05 1.26v2.87c0 .44 0 .88.05 1.25a3.51 3.51 0 00.72 2.25q.63.69 1.92-.06a4.6 4.6 0 001.92-2.16 9.15 9.15 0 00.73-3.08zM296.28 131.4a7.47 7.47 0 012.46-1 2.66 2.66 0 011.84.29 2.85 2.85 0 011.16 1.62 9 9 0 01.41 3 13.87 13.87 0 01-.64 4.36 11.35 11.35 0 01-1.84 3.48l2.58 6.14a.87.87 0 01.07.31 1.34 1.34 0 01-.14.58.86.86 0 01-.32.39l-2.86 1.68c-.28.16-.48.17-.6.05a1.53 1.53 0 01-.27-.41l-2.32-5.53-1.91 1.11v6.85a1.65 1.65 0 01-.17.71 1 1 0 01-.39.48l-2.73 1.58a.29.29 0 01-.39 0 .66.66 0 01-.17-.51v-20.7a1.48 1.48 0 01.17-.7 1.06 1.06 0 01.39-.49zm-2.35 11.07l2.35-1.36a4.25 4.25 0 001.45-1.35 3.82 3.82 0 00.57-2.18q0-1.35-.57-1.53a1.85 1.85 0 00-1.45.32l-2.35 1.36zM210.63 172a.17.17 0 01-.23 0 .4.4 0 01-.09-.29v-12a1 1 0 01.09-.4.7.7 0 01.23-.29l1.58-.9a.16.16 0 01.22 0 .38.38 0 01.1.3v12a.88.88 0 01-.1.41.6.6 0 01-.22.28zM220 166.6a.29.29 0 01-.33 0 .59.59 0 01-.16-.17l-2.75-4.74V168a1 1 0 01-.1.41.55.55 0 01-.23.28l-1.57.91a.18.18 0 01-.23 0 .39.39 0 01-.1-.3v-12a.85.85 0 01.1-.4.55.55 0 01.23-.28l1.25-.73a.31.31 0 01.33 0 .49.49 0 01.16.17l2.75 4.74v-6.26a.91.91 0 01.1-.41.6.6 0 01.23-.28l1.57-.91a.18.18 0 01.23 0 .38.38 0 01.1.3v11.95a.88.88 0 01-.1.41.6.6 0 01-.23.28zM229.72 148a.18.18 0 01.23 0 .41.41 0 01.09.3v2a1 1 0 01-.09.41.67.67 0 01-.23.28l-2.12 1.22v9.49a1 1 0 01-.09.41.67.67 0 01-.23.28l-1.58.91a.18.18 0 01-.23 0 .41.41 0 01-.09-.3v-9.49l-2.12 1.22a.17.17 0 01-.22 0 .39.39 0 01-.1-.3v-2a.88.88 0 01.1-.41.59.59 0 01.22-.28zM237.45 153.56a.17.17 0 01.23 0 .41.41 0 01.09.3v2a1 1 0 01-.09.41.67.67 0 01-.23.28l-5.7 3.29a.18.18 0 01-.23 0 .41.41 0 01-.09-.3v-12a1 1 0 01.09-.41.67.67 0 01.23-.28l5.6-3.23a.18.18 0 01.23 0 .38.38 0 01.1.3v2a.88.88 0 01-.1.41.6.6 0 01-.23.28l-3.7 2.14v2l3.44-2a.17.17 0 01.23 0 .4.4 0 01.09.29v2a1 1 0 01-.09.41.61.61 0 01-.23.28l-3.44 2v2.07zM243 140.34a4 4 0 011.42-.56 1.49 1.49 0 011.06.16 1.69 1.69 0 01.67.93 5.31 5.31 0 01.24 1.75 8 8 0 01-.38 2.52 6.47 6.47 0 01-1.05 2l1.49 3.55a.57.57 0 010 .18.78.78 0 01-.07.33.57.57 0 01-.19.23l-1.64.94c-.16.1-.27.11-.34 0a1 1 0 01-.16-.25l-1.26-3.12-1.11.63v4a.86.86 0 01-.1.41.6.6 0 01-.22.28l-1.58.91a.18.18 0 01-.23 0 .43.43 0 01-.09-.3v-12a1 1 0 01.09-.4.55.55 0 01.23-.28zm-1.36 6.39l1.36-.79a2.47 2.47 0 00.83-.78 2.18 2.18 0 00.33-1.26c0-.52-.11-.81-.33-.88a1.07 1.07 0 00-.83.18l-1.36.79zM253.58 147.2q-.21.12-.33 0a.41.41 0 01-.16-.17l-2.76-4.74v6.26a1 1 0 01-.09.41.63.63 0 01-.23.27l-1.58.91a.17.17 0 01-.22 0 .38.38 0 01-.1-.3v-12a.86.86 0 01.1-.41.6.6 0 01.22-.28l1.25-.72a.31.31 0 01.34 0 .78.78 0 01.16.17l2.75 4.74v-6.26a.85.85 0 01.1-.4.6.6 0 01.22-.28l1.58-.91a.18.18 0 01.23 0 .41.41 0 01.09.3v11.95a1 1 0 01-.09.41.61.61 0 01-.23.28zM261.59 129.63c.15-.1.28-.1.36 0a.75.75 0 01.18.31l2.62 10.14a1 1 0 010 .25.78.78 0 01-.07.33.57.57 0 01-.19.23l-1.52.88c-.16.09-.27.1-.34 0a.39.39 0 01-.12-.24l-.34-1.34-3.2 1.85-.33 1.73a1.56 1.56 0 01-.13.38.82.82 0 01-.34.35l-1.52.88a.14.14 0 01-.18 0 .29.29 0 01-.08-.24 1.13 1.13 0 01.05-.3l2.62-13.18a2.46 2.46 0 01.17-.5.85.85 0 01.37-.42zm-1.95 9.21l1.95-1.13-1-3.87zM272 133.63a.18.18 0 01.23 0 .4.4 0 01.09.29v2a1 1 0 01-.09.4.61.61 0 01-.23.28l-5.57 3.22a.18.18 0 01-.23 0 .41.41 0 01-.09-.3v-12a1 1 0 01.09-.41.67.67 0 01.23-.28l1.58-.91a.17.17 0 01.22 0 .37.37 0 01.1.29v9.5zM279.3 119.21a3.21 3.21 0 011.41-.47 2 2 0 011.11.24 1.81 1.81 0 01.74.76 2.34 2.34 0 01.27 1.08.78.78 0 01-.07.33c-.06.11-.11.19-.18.22l-1.57.91a.42.42 0 01-.27.08.41.41 0 01-.21-.1.66.66 0 01-.12-.12.64.64 0 00-.22-.14 1 1 0 00-.36 0 1.54 1.54 0 00-.53.2 4.27 4.27 0 00-.44.3 3.23 3.23 0 00-.37.37 1.39 1.39 0 00-.26.44 1.27 1.27 0 00-.11.52.52.52 0 00.12.39.54.54 0 00.38.13 3.41 3.41 0 00.72-.11l1.1-.29a3.66 3.66 0 011.16-.15 1.34 1.34 0 01.82.32 1.59 1.59 0 01.49.82 5 5 0 01.16 1.41 6.42 6.42 0 01-.27 1.85 7.33 7.33 0 01-.78 1.71 7.69 7.69 0 01-1.17 1.49 7.32 7.32 0 01-1.49 1.13 3.59 3.59 0 01-1.49.53 2 2 0 01-1.16-.21 1.82 1.82 0 01-.71-.85 2.65 2.65 0 01-.29-1.22.67.67 0 01.08-.33.45.45 0 01.17-.22l1.58-.91a.42.42 0 01.26-.09.52.52 0 01.19.08l.15.16a.76.76 0 00.26.19.72.72 0 00.41.07 1.43 1.43 0 00.6-.22 4.58 4.58 0 001.07-.82 1.49 1.49 0 00.42-1c0-.19-.05-.31-.16-.37a.69.69 0 00-.46-.07 3.75 3.75 0 00-.8.15l-1.15.3a1.74 1.74 0 01-1.81-.32 3.31 3.31 0 01-.57-2.22 6.55 6.55 0 01.23-1.65 8.25 8.25 0 01.68-1.67 7.93 7.93 0 011.08-1.5 5.89 5.89 0 011.36-1.13zM290.6 122.87a.17.17 0 01.23 0 .4.4 0 01.09.29v2a1 1 0 01-.09.41.67.67 0 01-.23.28l-5.7 3.29a.18.18 0 01-.23 0 .4.4 0 01-.09-.29v-12a1 1 0 01.09-.41.67.67 0 01.23-.28l5.6-3.23a.17.17 0 01.23 0 .37.37 0 01.1.29v2a.86.86 0 01-.1.41.55.55 0 01-.23.28l-3.7 2.09v2l3.44-2a.18.18 0 01.23 0 .41.41 0 01.09.3v2a1 1 0 01-.09.4.61.61 0 01-.23.28l-3.44 2v2.07zM296.19 109.65a4.24 4.24 0 011.42-.56 1.54 1.54 0 011.06.16 1.74 1.74 0 01.68.94 5.51 5.51 0 01.23 1.74 8 8 0 01-.38 2.52 6.42 6.42 0 01-1.05 2l1.49 3.54a.57.57 0 010 .18.86.86 0 01-.07.34.47.47 0 01-.19.22l-1.63 1c-.16.09-.28.1-.35 0a1 1 0 01-.16-.24l-1.33-3.19-1.11.64v4a.85.85 0 01-.1.4.6.6 0 01-.22.28l-1.58.91a.18.18 0 01-.23 0 .41.41 0 01-.09-.3v-12a1 1 0 01.09-.41.61.61 0 01.23-.28zm-1.36 6.35l1.36-.78a2.58 2.58 0 00.83-.78 2.16 2.16 0 00.34-1.27c0-.52-.11-.81-.34-.88a1 1 0 00-.83.19l-1.36.78zM303.53 118.36c-.31.18-.52.06-.63-.37l-2.46-10.15a.61.61 0 000-.15v-.07a.67.67 0 01.08-.33.49.49 0 01.18-.23l1.52-.88c.16-.09.27-.1.34 0a.47.47 0 01.13.24l1.67 7.18 1.67-9.11a1.37 1.37 0 01.12-.38.86.86 0 01.35-.36l1.51-.87a.15.15 0 01.19 0 .33.33 0 01.08.24v.08a1.05 1.05 0 010 .18l-2.45 13a1.55 1.55 0 01-.64 1.11zM315.67 108.39a.18.18 0 01.23 0 .37.37 0 01.1.29v2a.85.85 0 01-.1.4.55.55 0 01-.23.28l-5.67 3.28a.18.18 0 01-.23 0 .39.39 0 01-.1-.3v-12a.85.85 0 01.1-.4.55.55 0 01.23-.28l5.6-3.24a.18.18 0 01.23 0 .41.41 0 01.09.3v2a1 1 0 01-.09.4.61.61 0 01-.23.28l-3.7 2.14v2l3.43-2a.17.17 0 01.23 0 .38.38 0 01.1.3v2a.88.88 0 01-.1.41.6.6 0 01-.23.28l-3.43 2v2.07zM321.26 95.17a4.56 4.56 0 011.42-.56 1.57 1.57 0 011.07.16 1.73 1.73 0 01.67.94 5.31 5.31 0 01.23 1.74 7.94 7.94 0 01-.37 2.52 6.43 6.43 0 01-1.06 2l1.49 3.54a.41.41 0 010 .19.8.8 0 01-.08.33.49.49 0 01-.19.23l-1.63.94c-.16.09-.27.1-.34 0a.78.78 0 01-.16-.24l-1.31-3.15-1.1.64v4a1 1 0 01-.1.41.67.67 0 01-.23.28L318 110a.18.18 0 01-.23 0 .38.38 0 01-.1-.3v-12a.88.88 0 01.1-.41.6.6 0 01.23-.28zm-1.35 6.39l1.35-.78a2.51 2.51 0 00.84-.78 2.2 2.2 0 00.33-1.26c0-.52-.11-.82-.33-.89a1.11 1.11 0 00-.84.19l-1.35.78zM252.08 191.4a1.47 1.47 0 00-2.18.27c-.67.83-.73 3.39-1 5.12a20.89 20.89 0 01-1.69 5.34 27.8 27.8 0 01-5.71 7.86 36.13 36.13 0 01-10.93 7.29 31.77 31.77 0 01-4.24 1.48c-1.43.39-3.35.43-4.58 1.24a.42.42 0 000 .62c1.5.62 3.34.35 4.9.14a25.91 25.91 0 005.79-1.6 35.36 35.36 0 0010.44-6.35 30.94 30.94 0 007.44-9.28 21.71 21.71 0 002.06-6.06 20.93 20.93 0 00.44-3.34 3.22 3.22 0 00-.74-2.73z" fill="#455a64"/></g></svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/topiam.svg b/ruoyi-plus-soybean/src/assets/svg-icon/topiam.svg
new file mode 100755
index 0000000..e7ea057
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/topiam.svg
@@ -0,0 +1,29 @@
+<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_446_540)">
+<path d="M113.069 160.072C103.717 170.743 93.0453 180.216 81.5345 188.609C61.5105 174.46 44.3642 156.595 30.9349 135.971C23.5009 124.46 17.2659 112.11 12.4697 99.0407C9.592 91.3668 7.19392 83.3332 5.27545 75.2996C2.03803 61.3907 0.359375 47.0022 0.359375 32.1341C0.359375 30.6953 0.359375 29.1365 0.359375 27.6977C6.35459 23.9806 12.7095 20.7432 19.0644 17.7456C20.7431 32.1341 24.1004 46.043 28.8966 59.3524C31.6544 66.9063 34.7719 74.3404 38.4889 81.4147C44.604 93.5251 52.0381 104.796 60.4314 115.228C75.1796 133.093 92.9254 148.321 113.069 160.072Z" fill="url(#paint0_linear_446_540)"/>
+<path d="M196.643 67.6256C195.084 76.3786 192.926 84.8918 190.168 93.1652C178.897 91.1269 167.266 90.0477 155.276 90.0477C154.197 90.0477 153.118 90.0477 152.039 90.0477C126.859 90.4074 102.878 95.6832 80.9352 105.036C72.302 94.8439 64.868 83.453 58.9927 71.3427C81.6546 61.8702 106.475 56.7144 132.614 56.7144C141.487 56.7144 150.24 57.3139 158.753 58.5129C171.823 60.1916 184.533 63.3091 196.643 67.6256Z" fill="url(#paint1_linear_446_540)"/>
+<path d="M199.64 34.0528C199.64 39.2087 199.401 44.3646 199.041 49.4005C186.691 44.1247 173.621 40.048 160.072 37.53C148.321 35.2518 136.211 34.0528 123.981 34.0528C97.7218 34.0528 72.6619 39.3286 49.88 48.9209C42.6858 51.9185 35.7313 55.3958 29.0167 59.2327C24.2205 46.0432 20.8632 32.0144 19.1846 17.6259C26.6186 14.1487 34.2925 11.271 42.2062 8.75301C60.3117 3.11751 79.4964 0 99.4005 0C119.904 0 139.568 3.23741 158.153 9.11272C172.782 13.789 186.691 20.2638 199.52 28.1775C199.64 30.2159 199.64 32.1343 199.64 34.0528Z" fill="url(#paint2_linear_446_540)"/>
+<path d="M190.168 93.2855C182.494 116.547 170.384 137.65 154.796 155.875C149.76 161.751 144.364 167.386 138.609 172.542C126.858 183.214 113.789 192.446 99.7601 200C93.4052 196.523 87.41 192.686 81.5347 188.609C93.0455 180.336 103.717 170.744 113.069 160.072C117.866 154.676 122.302 148.921 126.499 143.046C137.65 127.098 146.403 109.233 152.158 90.1679C153.237 90.1679 154.316 90.1679 155.396 90.1679C167.146 90.048 178.777 91.1272 190.168 93.2855Z" fill="url(#paint3_linear_446_540)"/>
+</g>
+<defs>
+<linearGradient id="paint0_linear_446_540" x1="15.8569" y1="27.5782" x2="86.4712" y2="182.06" gradientUnits="userSpaceOnUse">
+<stop stop-color="#57A4F7"/>
+<stop offset="1" stop-color="#2158F9"/>
+</linearGradient>
+<linearGradient id="paint1_linear_446_540" x1="58.9501" y1="80.8427" x2="196.648" y2="80.8427" gradientUnits="userSpaceOnUse">
+<stop stop-color="#2158F9"/>
+<stop offset="1" stop-color="#33E1E5"/>
+</linearGradient>
+<linearGradient id="paint2_linear_446_540" x1="19.1564" y1="29.6353" x2="199.647" y2="29.6353" gradientUnits="userSpaceOnUse">
+<stop stop-color="#255DF9"/>
+<stop offset="1" stop-color="#7C35BA"/>
+</linearGradient>
+<linearGradient id="paint3_linear_446_540" x1="95.3808" y1="192.567" x2="174.674" y2="97.4815" gradientUnits="userSpaceOnUse">
+<stop stop-color="#54A0F7"/>
+<stop offset="1" stop-color="#2158F9"/>
+</linearGradient>
+<clipPath id="clip0_446_540">
+<rect width="200" height="200" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/ruoyi-plus-soybean/src/assets/svg-icon/wind.svg b/ruoyi-plus-soybean/src/assets/svg-icon/wind.svg
new file mode 100755
index 0000000..7c90590
--- /dev/null
+++ b/ruoyi-plus-soybean/src/assets/svg-icon/wind.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="feather feather-wind"><path d="M9.59 4.59A2 2 0 1 1 11 8H2m10.59 11.41A2 2 0 1 0 14 16H2m15.73-8.27A2.5 2.5 0 1 1 19.5 12H2"></path></svg>
diff --git a/ruoyi-plus-soybean/src/components/advanced/table-column-setting.vue b/ruoyi-plus-soybean/src/components/advanced/table-column-setting.vue
new file mode 100755
index 0000000..5c24075
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/advanced/table-column-setting.vue
@@ -0,0 +1,54 @@
+<script setup lang="ts" generic="T extends Record<string, unknown>, K = never">
+import { VueDraggable } from 'vue-draggable-plus';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'TableColumnSetting'
+});
+
+const columns = defineModel<NaiveUI.TableColumnCheck[]>('columns', {
+ required: true
+});
+</script>
+
+<template>
+ <NPopover placement="bottom-end" trigger="click">
+ <template #trigger>
+ <NButton size="small">
+ <template #icon>
+ <icon-ant-design-setting-outlined class="text-icon" />
+ </template>
+ {{ $t('common.columnSetting') }}
+ </NButton>
+ </template>
+ <VueDraggable v-model="columns" :animation="150" filter=".none_draggable" class="table-column-setting-list">
+ <div
+ v-for="item in columns"
+ :key="item.key"
+ class="h-36px flex-y-center rd-4px hover:(bg-primary bg-opacity-20)"
+ :class="{ hidden: !item.visible }"
+ >
+ <icon-mdi-drag class="mr-8px h-full cursor-move text-icon" />
+ <NCheckbox v-model:checked="item.checked" class="none_draggable flex-1">
+ <template v-if="typeof item.title === 'function'">
+ <component :is="item.title" />
+ </template>
+ <template v-else>{{ item.title }}</template>
+ </NCheckbox>
+ </div>
+ </VueDraggable>
+ </NPopover>
+</template>
+
+<style scoped lang="scss">
+.table-column-setting-list {
+ max-height: 60vh;
+ overflow-y: auto;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+
+.table-column-setting-list::-webkit-scrollbar {
+ display: none;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/components/advanced/table-header-operation.vue b/ruoyi-plus-soybean/src/components/advanced/table-header-operation.vue
new file mode 100755
index 0000000..4966d47
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/advanced/table-header-operation.vue
@@ -0,0 +1,97 @@
+<script setup lang="ts">
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'TableHeaderOperation'
+});
+
+interface Props {
+ itemAlign?: NaiveUI.Align;
+ disabledDelete?: boolean;
+ disableAdd?: boolean;
+ loading?: boolean;
+ showAdd?: boolean;
+ showDelete?: boolean;
+ showExport?: boolean;
+ showRefresh?: boolean;
+}
+
+withDefaults(defineProps<Props>(), {
+ itemAlign: undefined,
+ showAdd: true,
+ showDelete: true,
+ showExport: false,
+ showRefresh: true
+});
+
+interface Emits {
+ (e: 'add'): void;
+ (e: 'delete'): void;
+ (e: 'refresh'): void;
+ (e: 'export'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const columns = defineModel<NaiveUI.TableColumnCheck[]>('columns', {
+ default: () => []
+});
+
+function add() {
+ emit('add');
+}
+
+function batchDelete() {
+ emit('delete');
+}
+
+function refresh() {
+ emit('refresh');
+}
+
+function handleExport() {
+ emit('export');
+}
+</script>
+
+<template>
+ <NSpace :align="itemAlign" wrap justify="end" class="lt-sm:w-200px">
+ <slot name="prefix"></slot>
+ <slot name="default">
+ <NButton v-if="showAdd" :disabled="disableAdd" size="small" ghost type="primary" @click="add">
+ <template #icon>
+ <icon-material-symbols-add class="text-icon" />
+ </template>
+ {{ $t('common.add') }}
+ </NButton>
+ <NPopconfirm v-if="showDelete" @positive-click="batchDelete">
+ <template #trigger>
+ <NButton size="small" ghost type="error" :disabled="disabledDelete">
+ <template #icon>
+ <icon-material-symbols-delete-outline class="text-icon" />
+ </template>
+ {{ $t('common.batchDelete') }}
+ </NButton>
+ </template>
+ {{ $t('common.confirmDelete') }}
+ </NPopconfirm>
+ <NButton v-if="showExport" size="small" ghost @click="handleExport">
+ <template #icon>
+ <icon-material-symbols-download-rounded class="text-icon" />
+ </template>
+ {{ $t('common.export') }}
+ </NButton>
+ </slot>
+ <slot name="after"></slot>
+ <NButton v-if="showRefresh" size="small" @click="refresh">
+ <template #icon>
+ <icon-material-symbols-refresh-rounded class="text-icon" :class="{ 'animate-spin': loading }" />
+ </template>
+ {{ $t('common.refresh') }}
+ </NButton>
+ <TableColumnSetting v-model:columns="columns" />
+ <slot name="suffix"></slot>
+ </NSpace>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/advanced/table-row-check-alert.vue b/ruoyi-plus-soybean/src/components/advanced/table-row-check-alert.vue
new file mode 100755
index 0000000..db4e32d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/advanced/table-row-check-alert.vue
@@ -0,0 +1,29 @@
+<script setup lang="ts">
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'TableRowCheckAlert'
+});
+
+const checkedRowKeys = defineModel<CommonType.IdType[]>('checkedRowKeys', { required: true });
+</script>
+
+<template>
+ <NAlert type="info">
+ <span v-if="checkedRowKeys.length">
+ {{ $t('common.selected') }} {{ checkedRowKeys.length }} {{ $t('common.anyRecords') }}
+ <NButton class="pl-6px" text type="primary" @click="() => (checkedRowKeys = [])">
+ {{ $t('common.clear') }}
+ </NButton>
+ </span>
+ <span v-else>{{ $t('common.noSelectRecord') }}</span>
+ </NAlert>
+</template>
+
+<style scoped lang="scss">
+.n-alert {
+ --n-padding: 5px 13px !important;
+ --n-icon-margin: 6px 8px 0 12px !important;
+ --n-icon-size: 20px !important;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/components/advanced/table-sider-layout.vue b/ruoyi-plus-soybean/src/components/advanced/table-sider-layout.vue
new file mode 100755
index 0000000..bd6b675
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/advanced/table-sider-layout.vue
@@ -0,0 +1,109 @@
+<script setup lang="ts">
+import { breakpointsTailwind, useBreakpoints } from '@vueuse/core';
+
+defineOptions({
+ name: 'TableSiderLayout'
+});
+
+interface Props {
+ defaultExpanded?: boolean;
+ siderTitle?: string;
+}
+
+withDefaults(defineProps<Props>(), {
+ defaultExpanded: false,
+ siderTitle: undefined
+});
+
+const time = new Date().getTime();
+const breakpoints = useBreakpoints(breakpointsTailwind);
+const isCollapse = breakpoints.smaller('lg');
+</script>
+
+<template>
+ <NGrid
+ v-if="isCollapse"
+ class="min-h-500px flex-col-stretch gap-16px overflow-auto"
+ :x-gap="12"
+ :y-gap="12"
+ item-responsive
+ responsive="screen"
+ >
+ <NGridItem span="24 s:24 1034:10 m:8 l:7 xl:6 xxl:5">
+ <NCard
+ :bordered="false"
+ size="small"
+ class="sider-layout-card h-full card-wrapper"
+ content-class="sider-layout-card-content"
+ >
+ <NCollapse v-if="isCollapse" :default-expanded-names="defaultExpanded ? [`table-sider-layout${time}`] : []">
+ <NCollapseItem :title="siderTitle" :name="`table-sider-layout${time}`" display-directive="show">
+ <slot name="sider" />
+ <template #header>
+ <slot name="header">
+ <span>{{ siderTitle }}</span>
+ </slot>
+ </template>
+ <template #header-extra>
+ <slot name="header-extra" />
+ </template>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+ </NGridItem>
+ <NGridItem class="content" span="24 s:24 m:16 l:17 xl:18 xxl:19">
+ <slot />
+ </NGridItem>
+ </NGrid>
+ <NLayout v-else has-sider>
+ <NLayoutSider collapse-mode="transform" :collapsed-width="0" :width="320" show-trigger="bar">
+ <NCard
+ :bordered="false"
+ size="small"
+ class="sider-layout-card h-full card-wrapper"
+ content-class="sider-layout-card-content"
+ >
+ <slot name="sider" />
+ <template #header>
+ <slot name="header">
+ <span>{{ siderTitle }}</span>
+ </slot>
+ </template>
+ <template #header-extra>
+ <slot name="header-extra" />
+ </template>
+ </NCard>
+ </NLayoutSider>
+ <NLayoutContent content-class="bg-transparent">
+ <slot />
+ </NLayoutContent>
+ </NLayout>
+</template>
+
+<style scoped>
+.title {
+ font-weight: 500;
+ font-size: 16px;
+ transition: color 0.3s var(--n-bezier);
+ flex: 1;
+ min-width: 0;
+ color: var(--n-title-text-color);
+}
+
+.content {
+ min-height: calc(100vh - 196px - var(--calc-footer-height, 0px));
+}
+
+:deep(.n-collapse-item__header) {
+ padding-top: 0 !important;
+}
+
+:deep(.n-layout-content) {
+ background-color: transparent;
+ padding-left: 25px;
+}
+
+:deep(.n-layout-sider) {
+ background-color: transparent;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/components/common/app-provider.vue b/ruoyi-plus-soybean/src/components/common/app-provider.vue
new file mode 100755
index 0000000..f6aa527
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/common/app-provider.vue
@@ -0,0 +1,56 @@
+<script setup lang="ts">
+import { createTextVNode, defineComponent } from 'vue';
+import { useDialog, useLoadingBar, useMessage, useNotification } from 'naive-ui';
+import useContentLoading from '@/hooks/common/loading';
+
+defineOptions({
+ name: 'AppProvider'
+});
+
+const contentLoading = useContentLoading();
+
+const ContextHolder = defineComponent({
+ name: 'ContextHolder',
+ setup() {
+ function register() {
+ window.$loadingBar = useLoadingBar();
+ window.$dialog = useDialog();
+ window.$message = useMessage();
+ window.$notification = useNotification();
+ window.$loading = contentLoading;
+ }
+
+ register();
+
+ return () => createTextVNode();
+ }
+});
+</script>
+
+<template>
+ <NSpin
+ class="h-full"
+ :size="52"
+ content-class="h-full"
+ :show="contentLoading.loading.value"
+ :description="contentLoading.description.value"
+ >
+ <NLoadingBarProvider>
+ <NDialogProvider>
+ <NNotificationProvider>
+ <NMessageProvider>
+ <ContextHolder />
+ <slot></slot>
+ </NMessageProvider>
+ </NNotificationProvider>
+ </NDialogProvider>
+ </NLoadingBarProvider>
+ </NSpin>
+</template>
+
+<style scoped>
+:deep(.n-spin-description) {
+ margin-top: 24px;
+ font-size: 16px;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/components/common/dark-mode-container.vue b/ruoyi-plus-soybean/src/components/common/dark-mode-container.vue
new file mode 100755
index 0000000..71f2733
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/common/dark-mode-container.vue
@@ -0,0 +1,17 @@
+<script setup lang="ts">
+defineOptions({ name: 'DarkModeContainer' });
+
+interface Props {
+ inverted?: boolean;
+}
+
+defineProps<Props>();
+</script>
+
+<template>
+ <div class="bg-container text-base-text transition-300" :class="{ 'bg-inverted text-#1f1f1f': inverted }">
+ <slot></slot>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/common/data-table.vue b/ruoyi-plus-soybean/src/components/common/data-table.vue
new file mode 100755
index 0000000..cc5ce08
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/common/data-table.vue
@@ -0,0 +1,35 @@
+<script setup lang="ts">
+import { useAttrs } from 'vue';
+import type { DataTableProps } from 'naive-ui';
+import type { CreateRowKey } from 'naive-ui/es/data-table/src/interface';
+import { useThemeStore } from '@/store/modules/theme';
+
+defineOptions({
+ name: 'DataTable',
+ inheritAttrs: false
+});
+
+interface Props {
+ rowKey?: CreateRowKey<any>;
+}
+
+defineProps<Props>();
+
+const { table } = useThemeStore();
+const attrs: DataTableProps = useAttrs();
+</script>
+
+<template>
+ <NDataTable
+ :bordered="table.bordered"
+ :bottom-bordered="table.bottomBordered"
+ :single-column="table.singleColumn"
+ :single-line="table.singleLine"
+ :size="table.size"
+ :striped="table.striped"
+ :row-key="rowKey"
+ v-bind="attrs"
+ />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/common/exception-base.vue b/ruoyi-plus-soybean/src/components/common/exception-base.vue
new file mode 100755
index 0000000..418e965
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/common/exception-base.vue
@@ -0,0 +1,43 @@
+<script lang="ts" setup>
+import { computed } from 'vue';
+import { useRouterPush } from '@/hooks/common/router';
+import { $t } from '@/locales';
+
+defineOptions({ name: 'ExceptionBase' });
+
+type ExceptionType = '403' | '404' | '500';
+
+interface Props {
+ /**
+ * Exception type
+ *
+ * - 403: no permission
+ * - 404: not found
+ * - 500: service error
+ */
+ type: ExceptionType;
+}
+
+const props = defineProps<Props>();
+
+const { routerPushByKey } = useRouterPush();
+
+const iconMap: Record<ExceptionType, string> = {
+ '403': 'no-permission',
+ '404': 'not-found',
+ '500': 'service-error'
+};
+
+const icon = computed(() => iconMap[props.type]);
+</script>
+
+<template>
+ <div class="size-full min-h-520px flex-col-center gap-24px overflow-hidden">
+ <div class="flex text-400px text-primary">
+ <SvgIcon :local-icon="icon" />
+ </div>
+ <NButton type="primary" @click="routerPushByKey('root')">{{ $t('common.backToHome') }}</NButton>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/common/full-screen.vue b/ruoyi-plus-soybean/src/components/common/full-screen.vue
new file mode 100755
index 0000000..41c1c56
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/common/full-screen.vue
@@ -0,0 +1,22 @@
+<script setup lang="ts">
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'FullScreen'
+});
+
+interface Props {
+ full?: boolean;
+}
+
+defineProps<Props>();
+</script>
+
+<template>
+ <ButtonIcon :key="String(full)" :tooltip-content="full ? $t('icon.fullscreenExit') : $t('icon.fullscreen')">
+ <icon-gridicons-fullscreen-exit v-if="full" />
+ <icon-gridicons-fullscreen v-else />
+ </ButtonIcon>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/common/icon-tooltip.vue b/ruoyi-plus-soybean/src/components/common/icon-tooltip.vue
new file mode 100755
index 0000000..d98db32
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/common/icon-tooltip.vue
@@ -0,0 +1,42 @@
+<script lang="ts" setup>
+import { computed, useSlots } from 'vue';
+import type { PopoverPlacement } from 'naive-ui';
+
+defineOptions({ name: 'IconTooltip' });
+
+interface Props {
+ icon?: string;
+ localIcon?: string;
+ desc?: string;
+ placement?: PopoverPlacement;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ icon: 'mdi-help-circle',
+ localIcon: '',
+ desc: '',
+ placement: 'top'
+});
+
+const slots = useSlots();
+const hasCustomTrigger = computed(() => Boolean(slots.trigger));
+
+if (!hasCustomTrigger.value && !props.icon && !props.localIcon) {
+ throw new Error('icon or localIcon is required when no custom trigger slot is provided');
+}
+</script>
+
+<template>
+ <NTooltip :placement="placement">
+ <template #trigger>
+ <slot name="trigger">
+ <div class="cursor-pointer">
+ <SvgIcon :icon="icon" :local-icon="localIcon" />
+ </div>
+ </slot>
+ </template>
+ <slot>
+ <span>{{ desc }}</span>
+ </slot>
+ </NTooltip>
+</template>
diff --git a/ruoyi-plus-soybean/src/components/common/lang-switch.vue b/ruoyi-plus-soybean/src/components/common/lang-switch.vue
new file mode 100755
index 0000000..bd9d2ec
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/common/lang-switch.vue
@@ -0,0 +1,61 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'LangSwitch'
+});
+
+interface Props {
+ /** Current language */
+ lang: App.I18n.LangType;
+ /** Language options */
+ langOptions: App.I18n.LangOption[];
+ /** Show tooltip */
+ showTooltip?: boolean;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ showTooltip: true
+});
+
+type Emits = {
+ (e: 'changeLang', lang: App.I18n.LangType): void;
+};
+
+const emit = defineEmits<Emits>();
+
+const tooltipContent = computed(() => {
+ if (!props.showTooltip) return '';
+
+ return $t('icon.lang');
+});
+
+/** Add bottom margin to all options except the last one for proper visual separation */
+const dropdownOptions = computed(() => {
+ const lastIndex = props.langOptions.length - 1;
+
+ return props.langOptions.map((option, index) => ({
+ ...option,
+ props: {
+ class: index < lastIndex ? 'mb-1' : undefined
+ }
+ }));
+});
+
+function changeLang(lang: App.I18n.LangType) {
+ emit('changeLang', lang);
+}
+</script>
+
+<template>
+ <NDropdown :value="lang" :options="dropdownOptions" trigger="hover" @select="changeLang">
+ <div>
+ <ButtonIcon :tooltip-content="tooltipContent" tooltip-placement="left">
+ <SvgIcon icon="heroicons:language" />
+ </ButtonIcon>
+ </div>
+ </NDropdown>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/common/menu-toggler.vue b/ruoyi-plus-soybean/src/components/common/menu-toggler.vue
new file mode 100755
index 0000000..31cb1b9
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/common/menu-toggler.vue
@@ -0,0 +1,53 @@
+<script lang="ts" setup>
+import { computed } from 'vue';
+import { $t } from '@/locales';
+
+defineOptions({ name: 'MenuToggler' });
+
+interface Props {
+ /** Show collapsed icon */
+ collapsed?: boolean;
+ /** Arrow style icon */
+ arrowIcon?: boolean;
+ zIndex?: number;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ arrowIcon: false,
+ zIndex: 98
+});
+
+type NumberBool = 0 | 1;
+
+const icon = computed(() => {
+ const icons: Record<NumberBool, Record<NumberBool, string>> = {
+ 0: {
+ 0: 'line-md:menu-fold-left',
+ 1: 'line-md:menu-fold-right'
+ },
+ 1: {
+ 0: 'ph-caret-double-left-bold',
+ 1: 'ph-caret-double-right-bold'
+ }
+ };
+
+ const arrowIcon = Number(props.arrowIcon || false) as NumberBool;
+
+ const collapsed = Number(props.collapsed || false) as NumberBool;
+
+ return icons[arrowIcon][collapsed];
+});
+</script>
+
+<template>
+ <ButtonIcon
+ :key="String(collapsed)"
+ :tooltip-content="collapsed ? $t('icon.expand') : $t('icon.collapse')"
+ tooltip-placement="bottom-start"
+ :z-index="zIndex"
+ >
+ <SvgIcon :icon="icon" />
+ </ButtonIcon>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/common/pin-toggler.vue b/ruoyi-plus-soybean/src/components/common/pin-toggler.vue
new file mode 100755
index 0000000..aecc92d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/common/pin-toggler.vue
@@ -0,0 +1,26 @@
+<script lang="ts" setup>
+import { computed } from 'vue';
+import { $t } from '@/locales';
+
+defineOptions({ name: 'PinToggler' });
+
+interface Props {
+ pin?: boolean;
+}
+
+const props = defineProps<Props>();
+
+const icon = computed(() => (props.pin ? 'mdi-pin-off' : 'mdi-pin'));
+</script>
+
+<template>
+ <ButtonIcon
+ :tooltip-content="pin ? $t('icon.unpin') : $t('icon.pin')"
+ tooltip-placement="bottom-start"
+ :z-index="100"
+ >
+ <SvgIcon :icon="icon" />
+ </ButtonIcon>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/common/reload-button.vue b/ruoyi-plus-soybean/src/components/common/reload-button.vue
new file mode 100755
index 0000000..2881d03
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/common/reload-button.vue
@@ -0,0 +1,21 @@
+<script setup lang="ts">
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'ReloadButton'
+});
+
+interface Props {
+ loading?: boolean;
+}
+
+defineProps<Props>();
+</script>
+
+<template>
+ <ButtonIcon :tooltip-content="$t('icon.reload')">
+ <icon-ant-design-reload-outlined :class="{ 'animate-spin animate-duration-750': loading }" />
+ </ButtonIcon>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/common/system-logo.vue b/ruoyi-plus-soybean/src/components/common/system-logo.vue
new file mode 100755
index 0000000..7f23cc1
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/common/system-logo.vue
@@ -0,0 +1,9 @@
+<script lang="ts" setup>
+defineOptions({ name: 'SystemLogo' });
+</script>
+
+<template>
+ <icon-local-logo />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/common/theme-schema-switch.vue b/ruoyi-plus-soybean/src/components/common/theme-schema-switch.vue
new file mode 100755
index 0000000..78050d4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/common/theme-schema-switch.vue
@@ -0,0 +1,56 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import type { PopoverPlacement } from 'naive-ui';
+import { $t } from '@/locales';
+
+defineOptions({ name: 'ThemeSchemaSwitch' });
+
+interface Props {
+ /** Theme schema */
+ themeSchema: UnionKey.ThemeScheme;
+ /** Show tooltip */
+ showTooltip?: boolean;
+ /** Tooltip placement */
+ tooltipPlacement?: PopoverPlacement;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ showTooltip: true,
+ tooltipPlacement: 'bottom'
+});
+
+interface Emits {
+ (e: 'switch'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+function handleSwitch() {
+ emit('switch');
+}
+
+const icons: Record<UnionKey.ThemeScheme, string> = {
+ light: 'material-symbols:sunny',
+ dark: 'material-symbols:nightlight-rounded',
+ auto: 'material-symbols:hdr-auto'
+};
+
+const icon = computed(() => icons[props.themeSchema]);
+
+const tooltipContent = computed(() => {
+ if (!props.showTooltip) return '';
+
+ return $t('icon.themeSchema');
+});
+</script>
+
+<template>
+ <ButtonIcon
+ :icon="icon"
+ :tooltip-content="tooltipContent"
+ :tooltip-placement="tooltipPlacement"
+ @click="handleSwitch"
+ />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/better-scroll.vue b/ruoyi-plus-soybean/src/components/custom/better-scroll.vue
new file mode 100755
index 0000000..7d2559c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/better-scroll.vue
@@ -0,0 +1,53 @@
+<script setup lang="ts">
+import { computed, onMounted, ref, watch } from 'vue';
+import { useElementSize } from '@vueuse/core';
+import BScroll from '@better-scroll/core';
+import type { Options } from '@better-scroll/core';
+
+defineOptions({ name: 'BetterScroll' });
+
+interface Props {
+ /**
+ * BetterScroll options
+ *
+ * @link https://better-scroll.github.io/docs/zh-CN/guide/base-scroll-options.html
+ */
+ options: Options;
+}
+
+const props = defineProps<Props>();
+
+const bsWrapper = ref<HTMLElement>();
+const bsContent = ref<HTMLElement>();
+const { width: wrapWidth } = useElementSize(bsWrapper);
+const { width, height } = useElementSize(bsContent);
+
+const instance = ref<BScroll>();
+const isScrollY = computed(() => Boolean(props.options.scrollY));
+
+function initBetterScroll() {
+ if (!bsWrapper.value) return;
+ instance.value = new BScroll(bsWrapper.value, props.options);
+}
+
+// refresh BS when scroll element size changed
+watch([() => wrapWidth.value, () => width.value, () => height.value], () => {
+ instance.value?.refresh();
+});
+
+onMounted(() => {
+ initBetterScroll();
+});
+
+defineExpose({ instance });
+</script>
+
+<template>
+ <div ref="bsWrapper" class="h-full text-left">
+ <div ref="bsContent" class="inline-block" :class="{ 'h-full': !isScrollY }">
+ <slot></slot>
+ </div>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/boolean-tag.vue b/ruoyi-plus-soybean/src/components/custom/boolean-tag.vue
new file mode 100755
index 0000000..627aea6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/boolean-tag.vue
@@ -0,0 +1,24 @@
+<script setup lang="ts">
+import { useAttrs } from 'vue';
+import type { TagProps } from 'naive-ui';
+import { isNotNull } from '@/utils/common';
+
+defineOptions({
+ name: 'BooleanTag'
+});
+
+const value = defineModel<'0' | '1'>('value', { required: true });
+
+const tagMap: Record<'0' | '1', NaiveUI.ThemeColor> = {
+ 0: 'success',
+ 1: 'error'
+};
+
+const attrs: TagProps = useAttrs();
+</script>
+
+<template>
+ <NTag v-if="isNotNull(value)" :type="tagMap[value]" v-bind="attrs">{{ value === '0' ? '鏄�' : '鍚�' }}</NTag>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/button-icon.vue b/ruoyi-plus-soybean/src/components/custom/button-icon.vue
new file mode 100755
index 0000000..604a491
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/button-icon.vue
@@ -0,0 +1,91 @@
+<script setup lang="ts">
+import { type VNode, computed, useAttrs } from 'vue';
+import type { ButtonProps, PopoverPlacement } from 'naive-ui';
+import { twMerge } from 'tailwind-merge';
+
+defineOptions({
+ name: 'ButtonIcon',
+ inheritAttrs: false
+});
+
+interface Props {
+ /** Button class */
+ class?: string;
+ /** Show popconfirm icon */
+ showPopconfirmIcon?: boolean;
+ /** Iconify icon name */
+ icon?: string;
+ /** Local icon name */
+ localIcon?: string;
+ /** Tooltip content */
+ tooltipContent?: string;
+ /** Tooltip placement */
+ tooltipPlacement?: PopoverPlacement;
+ /** Popconfirm content - can be string or VNode */
+ popconfirmContent?: string | VNode;
+ zIndex?: number;
+ quaternary?: boolean;
+ [key: string]: any;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ class: '',
+ showPopconfirmIcon: true,
+ icon: '',
+ localIcon: '',
+ tooltipContent: '',
+ tooltipPlacement: 'bottom',
+ popconfirmContent: '',
+ zIndex: 98,
+ quaternary: true
+});
+
+interface Emits {
+ (e: 'positiveClick'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const DEFAULT_CLASS = 'h-[36px] text-icon';
+
+const attrs: ButtonProps = useAttrs();
+
+const quaternary = computed(() => {
+ return !(attrs.text || attrs.dashed || attrs.ghost) && props.quaternary;
+});
+
+const handlePositiveClick = () => {
+ emit('positiveClick');
+};
+</script>
+
+<template>
+ <NTooltip :placement="tooltipPlacement" :z-index="zIndex" :disabled="!tooltipContent">
+ <template #trigger>
+ <NPopconfirm :show-icon="showPopconfirmIcon" :disabled="!popconfirmContent" @positive-click="handlePositiveClick">
+ <template #default>
+ <component :is="popconfirmContent" v-if="typeof popconfirmContent !== 'string'" />
+ <template v-else>{{ popconfirmContent }}</template>
+ </template>
+ <template #trigger>
+ <NButton
+ :quaternary="quaternary"
+ :class="twMerge(DEFAULT_CLASS, props.class)"
+ :focusable="false"
+ v-bind="attrs"
+ >
+ <div class="flex-center gap-8px">
+ <slot>
+ <SvgIcon v-if="icon" :icon="icon" />
+ <SvgIcon v-else :local-icon="localIcon" />
+ </slot>
+ </div>
+ </NButton>
+ </template>
+ </NPopconfirm>
+ </template>
+ {{ tooltipContent }}
+ </NTooltip>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/count-to.vue b/ruoyi-plus-soybean/src/components/custom/count-to.vue
new file mode 100755
index 0000000..910b4cc
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/count-to.vue
@@ -0,0 +1,88 @@
+<script setup lang="ts">
+import { computed, nextTick, ref, watch } from 'vue';
+import { TransitionPresets, useTransition } from '@vueuse/core';
+
+defineOptions({
+ name: 'CountTo'
+});
+
+interface Props {
+ startValue?: number;
+ endValue?: number;
+ duration?: number;
+ autoplay?: boolean;
+ decimals?: number;
+ prefix?: string;
+ suffix?: string;
+ separator?: string;
+ decimal?: string;
+ useEasing?: boolean;
+ transition?: keyof typeof TransitionPresets;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ startValue: 0,
+ endValue: 2021,
+ duration: 1500,
+ autoplay: true,
+ decimals: 0,
+ prefix: '',
+ suffix: '',
+ separator: ',',
+ decimal: '.',
+ useEasing: true,
+ transition: 'linear'
+});
+
+const source = ref(props.startValue);
+
+const transition = computed(() => (props.useEasing ? TransitionPresets[props.transition] : undefined));
+
+const outputValue = useTransition(source, {
+ disabled: false,
+ duration: props.duration,
+ transition: transition.value
+});
+
+const value = computed(() => formatValue(outputValue.value));
+
+function formatValue(num: number) {
+ const { decimals, decimal, separator, suffix, prefix } = props;
+
+ let number = num.toFixed(decimals);
+ number = String(number);
+
+ const x = number.split('.');
+ let x1 = x[0];
+ const x2 = x.length > 1 ? decimal + x[1] : '';
+ const rgx = /(\d+)(\d{3})/;
+ if (separator) {
+ while (rgx.test(x1)) {
+ x1 = x1.replace(rgx, `$1${separator}$2`);
+ }
+ }
+
+ return prefix + x1 + x2 + suffix;
+}
+
+async function start() {
+ await nextTick();
+ source.value = props.endValue;
+}
+
+watch(
+ [() => props.startValue, () => props.endValue],
+ () => {
+ if (props.autoplay) {
+ start();
+ }
+ },
+ { immediate: true }
+);
+</script>
+
+<template>
+ <span>{{ value }}</span>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/dept-tree-select.vue b/ruoyi-plus-soybean/src/components/custom/dept-tree-select.vue
new file mode 100755
index 0000000..457c4cf
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/dept-tree-select.vue
@@ -0,0 +1,51 @@
+<script setup lang="tsx">
+import { useAttrs } from 'vue';
+import type { TreeSelectProps } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { fetchGetDeptTree } from '@/service/api/system';
+
+defineOptions({ name: 'DeptTreeSelect' });
+
+interface Props {
+ [key: string]: any;
+}
+
+defineProps<Props>();
+
+const value = defineModel<CommonType.IdType | null>('value', { required: false });
+const options = defineModel<Api.Common.CommonTreeRecord>('options', { required: false, default: [] });
+const expandedKeys = defineModel<CommonType.IdType[]>('expandedKeys', { required: false, default: [] });
+
+const attrs: TreeSelectProps = useAttrs();
+const { loading, startLoading, endLoading } = useLoading();
+
+async function getDeptList() {
+ startLoading();
+ const { error, data } = await fetchGetDeptTree();
+ if (error) return;
+ options.value = data;
+ // 璁剧疆榛樿灞曞紑鐨勮妭鐐�
+ if (data?.length && !expandedKeys.value.length) {
+ expandedKeys.value = [data[0].id];
+ }
+ endLoading();
+}
+
+getDeptList();
+</script>
+
+<template>
+ <NTreeSelect
+ v-model:value="value"
+ v-model:expanded-keys="expandedKeys"
+ filterable
+ class="h-full"
+ :loading="loading"
+ key-field="id"
+ label-field="label"
+ :options="options as []"
+ v-bind="attrs"
+ />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/dept-tree.vue b/ruoyi-plus-soybean/src/components/custom/dept-tree.vue
new file mode 100755
index 0000000..25ef2e8
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/dept-tree.vue
@@ -0,0 +1,154 @@
+<script setup lang="tsx">
+import { onMounted, ref, useAttrs, watch } from 'vue';
+import type { TreeSelectInst, TreeSelectProps } from 'naive-ui';
+import { useBoolean } from '@sa/hooks';
+import { fetchGetDeptTree } from '@/service/api/system/user';
+
+defineOptions({ name: 'DeptTree' });
+
+interface Props {
+ immediate?: boolean;
+ [key: string]: any;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ immediate: true
+});
+
+const { bool: expandAll } = useBoolean();
+const { bool: checkAll } = useBoolean();
+const expandedKeys = ref<CommonType.IdType[]>([100]);
+
+const deptTreeRef = ref<TreeSelectInst | null>(null);
+const value = defineModel<CommonType.IdType[]>('value', { required: false, default: [] });
+const options = defineModel<any[]>('options', { required: false, default: [] });
+const cascade = defineModel<boolean>('cascade', { required: false, default: true });
+const loading = defineModel<boolean>('loading', { required: false, default: false });
+
+const attrs: TreeSelectProps = useAttrs();
+
+async function getDeptList() {
+ loading.value = true;
+ const { error, data } = await fetchGetDeptTree();
+ if (error) return;
+
+ // 纭繚 options.value 鏄暟缁�
+ if (data) {
+ options.value = Array.isArray(data) ? data : [data];
+ } else {
+ options.value = [];
+ }
+
+ loading.value = false;
+}
+
+onMounted(() => {
+ if (props.immediate) {
+ getDeptList();
+ }
+});
+
+watch([expandAll, options], ([newVal]) => {
+ if (newVal) {
+ // 灞曞紑鎵�鏈夎妭鐐�
+ expandedKeys.value = getAllDeptIds(options.value);
+ } else {
+ // 鎶樺彔鍒板彧鏄剧ず鏍硅妭鐐�
+ expandedKeys.value = [100];
+ }
+});
+
+function getAllDeptIds(depts: any[]) {
+ const deptIds: CommonType.IdType[] = [];
+ depts.forEach(item => {
+ if (item.id) {
+ deptIds.push(item.id);
+ }
+ if (item.children && Array.isArray(item.children)) {
+ deptIds.push(...getAllDeptIds(item.children));
+ }
+ });
+ return deptIds;
+}
+
+function handleCheckedTreeNodeAll(checked: boolean) {
+ if (checked) {
+ value.value = getAllDeptIds(options.value);
+ return;
+ }
+ value.value = [];
+}
+
+function handleSubmit() {
+ const deptIds = [...value.value];
+ const indeterminateData = deptTreeRef.value?.getIndeterminateData();
+ if (cascade.value) {
+ const parentIds: string[] = indeterminateData?.keys.filter(item => !deptIds?.includes(String(item))) as string[];
+ deptIds?.push(...parentIds);
+ }
+ return deptIds;
+}
+
+defineExpose({
+ submit: handleSubmit,
+ refresh: getDeptList
+});
+</script>
+
+<template>
+ <div class="w-full flex-col gap-12px">
+ <div class="w-full flex-center">
+ <NCheckbox v-model:checked="expandAll" :checked-value="true" :unchecked-value="false">灞曞紑/鎶樺彔</NCheckbox>
+ <NCheckbox
+ v-model:checked="checkAll"
+ :checked-value="true"
+ :unchecked-value="false"
+ @update:checked="handleCheckedTreeNodeAll"
+ >
+ 鍏ㄩ��/鍙嶉��
+ </NCheckbox>
+ <NCheckbox v-model:checked="cascade" :checked-value="true" :unchecked-value="false">鐖跺瓙鑱斿姩</NCheckbox>
+ </div>
+ <NSpin class="resource h-full w-full py-6px pl-3px" content-class="h-full" :show="loading">
+ <NTree
+ ref="deptTreeRef"
+ v-model:checked-keys="value"
+ v-model:expanded-keys="expandedKeys"
+ multiple
+ checkable
+ :selectable="false"
+ key-field="id"
+ label-field="label"
+ :data="options"
+ :cascade="cascade"
+ :loading="loading"
+ virtual-scroll
+ :check-strategy="cascade ? 'child' : 'all'"
+ v-bind="attrs"
+ />
+ </NSpin>
+ </div>
+</template>
+
+<style scoped lang="scss">
+.resource {
+ border-radius: 6px;
+ border: 1px solid rgb(224, 224, 230);
+
+ .n-tree {
+ min-height: 200px;
+ max-height: 300px;
+ width: 100%;
+ height: 100%;
+
+ :deep(.n-tree__empty) {
+ min-height: 200px;
+ justify-content: center;
+ }
+ }
+
+ .n-empty {
+ justify-content: center;
+ }
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/components/custom/dict-checkbox.vue b/ruoyi-plus-soybean/src/components/custom/dict-checkbox.vue
new file mode 100755
index 0000000..6483166
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/dict-checkbox.vue
@@ -0,0 +1,28 @@
+<script setup lang="ts">
+import { useDict } from '@/hooks/business/dict';
+
+defineOptions({ name: 'DictCheckbox' });
+
+interface Props {
+ dictCode: string;
+ immediate?: boolean;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ immediate: false
+});
+
+const value = defineModel<string[]>('value', { required: false });
+
+const { options } = useDict(props.dictCode, props.immediate);
+</script>
+
+<template>
+ <NCheckboxGroup v-model:value="value">
+ <NSpace>
+ <NCheckbox v-for="option in options" :key="option.value" :value="option.value" :label="option.label" />
+ </NSpace>
+ </NCheckboxGroup>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/dict-radio.vue b/ruoyi-plus-soybean/src/components/custom/dict-radio.vue
new file mode 100755
index 0000000..cf83004
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/dict-radio.vue
@@ -0,0 +1,28 @@
+<script setup lang="ts">
+import { useDict } from '@/hooks/business/dict';
+
+defineOptions({ name: 'DictRadio' });
+
+interface Props {
+ dictCode: string;
+ immediate?: boolean;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ immediate: false
+});
+
+const value = defineModel<string | null>('value', { required: false });
+
+const { options } = useDict(props.dictCode, props.immediate);
+</script>
+
+<template>
+ <NRadioGroup v-model:value="value">
+ <NSpace>
+ <NRadio v-for="option in options" :key="option.value" :value="option.value" :label="option.label" />
+ </NSpace>
+ </NRadioGroup>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/dict-select.vue b/ruoyi-plus-soybean/src/components/custom/dict-select.vue
new file mode 100755
index 0000000..6956dfe
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/dict-select.vue
@@ -0,0 +1,37 @@
+<script setup lang="ts">
+import { useAttrs } from 'vue';
+import type { SelectProps } from 'naive-ui';
+import { useDict } from '@/hooks/business/dict';
+
+defineOptions({ name: 'DictSelect' });
+
+interface Props {
+ dictCode: string;
+ immediate?: boolean;
+ multiple?: boolean;
+ [key: string]: any;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ immediate: false,
+ multiple: false
+});
+
+const value = defineModel<string | string[] | null>('value', { required: false });
+
+const attrs: SelectProps = useAttrs();
+const { options } = useDict(props.dictCode, props.immediate);
+</script>
+
+<template>
+ <NSelect
+ v-model:value="value"
+ :multiple="multiple"
+ :loading="!options.length"
+ :options="options"
+ :clear-filter-after-select="false"
+ v-bind="attrs"
+ />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/dict-tag.vue b/ruoyi-plus-soybean/src/components/custom/dict-tag.vue
new file mode 100755
index 0000000..a96a6e3
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/dict-tag.vue
@@ -0,0 +1,62 @@
+<script setup lang="ts">
+import { computed, useAttrs } from 'vue';
+import type { TagProps } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import { useDict } from '@/hooks/business/dict';
+import { isNotNull } from '@/utils/common';
+import { $t } from '@/locales';
+
+defineOptions({ name: 'DictTag' });
+
+interface Props {
+ value?: string[] | number[] | string | number;
+ dictCode?: string;
+ immediate?: boolean;
+ dictData?: Api.System.DictData;
+ [key: string]: any;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ immediate: false,
+ dictData: undefined,
+ dictCode: '',
+ value: () => []
+});
+
+const attrs = useAttrs() as TagProps;
+
+const { transformDictData } = useDict(props.dictCode, props.immediate);
+
+const dictTagData = computed<Api.System.DictData[]>(() => {
+ if (props.dictData) {
+ const dictData = jsonClone(props.dictData);
+ if (dictData.dictLabel?.startsWith(`dict.${dictData.dictType}.`)) {
+ dictData.dictLabel = $t(dictData.dictLabel as App.I18n.I18nKey);
+ }
+ return [dictData];
+ }
+ // 閬垮厤 props.value 涓� 0 鏃讹紝鏃犳硶瑙﹀彂
+ if (props.dictCode && isNotNull(props.value)) {
+ return transformDictData(props.value) || [];
+ }
+
+ return [];
+});
+</script>
+
+<template>
+ <div v-if="dictTagData.length">
+ <NTag
+ v-for="item in dictTagData"
+ :key="item.dictValue"
+ class="m-1"
+ :class="[item.cssClass]"
+ v-bind="attrs"
+ :type="item.listClass || 'default'"
+ >
+ {{ item.dictLabel }}
+ </NTag>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/file-upload.vue b/ruoyi-plus-soybean/src/components/custom/file-upload.vue
new file mode 100755
index 0000000..249c7fa
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/file-upload.vue
@@ -0,0 +1,212 @@
+<script setup lang="tsx">
+import { computed, defineComponent, useAttrs } from 'vue';
+import type { UploadFileInfo, UploadProps } from 'naive-ui';
+import type { JSX } from 'vue/jsx-runtime';
+import { fetchBatchDeleteOss } from '@/service/api/system/oss';
+import { getToken } from '@/store/modules/auth/shared';
+import { getServiceBaseURL } from '@/utils/service';
+import { AcceptType } from '@/enum/business';
+
+defineOptions({
+ name: 'FileUpload'
+});
+
+interface Props {
+ action?: string;
+ data?: Record<string, any>;
+ defaultUpload?: boolean;
+ showTip?: boolean;
+ max?: number;
+ accept?: string;
+ fileSize?: number;
+ uploadType?: 'file' | 'image';
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ action: `/resource/oss/upload`,
+ data: undefined,
+ defaultUpload: true,
+ showTip: true,
+ max: 5,
+ accept: undefined,
+ fileSize: 5,
+ uploadType: 'file'
+});
+
+const accept = computed(() => {
+ if (props.accept) {
+ return props.accept;
+ }
+ return props.uploadType === 'file' ? AcceptType.File : AcceptType.Image;
+});
+
+const attrs: UploadProps = useAttrs();
+
+let fileNum = 0;
+const fileList = defineModel<UploadFileInfo[]>('fileList', {
+ default: () => []
+});
+
+const TooltipContent = defineComponent({
+ setup() {
+ const startTip = <>璇蜂笂浼�</>;
+
+ const maxTip = (
+ <>
+ 鏁伴噺涓嶈秴杩�
+ <b class="text-info"> {props.max}涓�</b>锛�
+ </>
+ );
+
+ const fileSizeTip = (
+ <>
+ 澶у皬涓嶈秴杩�
+ <b class="text-info"> {props.fileSize}MB</b>锛�
+ </>
+ );
+
+ const acceptTip = (
+ <>
+ 鏍煎紡涓�
+ <b class="text-info"> {props.accept?.replaceAll(',', ', ')} </b>
+ </>
+ );
+
+ const tips: JSX.Element[] = [];
+ if (props.max) tips.push(maxTip);
+ if (props.fileSize) tips.push(fileSizeTip);
+ if (props.accept) tips.push(acceptTip);
+
+ const endTip = (
+ <>
+ {tips.length ? '鐨�' : ''}
+ {props.uploadType === 'file' ? '鏂囦欢' : '鍥剧墖'}
+ </>
+ );
+
+ return () => (
+ <NP depth={3}>
+ {startTip}
+ {tips.map(tip => tip)}
+ {endTip}
+ </NP>
+ );
+ }
+});
+
+const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
+const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
+
+const headers: Record<string, string> = {
+ Authorization: `Bearer ${getToken()}`,
+ clientid: import.meta.env.VITE_APP_CLIENT_ID!
+};
+
+function beforeUpload(options: { file: UploadFileInfo; fileList: UploadFileInfo[] }) {
+ fileNum += 1;
+ const { file } = options;
+
+ // 鏍℃鏂囦欢绫诲瀷
+ if (accept.value) {
+ const fileName = file.name.split('.');
+ const fileExt = `.${fileName[fileName.length - 1]}`;
+ const isTypeOk = accept.value.split(',')?.includes(fileExt);
+ if (!isTypeOk) {
+ window.$message?.error(`鏂囦欢鏍煎紡涓嶆纭�, 璇蜂笂浼� ${accept.value} 鏍煎紡鏂囦欢!`);
+ return false;
+ }
+ }
+ // 鏍℃鏂囦欢鍚嶆槸鍚﹀寘鍚壒娈婂瓧绗�
+ if (file.name.includes(',')) {
+ window.$message?.error('鏂囦欢鍚嶄笉姝g‘锛屼笉鑳藉寘鍚嫳鏂囬�楀彿!');
+ return false;
+ }
+ // 鏍℃鏂囦欢澶у皬
+ if (props.fileSize && file.file?.size) {
+ const isLt = file.file?.size / 1024 / 1024 < props.fileSize;
+ if (!isLt) {
+ window.$message?.error(`涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃 ${props.fileSize} MB!`);
+ return false;
+ }
+ }
+ return true;
+}
+
+function isErrorState(xhr: XMLHttpRequest) {
+ const responseText = xhr?.responseText;
+ const response = JSON.parse(responseText);
+ return response.code !== 200;
+}
+
+function handleFinish(options: { file: UploadFileInfo; event?: ProgressEvent }) {
+ fileNum -= 1;
+ const { file, event } = options;
+ // @ts-expect-error Ignore type errors
+ const responseText = event?.target?.responseText;
+ const response = JSON.parse(responseText);
+ const oss: Api.System.Oss = response.data;
+ fileList.value.find(item => item.id === file.id)!.id = String(oss.ossId);
+ file.id = String(oss.ossId);
+ file.url = oss.url;
+ file.name = oss.fileName;
+ if (fileNum === 0) {
+ window.$message?.success('涓婁紶鎴愬姛');
+ }
+ return file;
+}
+
+function handleError(options: { file: UploadFileInfo; event?: ProgressEvent }) {
+ const { event } = options;
+ // @ts-expect-error Ignore type errors
+ const responseText = event?.target?.responseText;
+ const msg = JSON.parse(responseText).msg;
+ window.$message?.error(msg || '涓婁紶澶辫触');
+}
+
+async function handleRemove(file: UploadFileInfo) {
+ if (file.status !== 'finished') {
+ return false;
+ }
+ const { error } = await fetchBatchDeleteOss([file.id]);
+ if (error) return false;
+ window.$message?.success('鍒犻櫎鎴愬姛');
+ return true;
+}
+</script>
+
+<template>
+ <div class="w-full flex-col">
+ <NUpload
+ v-bind="attrs"
+ v-model:file-list="fileList"
+ :action="`${baseURL}${action}`"
+ :data="data"
+ :headers="headers"
+ :max="max"
+ :accept="accept"
+ :multiple="max > 1"
+ directory-dnd
+ :default-upload="defaultUpload"
+ :list-type="uploadType === 'image' ? 'image-card' : 'text'"
+ :is-error-state="isErrorState"
+ @finish="handleFinish"
+ @error="handleError"
+ @before-upload="beforeUpload"
+ @remove="({ file }) => handleRemove(file)"
+ >
+ <NUploadDragger v-if="uploadType === 'file'">
+ <div class="mb-12px flex-center">
+ <SvgIcon icon="material-symbols:unarchive-outline" class="text-58px color-#d8d8db dark:color-#a1a1a2" />
+ </div>
+ <NText class="text-16px">鐐瑰嚮鎴栬�呮嫋鍔ㄦ枃浠跺埌璇ュ尯鍩熸潵涓婁紶</NText>
+ <TooltipContent v-if="showTip" class="mt-8px text-center" />
+ </NUploadDragger>
+ <NUploadDragger v-else>
+ <SvgIcon icon="material-symbols:image-arrow-up-outline" class="text-58px color-#d8d8db dark:color-#a1a1a2" />
+ </NUploadDragger>
+ </NUpload>
+ <TooltipContent v-if="showTip && uploadType === 'image'" class="mt-12px" />
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/form-tip.vue b/ruoyi-plus-soybean/src/components/custom/form-tip.vue
new file mode 100755
index 0000000..352ad59
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/form-tip.vue
@@ -0,0 +1,26 @@
+<script setup lang="ts">
+import { NTooltip } from 'naive-ui';
+
+defineOptions({
+ name: 'FormTip'
+});
+
+interface Props {
+ content: string;
+}
+
+defineProps<Props>();
+</script>
+
+<template>
+ <NTooltip trigger="hover">
+ <template #default>
+ <span class="whitespace-pre-wrap">{{ content }}</span>
+ </template>
+ <template #trigger>
+ <div class="cursor-pointer pr-3px">
+ <SvgIcon class="text-15px" icon="ph:warning-circle-bold" />
+ </div>
+ </template>
+ </NTooltip>
+</template>
diff --git a/ruoyi-plus-soybean/src/components/custom/json-preview.vue b/ruoyi-plus-soybean/src/components/custom/json-preview.vue
new file mode 100755
index 0000000..e815806
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/json-preview.vue
@@ -0,0 +1,52 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import hljs from 'highlight.js/lib/core';
+import json from 'highlight.js/lib/languages/json';
+import { twMerge } from 'tailwind-merge';
+
+hljs.registerLanguage('json', json);
+
+defineOptions({
+ name: 'JsonPreview'
+});
+
+interface Props {
+ class?: string;
+ code?: string;
+ showLineNumbers?: boolean;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ class: '',
+ code: '',
+ showLineNumbers: false
+});
+
+const DEFAULT_CLASS = 'max-h-500px';
+
+/** 鏍煎紡鍖朖SON鏁版嵁 */
+const jsonData = computed<string>(() => {
+ if (!props.code) return '';
+ try {
+ return typeof props.code === 'string'
+ ? JSON.stringify(JSON.parse(props.code), null, '\t')
+ : JSON.stringify(props.code, null, '\t');
+ } catch {
+ return props.code;
+ }
+});
+</script>
+
+<template>
+ <NScrollbar :class="twMerge(DEFAULT_CLASS, props.class)">
+ <NCode :code="jsonData" :hljs="hljs" language="json" :show-line-numbers="showLineNumbers" :word-wrap="true" />
+ </NScrollbar>
+</template>
+
+<style lang="scss">
+html[class='dark'] {
+ .vjs-tree-node:hover {
+ background-color: #7c7777;
+ }
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/components/custom/look-forward.vue b/ruoyi-plus-soybean/src/components/custom/look-forward.vue
new file mode 100755
index 0000000..d0494f9
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/look-forward.vue
@@ -0,0 +1,20 @@
+<script setup lang="ts">
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'LookForward'
+});
+</script>
+
+<template>
+ <div class="size-full min-h-520px flex-col-center gap-24px overflow-hidden">
+ <div class="flex text-400px text-primary">
+ <SvgIcon local-icon="expectation" />
+ </div>
+ <slot>
+ <h3 class="text-28px text-primary font-500">{{ $t('common.lookForward') }}</h3>
+ </slot>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/menu-tree-select.vue b/ruoyi-plus-soybean/src/components/custom/menu-tree-select.vue
new file mode 100755
index 0000000..a08c910
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/menu-tree-select.vue
@@ -0,0 +1,82 @@
+<script setup lang="tsx">
+import { onMounted, useAttrs } from 'vue';
+import type { TreeOption, TreeSelectProps } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { fetchGetMenuList } from '@/service/api/system';
+import { handleTree } from '@/utils/common';
+import SvgIcon from '@/components/custom/svg-icon.vue';
+import { $t } from '@/locales';
+
+defineOptions({ name: 'MenuTreeSelect' });
+
+interface Props {
+ immediate?: boolean;
+ [key: string]: any;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ immediate: true
+});
+
+const value = defineModel<CommonType.IdType | null>('value', { required: false });
+const options = defineModel<Api.System.MenuList>('options', { required: false, default: [] });
+
+const attrs: TreeSelectProps = useAttrs();
+const { loading, startLoading, endLoading } = useLoading();
+
+async function getMenuList() {
+ startLoading();
+ const { error, data } = await fetchGetMenuList();
+ if (error) return;
+ const { tree } = handleTree(data, { idField: 'menuId', filterFn: item => item.menuType !== 'F' });
+ options.value = [
+ {
+ menuId: 0,
+ menuName: '鏍圭洰褰�',
+ icon: 'material-symbols:home-outline-rounded',
+ children: tree
+ }
+ ] as Api.System.MenuList;
+ endLoading();
+}
+
+onMounted(() => {
+ if (props.immediate) {
+ getMenuList();
+ }
+});
+
+function renderLabel({ option }: { option: TreeOption }) {
+ let label = String(option.menuName);
+ if (label?.startsWith('route.') || label?.startsWith('menu.')) {
+ label = $t(label as App.I18n.I18nKey);
+ }
+ return <div>{label}</div>;
+}
+
+function renderPrefix({ option }: { option: TreeOption }) {
+ const renderLocalIcon = String(option.icon).startsWith('local-icon-');
+ const icon = renderLocalIcon ? undefined : String(option.icon);
+ const localIcon = renderLocalIcon ? String(option.icon).replace('local-icon-', 'menu-') : undefined;
+ return <SvgIcon icon={icon} localIcon={localIcon} />;
+}
+</script>
+
+<template>
+ <NTreeSelect
+ v-model:value="value"
+ filterable
+ class="h-full"
+ :loading="loading"
+ key-field="menuId"
+ label-field="menuName"
+ :options="options"
+ :default-expanded-keys="[0]"
+ :render-tag="renderLabel"
+ :render-label="renderLabel"
+ :render-prefix="renderPrefix"
+ v-bind="attrs"
+ />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/menu-tree.vue b/ruoyi-plus-soybean/src/components/custom/menu-tree.vue
new file mode 100755
index 0000000..64f6853
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/menu-tree.vue
@@ -0,0 +1,222 @@
+<script setup lang="tsx">
+import { onMounted, ref, useAttrs, watch } from 'vue';
+import type { TreeOption, TreeSelectInst, TreeSelectProps } from 'naive-ui';
+import { useBoolean } from '@sa/hooks';
+import { fetchGetMenuTreeSelect } from '@/service/api/system';
+import SvgIcon from '@/components/custom/svg-icon.vue';
+import { $t } from '@/locales';
+
+defineOptions({ name: 'MenuTree' });
+
+interface Props {
+ immediate?: boolean;
+ showHeader?: boolean;
+ [key: string]: any;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ immediate: true,
+ showHeader: true
+});
+
+const { bool: expandAll } = useBoolean();
+const { bool: checkAll } = useBoolean();
+const expandedKeys = ref<CommonType.IdType[]>([0]);
+
+const menuTreeRef = ref<TreeSelectInst | null>(null);
+const checkedKeys = defineModel<CommonType.IdType[]>('checkedKeys', { required: false, default: [] });
+const options = defineModel<Api.System.MenuList>('options', { required: false, default: [] });
+const cascade = defineModel<boolean>('cascade', { required: false, default: true });
+const loading = defineModel<boolean>('loading', { required: false, default: false });
+const attrs: TreeSelectProps = useAttrs();
+
+async function getMenuList() {
+ loading.value = true;
+ const { error, data } = await fetchGetMenuTreeSelect();
+ if (error) return;
+ options.value = [
+ {
+ id: 0,
+ label: '鏍圭洰褰�',
+ icon: 'material-symbols:home-outline-rounded',
+ children: data
+ }
+ ] as Api.System.MenuList;
+ // 鎶樺彔鍒板彧鏄剧ず鏍硅妭鐐�
+ loading.value = false;
+}
+
+onMounted(() => {
+ if (props.immediate) {
+ getMenuList();
+ }
+});
+
+watch([expandAll, options], ([newVal]) => {
+ if (newVal) {
+ // 灞曞紑鎵�鏈夎妭鐐�
+ expandedKeys.value = getAllMenuIds(options.value);
+ } else {
+ expandedKeys.value = [0];
+ }
+});
+
+function renderLabel({ option }: { option: TreeOption }) {
+ let label = option.label;
+ if (label?.startsWith('route.') || label?.startsWith('menu.')) {
+ label = $t(label as App.I18n.I18nKey);
+ }
+ // 绂佺敤鐨勮彍鍗曟樉绀虹孩鑹�
+ if (option.status === '1') {
+ return (
+ <div class="flex items-center gap-4px text-error-200">
+ {label}
+ <SvgIcon icon="ri:prohibited-line" class="text-16px" />
+ </div>
+ );
+ }
+ // 闅愯棌鐨勮彍鍗曟樉绀虹伆鑹�
+ if (option.visible === '1') {
+ return (
+ <div class="flex items-center gap-4px text-gray-400">
+ {label}
+ <SvgIcon icon="codex:hidden" class="text-21px" />
+ </div>
+ );
+ }
+ return <div>{label}</div>;
+}
+
+function renderPrefix({ option }: { option: TreeOption }) {
+ const renderLocalIcon = String(option.icon).startsWith('local-icon-');
+ let icon = renderLocalIcon ? undefined : String(option.icon ?? 'material-symbols:buttons-alt-outline-rounded');
+ const localIcon = renderLocalIcon ? String(option.icon).replace('local-icon-', 'menu-') : undefined;
+ if (icon === '#') {
+ icon = 'material-symbols:buttons-alt-outline-rounded';
+ }
+ return <SvgIcon icon={icon} localIcon={localIcon} />;
+}
+
+function getAllMenuIds(menu: Api.System.MenuList) {
+ const menuIds: CommonType.IdType[] = [];
+ menu.forEach(item => {
+ menuIds.push(item.id!);
+ if (item.children) {
+ menuIds.push(...getAllMenuIds(item.children));
+ }
+ });
+ return menuIds;
+}
+
+/** 鑾峰彇鎵�鏈夊彾瀛愯妭鐐圭殑 ID锛堟病鏈夊瓙鑺傜偣鐨勮妭鐐癸級 */
+function getLeafMenuIds(menu: Api.System.MenuList): CommonType.IdType[] {
+ const leafIds: CommonType.IdType[] = [];
+ menu.forEach(item => {
+ if (!item.children || item.children.length === 0) {
+ // 鏄彾瀛愯妭鐐�
+ leafIds.push(item.id!);
+ } else {
+ // 鏈夊瓙鑺傜偣锛岄�掑綊鑾峰彇瀛愯妭鐐逛腑鐨勫彾瀛愯妭鐐�
+ leafIds.push(...getLeafMenuIds(item.children));
+ }
+ });
+ return leafIds;
+}
+
+function handleCheckedTreeNodeAll(checked: boolean) {
+ if (checked) {
+ checkedKeys.value = getAllMenuIds(options.value);
+ return;
+ }
+ checkedKeys.value = [];
+}
+
+function getCheckedMenuIds(isCascade: boolean = false) {
+ const menuIds = menuTreeRef.value?.getCheckedData()?.keys as string[];
+ const indeterminateData = menuTreeRef.value?.getIndeterminateData();
+ if (cascade.value || isCascade) {
+ const parentIds: string[] = indeterminateData?.keys.filter(item => !menuIds?.includes(String(item))) as string[];
+ menuIds?.push(...parentIds);
+ }
+ return menuIds;
+}
+
+watch(cascade, () => {
+ if (cascade.value) {
+ // 鑾峰彇褰撳墠鑿滃崟鏍戜腑鐨勬墍鏈夊彾瀛愯妭鐐笽D
+ const allLeafIds = getLeafMenuIds(options.value);
+ // 绛涢�夊嚭褰撳墠閫変腑椤逛腑鐨勫彾瀛愯妭鐐�
+ const selectedLeafIds = checkedKeys.value.filter(id => allLeafIds.includes(id));
+ // 閲嶆柊璁剧疆閫変腑鐘舵�佷负鍙寘鍚彾瀛愯妭鐐癸紝璁╃粍浠跺熀浜庣埗瀛愯仈鍔ㄨ鍒欓噸鏂拌绠楃埗鑺傜偣鐘舵��
+ checkedKeys.value = selectedLeafIds;
+ return;
+ }
+ // 绂佺敤鐖跺瓙鑱斿姩鏃讹紝灏嗗崐閫変腑鐨勭埗鑺傜偣涔熷姞鍏ュ埌閫変腑鍒楄〃
+ checkedKeys.value = getCheckedMenuIds(true);
+});
+
+defineExpose({
+ getCheckedMenuIds,
+ refresh: getMenuList
+});
+</script>
+
+<template>
+ <div class="w-full flex-col gap-12px">
+ <div v-if="showHeader" class="w-full flex-center">
+ <NCheckbox v-model:checked="expandAll" :checked-value="true" :unchecked-value="false">灞曞紑/鎶樺彔</NCheckbox>
+ <NCheckbox
+ v-model:checked="checkAll"
+ :checked-value="true"
+ :unchecked-value="false"
+ @update:checked="handleCheckedTreeNodeAll"
+ >
+ 鍏ㄩ��/鍙嶉��
+ </NCheckbox>
+ <NCheckbox v-model:checked="cascade" :checked-value="true" :unchecked-value="false">鐖跺瓙鑱斿姩</NCheckbox>
+ </div>
+ <NSpin class="resource h-full w-full py-6px pl-3px" content-class="h-full" :show="loading">
+ <NTree
+ ref="menuTreeRef"
+ v-model:checked-keys="checkedKeys"
+ v-model:expanded-keys="expandedKeys"
+ multiple
+ checkable
+ :selectable="false"
+ key-field="id"
+ label-field="label"
+ :data="options"
+ :cascade="cascade"
+ :loading="loading"
+ virtual-scroll
+ check-strategy="all"
+ :render-label="renderLabel"
+ :render-prefix="renderPrefix"
+ v-bind="attrs"
+ />
+ </NSpin>
+ </div>
+</template>
+
+<style scoped lang="scss">
+.resource {
+ border-radius: 6px;
+ border: 1px solid rgb(224, 224, 230);
+
+ .n-tree {
+ min-height: 200px;
+ max-height: 300px;
+ width: 100%;
+ height: 100%;
+
+ :deep(.n-tree__empty) {
+ min-height: 200px;
+ justify-content: center;
+ }
+ }
+
+ .n-empty {
+ justify-content: center;
+ }
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/components/custom/oss-upload.vue b/ruoyi-plus-soybean/src/components/custom/oss-upload.vue
new file mode 100755
index 0000000..1dcb07c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/oss-upload.vue
@@ -0,0 +1,69 @@
+<script setup lang="ts">
+import { ref, useAttrs, watch } from 'vue';
+import type { UploadFileInfo } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { fetchGetOssListByIds } from '@/service/api/system/oss';
+import { isNotNull } from '@/utils/common';
+import FileUpload from '@/components/custom/file-upload.vue';
+
+defineOptions({
+ name: 'OssUpload'
+});
+
+const attrs = useAttrs();
+
+const value = defineModel<string>('value', { default: '' });
+
+const { loading, startLoading, endLoading } = useLoading();
+
+const fileList = ref<UploadFileInfo[]>([]);
+
+async function handleFetchOssList(ossIds: string[]) {
+ startLoading();
+ try {
+ const { error, data } = await fetchGetOssListByIds(ossIds);
+ if (error) return;
+ fileList.value = data.map(item => ({
+ id: String(item.ossId),
+ url: item.url,
+ name: item.originalName,
+ status: 'finished'
+ }));
+ } catch (error) {
+ window.$message?.error(`鑾峰彇鏂囦欢鍒楄〃澶辫触: ${error}`);
+ } finally {
+ endLoading();
+ }
+}
+
+watch(
+ value,
+ async val => {
+ const ossIds = val?.split(',')?.filter(item => isNotNull(item)) || [];
+ if (ossIds.length === 0) {
+ fileList.value = [];
+ return;
+ }
+ const fileIds = new Set(fileList.value.filter(item => item.status === 'finished').map(item => item.id));
+ if (ossIds.every(item => fileIds.has(item))) {
+ return;
+ }
+ await handleFetchOssList(ossIds);
+ },
+ { immediate: true }
+);
+
+watch(fileList, val => {
+ value.value = val
+ .filter(item => item.status === 'finished')
+ .map(item => item.id)
+ .join(',');
+});
+</script>
+
+<template>
+ <NSpin v-if="loading" />
+ <FileUpload v-else v-bind="attrs" v-model:file-list="fileList" />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/post-select.vue b/ruoyi-plus-soybean/src/components/custom/post-select.vue
new file mode 100755
index 0000000..57cfee9
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/post-select.vue
@@ -0,0 +1,63 @@
+<script setup lang="ts">
+import { ref, useAttrs, watch } from 'vue';
+import type { SelectProps } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { fetchGetPostSelect } from '@/service/api/system';
+
+defineOptions({
+ name: 'PostSelect'
+});
+
+interface Props {
+ deptId?: CommonType.IdType | null;
+ [key: string]: any;
+}
+
+const props = defineProps<Props>();
+
+const value = defineModel<CommonType.IdType[] | null>('value', { required: false });
+
+const attrs: SelectProps = useAttrs();
+
+const { loading: postLoading, startLoading: startPostLoading, endLoading: endPostLoading } = useLoading();
+
+/** the enabled post options */
+const postOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
+
+watch(
+ () => props.deptId,
+ () => {
+ if (!props.deptId) {
+ postOptions.value = [];
+ return;
+ }
+ getPostOptions();
+ },
+ { immediate: true }
+);
+
+async function getPostOptions() {
+ startPostLoading();
+ const { error, data } = await fetchGetPostSelect(props.deptId!);
+
+ if (!error) {
+ postOptions.value = data.map(item => ({
+ label: item.postName,
+ value: item.postId
+ }));
+ }
+ endPostLoading();
+}
+</script>
+
+<template>
+ <NSelect
+ v-model:value="value"
+ :loading="postLoading"
+ :options="postOptions"
+ v-bind="attrs"
+ placeholder="璇烽�夋嫨宀椾綅"
+ />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/role-select.vue b/ruoyi-plus-soybean/src/components/custom/role-select.vue
new file mode 100755
index 0000000..00872d7
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/role-select.vue
@@ -0,0 +1,52 @@
+<script setup lang="ts">
+import { ref, useAttrs } from 'vue';
+import type { SelectProps } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { fetchGetRoleSelect } from '@/service/api/system';
+
+defineOptions({
+ name: 'RoleSelect'
+});
+
+interface Props {
+ [key: string]: any;
+}
+
+defineProps<Props>();
+
+const value = defineModel<CommonType.IdType[] | null>('value', { required: false });
+
+const attrs: SelectProps = useAttrs();
+
+const { loading: roleLoading, startLoading: startRoleLoading, endLoading: endRoleLoading } = useLoading();
+
+/** the enabled role options */
+const roleOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
+
+async function getRoleOptions() {
+ startRoleLoading();
+ const { error, data } = await fetchGetRoleSelect();
+
+ if (!error) {
+ roleOptions.value = data.map(item => ({
+ label: item.roleName,
+ value: item.roleId
+ }));
+ }
+ endRoleLoading();
+}
+
+getRoleOptions();
+</script>
+
+<template>
+ <NSelect
+ v-model:value="value"
+ :loading="roleLoading"
+ :options="roleOptions"
+ v-bind="attrs"
+ placeholder="璇烽�夋嫨瑙掕壊"
+ />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/soybean-avatar.vue b/ruoyi-plus-soybean/src/components/custom/soybean-avatar.vue
new file mode 100755
index 0000000..8d3278a
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/soybean-avatar.vue
@@ -0,0 +1,13 @@
+<script setup lang="ts">
+defineOptions({
+ name: 'SoybeanAvatar'
+});
+</script>
+
+<template>
+ <div class="size-72px overflow-hidden rd-1/2">
+ <img src="@/assets/imgs/soybean.jpg" class="size-full" />
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/status-switch.vue b/ruoyi-plus-soybean/src/components/custom/status-switch.vue
new file mode 100755
index 0000000..d2856c4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/status-switch.vue
@@ -0,0 +1,61 @@
+<script setup lang="ts">
+import { useBoolean } from '@sa/hooks';
+import { enableStatusRecord } from '@/constants/business';
+
+defineOptions({
+ name: 'StatusSwitch'
+});
+
+interface Props {
+ disabled?: boolean;
+ info?: string;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ disabled: false,
+ info: ''
+});
+
+const value = defineModel<Api.Common.EnableStatus>('value', { default: '0' });
+
+interface Emits {
+ (e: 'submitted', value: Api.Common.EnableStatus, callback: (flag: boolean) => void): void;
+}
+
+const emit = defineEmits<Emits>();
+
+/** 鐘舵�佸垏鎹㈣繃绋嬬殑 loading 鐘舵�� */
+const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean();
+
+const handleUpdateValue = (val: Api.Common.EnableStatus) => {
+ value.value = val === '0' ? '1' : '0';
+ window.$dialog?.warning({
+ title: '绯荤粺鎻愮ず',
+ content: `纭畾瑕�${enableStatusRecord[val]} ${props.info} 鍚楋紵`,
+ positiveText: '纭畾',
+ negativeText: '鍙栨秷',
+ onPositiveClick: () => {
+ startLoading();
+ emit('submitted', val, flag => {
+ if (flag) value.value = val;
+ endLoading();
+ });
+ },
+ onNegativeClick: () => {}
+ });
+};
+</script>
+
+<template>
+ <NSwitch
+ v-model:value="value"
+ :loading="loading"
+ :rubber-band="false"
+ checked-value="0"
+ unchecked-value="1"
+ :disabled="props.disabled"
+ @update:value="handleUpdateValue"
+ />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/svg-icon.vue b/ruoyi-plus-soybean/src/components/custom/svg-icon.vue
new file mode 100755
index 0000000..504763e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/svg-icon.vue
@@ -0,0 +1,54 @@
+<script setup lang="ts">
+import { computed, useAttrs } from 'vue';
+import { Icon } from '@iconify/vue';
+
+defineOptions({ name: 'SvgIcon', inheritAttrs: false });
+
+/**
+ * Props
+ *
+ * - Support iconify and local svg icon
+ * - If icon and localIcon are passed at the same time, localIcon will be rendered first
+ */
+interface Props {
+ /** Iconify icon name */
+ icon?: string;
+ /** Local svg icon name */
+ localIcon?: string;
+}
+
+const props = defineProps<Props>();
+
+const attrs = useAttrs();
+
+const bindAttrs = computed<{ class: string; style: string }>(() => ({
+ class: (attrs.class as string) || '',
+ style: (attrs.style as string) || ''
+}));
+
+const symbolId = computed(() => {
+ const { VITE_ICON_LOCAL_PREFIX: prefix } = import.meta.env;
+
+ const defaultLocalIcon = 'no-icon';
+
+ const icon = props.localIcon || defaultLocalIcon;
+
+ return `#${prefix}-${icon}`;
+});
+
+/** If localIcon is passed, render localIcon first */
+const renderLocalIcon = computed(() => props.localIcon || !props.icon);
+</script>
+
+<template>
+ <template v-if="renderLocalIcon">
+ <svg aria-hidden="true" width="1em" height="1em" v-bind="bindAttrs">
+ <use :xlink:href="symbolId" fill="currentColor" />
+ </svg>
+ </template>
+ <template v-else>
+ <Icon v-if="icon" :icon="icon" v-bind="bindAttrs" />
+ </template>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/tenant-select.vue b/ruoyi-plus-soybean/src/components/custom/tenant-select.vue
new file mode 100755
index 0000000..de90188
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/tenant-select.vue
@@ -0,0 +1,103 @@
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue';
+import type { SelectOption } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { fetchTenantList } from '@/service/api';
+import { fetchChangeTenant, fetchClearTenant } from '@/service/api/system/tenant';
+import { useAppStore } from '@/store/modules/app';
+import { useTabStore } from '@/store/modules/tab';
+import { useAuthStore } from '@/store/modules/auth';
+import { useRouterPush } from '@/hooks/common/router';
+
+defineOptions({ name: 'TenantSelect' });
+
+interface Props {
+ clearable?: boolean;
+}
+
+withDefaults(defineProps<Props>(), {
+ clearable: false
+});
+
+const appStore = useAppStore();
+const { userInfo } = useAuthStore();
+const { clearTabs } = useTabStore();
+const { toHome } = useRouterPush();
+
+const tenantId = defineModel<CommonType.IdType>('tenantId', { required: false, default: undefined });
+const enabled = defineModel<boolean>('enabled', { required: false, default: false });
+
+const lastSelected = ref<CommonType.IdType>();
+
+const tenantOption = ref<SelectOption[]>([]);
+const { loading, startLoading, endLoading } = useLoading();
+
+const showTenantSelect = computed<boolean>(() => {
+ return userInfo.user?.userId === 1 && enabled.value;
+});
+
+/**
+ * 鍏抽棴褰撳墠椤甸潰骞跺埛鏂�
+ *
+ * @param msg 鎻愮ず淇℃伅
+ * @param val 绉熸埛ID
+ */
+async function closeAndRefresh(msg: string, val: CommonType.IdType = '') {
+ lastSelected.value = val;
+ window.$message?.success(msg);
+ clearTabs([], true);
+ toHome();
+ appStore.reloadPage(500);
+}
+
+async function handleChangeTenant(_tenantId: CommonType.IdType) {
+ if (!_tenantId) {
+ return;
+ }
+ if (lastSelected.value === _tenantId) {
+ return;
+ }
+ await fetchChangeTenant(_tenantId);
+ closeAndRefresh('鍒囨崲绉熸埛鎴愬姛', _tenantId);
+}
+
+async function handleClearTenant() {
+ await fetchClearTenant();
+ closeAndRefresh('鍒囨崲涓洪粯璁ょ鎴�');
+}
+
+async function handleFetchTenantList() {
+ startLoading();
+ const { data, error } = await fetchTenantList();
+ if (error) return;
+ enabled.value = data.tenantEnabled;
+ if (data.tenantEnabled) {
+ tenantOption.value = data.voList.map(tenant => {
+ return {
+ label: tenant.companyName,
+ value: tenant.tenantId
+ };
+ });
+ }
+ endLoading();
+}
+onMounted(async () => {
+ if (userInfo.user?.userId !== 1) {
+ return;
+ }
+ await handleFetchTenantList();
+});
+</script>
+
+<template>
+ <NSelect
+ v-if="showTenantSelect"
+ v-model:value="tenantId"
+ :clearable="clearable"
+ placeholder="璇烽�夋嫨绉熸埛"
+ :options="tenantOption"
+ :loading="loading"
+ @update:value="handleChangeTenant"
+ @clear="handleClearTenant"
+ />
+</template>
diff --git a/ruoyi-plus-soybean/src/components/custom/umo-doc-editor.vue b/ruoyi-plus-soybean/src/components/custom/umo-doc-editor.vue
new file mode 100755
index 0000000..10b85e4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/umo-doc-editor.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import { nextTick, ref, useAttrs, watch } from 'vue';
+import type { UmoEditorOptions } from '@umoteam/editor';
+import { UmoEditor } from '@umoteam/editor';
+import { fetchBatchDeleteOss, fetchUploadFile } from '@/service/api/system/oss';
+import { useAppStore } from '@/store/modules/app';
+import { useThemeStore } from '@/store/modules/theme';
+
+defineOptions({
+ name: 'UmoDocEditor'
+});
+
+const attrs: UmoEditorOptions = useAttrs();
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+const umoEditorRef = ref<InstanceType<typeof UmoEditor>>();
+const isSave = ref(false);
+
+const value = defineModel<string>('value', { required: true, default: '' });
+
+watch(
+ value,
+ () => {
+ nextTick(() => {
+ if (isSave.value) {
+ isSave.value = false;
+ return;
+ }
+ umoEditorRef.value?.setContent(value.value);
+ });
+ },
+ {
+ immediate: true
+ }
+);
+
+watch(
+ () => appStore.locale,
+ () => {
+ umoEditorRef.value?.setLocale(appStore.locale);
+ }
+);
+
+async function handleSave(content: { html: string }) {
+ isSave.value = true;
+ value.value = content.html;
+ return true;
+}
+
+async function handleFileUpload(file: File) {
+ const { error, data } = await fetchUploadFile(file);
+ if (error) throw new Error(error.message || '涓婁紶澶辫触');
+
+ return {
+ id: data.ossId,
+ url: data.url
+ };
+}
+
+function handleFileDelete(id: CommonType.IdType) {
+ window.$dialog?.warning({
+ title: '纭鍒犻櫎鏂囦欢锛�',
+ content: '鏂囦欢鍒犻櫎鍚庝笉鍙仮澶嶏紝璇风‘璁ゆ槸鍚﹀垹闄わ紒',
+ positiveText: '纭鍒犻櫎',
+ negativeText: '鍙栨秷',
+ onPositiveClick: async () => {
+ const { error } = await fetchBatchDeleteOss([id]);
+ if (error) throw new Error(error.message || '鏂囦欢鍒犻櫎澶辫触');
+ }
+ });
+ return true;
+}
+
+defineExpose({
+ saveContent: () => umoEditorRef.value?.saveContent()
+});
+</script>
+
+<template>
+ <div class="umo-editor size-full">
+ <UmoEditor
+ v-bind="attrs"
+ ref="umoEditorRef"
+ :theme="themeStore.darkMode ? 'dark' : 'light'"
+ @save="handleSave"
+ @file-upload="handleFileUpload"
+ @file-delete="handleFileDelete"
+ />
+ </div>
+</template>
+
+<style>
+body .flex-center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.umo-editor .flex-center {
+ display: inherit !important;
+ align-items: inherit !important;
+ justify-content: inherit !important;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/components/custom/user-select.vue b/ruoyi-plus-soybean/src/components/custom/user-select.vue
new file mode 100755
index 0000000..91dba79
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/user-select.vue
@@ -0,0 +1,52 @@
+<script setup lang="ts">
+import { ref, useAttrs } from 'vue';
+import type { SelectProps } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { fetchGetUserSelect } from '@/service/api/system';
+
+defineOptions({
+ name: 'UserSelect'
+});
+
+interface Props {
+ [key: string]: any;
+}
+
+defineProps<Props>();
+
+const value = defineModel<CommonType.IdType | null>('value', { required: false });
+
+const attrs: SelectProps = useAttrs();
+
+const { loading: userLoading, startLoading: startUserLoading, endLoading: endUserLoading } = useLoading();
+
+/** the enabled role options */
+const userOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
+
+async function getUserOptions() {
+ startUserLoading();
+ const { error, data } = await fetchGetUserSelect();
+
+ if (!error) {
+ userOptions.value = data.map(item => ({
+ label: `${item.nickName} ( ${item.userName} )`,
+ value: item.userId
+ }));
+ }
+ endUserLoading();
+}
+
+getUserOptions();
+</script>
+
+<template>
+ <NSelect
+ v-model:value="value"
+ :loading="userLoading"
+ :options="userOptions"
+ v-bind="attrs"
+ placeholder="璇烽�夋嫨鐢ㄦ埛"
+ />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/components/custom/wave-bg.vue b/ruoyi-plus-soybean/src/components/custom/wave-bg.vue
new file mode 100755
index 0000000..bc58438
--- /dev/null
+++ b/ruoyi-plus-soybean/src/components/custom/wave-bg.vue
@@ -0,0 +1,524 @@
+<!-- Copyright By https://github.com/Daymychen/art-design-pro/blob/main/src/components/core/views/login/LoginLeftView.vue -->
+<script lang="ts" setup>
+import { useThemeStore } from '@/store/modules/theme';
+
+defineOptions({ name: 'WaveBg' });
+
+const themeStore = useThemeStore();
+
+function toggleThemeScheme() {
+ if (themeStore.darkMode) {
+ themeStore.setThemeScheme('light');
+ return;
+ }
+ themeStore.setThemeScheme('dark');
+}
+</script>
+
+<template>
+ <div class="wave-bg">
+ <!-- 鍑犱綍瑁呴グ鍏冪礌 -->
+ <div class="geometric-decorations">
+ <!-- 鍩虹鍑犱綍褰㈢姸 -->
+ <div class="geo-element circle-outline animate-fade-in-up animate-delay-0s"></div>
+ <div class="geo-element square-rotated animate-fade-in-left animate-delay-0s"></div>
+ <div class="geo-element circle-small animate-fade-in-up animate-delay-0.3s"></div>
+
+ <div class="geo-element square-bottom-right animate-fade-in-right animate-delay-0s"></div>
+
+ <!-- 鑳屾櫙娉℃场 -->
+ <div class="geo-element bg-bubble animate-scale-in animate-delay-0.5s"></div>
+
+ <!-- 澶槼/鏈堜寒 -->
+ <div
+ class="geo-element circle-top-right animate-fade-in-down animate-delay-0.5s"
+ @click="toggleThemeScheme"
+ ></div>
+
+ <!-- 瑁呴グ鐐� -->
+ <div class="geo-element dot dot-top-left animate-bounce-in animate-delay-0s"></div>
+ <div class="geo-element dot dot-top-right animate-bounce-in animate-delay-0s"></div>
+ <div class="geo-element dot dot-center-right animate-bounce-in animate-delay-0s"></div>
+
+ <!-- 鍙犲姞鏂瑰潡缁� -->
+ <div class="squares-group">
+ <i class="geo-element square square-blue animate-fade-in-left-rotated-blue animate-delay-0.2s"></i>
+ <i class="geo-element square square-pink animate-fade-in-left-rotated-pink animate-delay-0.4s"></i>
+ <i class="geo-element square square-purple animate-fade-in-left-no-rotation animate-delay-0.6s"></i>
+ </div>
+ </div>
+ </div>
+</template>
+
+<style lang="scss" scoped>
+// 棰滆壊鍙橀噺瀹氫箟
+$primary-light-7: rgb(var(--primary-50-color));
+$primary-light-8: rgb(var(--primary-100-color));
+$primary-light-9: rgb(var(--primary-200-color));
+$primary-base: rgb(var(--primary-color));
+$main-bg: rgb(var(--primary-50-color));
+
+// 娣峰悎棰滆壊鍑芥暟
+$bg-mix-light-9: color-mix(in srgb, $primary-light-9 100%, $main-bg);
+$bg-mix-light-8: color-mix(in srgb, $primary-light-8 80%, $main-bg);
+$bg-mix-light-7: color-mix(in srgb, $primary-light-7 80%, $main-bg);
+
+.wave-bg {
+ .geometric-decorations {
+ .geo-element {
+ position: absolute;
+ opacity: 0;
+ animation-fill-mode: forwards;
+ animation-duration: 0.8s;
+ animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
+ }
+
+ // 鍔ㄧ敾 mixin
+ @mixin fadeAnimation($direction: '', $rotation: 0deg) {
+ from {
+ opacity: 0;
+
+ @if $direction == 'up' {
+ transform: translateY(30px) rotate($rotation);
+ } @else if $direction == 'down' {
+ transform: translateY(-30px) rotate($rotation);
+ } @else if $direction == 'left' {
+ transform: translateX(-30px) rotate($rotation);
+ } @else if $direction == 'right' {
+ transform: translateX(30px) rotate($rotation);
+ }
+ }
+
+ to {
+ opacity: 1;
+
+ @if $direction == 'up' or $direction == 'down' {
+ transform: translateY(0) rotate($rotation);
+ } @else {
+ transform: translateX(0) rotate($rotation);
+ }
+ }
+ }
+
+ // 鍔ㄧ敾瀹氫箟
+ @keyframes fadeInUp {
+ @include fadeAnimation('up');
+ }
+
+ @keyframes fadeInDown {
+ @include fadeAnimation('down');
+ }
+
+ @keyframes fadeInLeft {
+ @include fadeAnimation('left');
+ }
+
+ @keyframes fadeInLeftRotated {
+ @include fadeAnimation('left', -25deg);
+ }
+
+ @keyframes fadeInRight {
+ @include fadeAnimation('right');
+ }
+
+ @keyframes fadeInRightRotated {
+ @include fadeAnimation('right', 45deg);
+ }
+
+ @keyframes fadeInLeftRotatedBlue {
+ @include fadeAnimation('left', -10deg);
+ }
+
+ @keyframes fadeInLeftRotatedPink {
+ @include fadeAnimation('left', 10deg);
+ }
+
+ @keyframes fadeInLeftNoRotation {
+ @include fadeAnimation('left');
+ }
+
+ @keyframes scaleIn {
+ from {
+ opacity: 0;
+ transform: scale(0.8);
+ }
+
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+
+ @keyframes bounceIn {
+ 0% {
+ opacity: 0;
+ transform: scale(0.3);
+ }
+
+ 50% {
+ opacity: 1;
+ transform: scale(1.05);
+ }
+
+ 70% {
+ transform: scale(0.9);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+
+ @keyframes lineGrow {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+ }
+
+ @keyframes slideInLeft {
+ from {
+ opacity: 0;
+ transform: translateX(-30px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+ }
+
+ // 鍔ㄧ敾绫�
+ .animate-fade-in-up {
+ animation-name: fadeInUp;
+ }
+
+ .animate-fade-in-down {
+ animation-name: fadeInDown;
+ }
+
+ .animate-fade-in-left {
+ animation-name: fadeInLeft;
+ }
+
+ .animate-fade-in-right {
+ animation-name: fadeInRight;
+ }
+
+ .animate-scale-in {
+ animation-name: scaleIn;
+ animation-duration: 1.2s;
+ }
+
+ .animate-bounce-in {
+ animation-name: bounceIn;
+ animation-duration: 0.6s;
+ }
+
+ .animate-fade-in-left-rotated-blue {
+ animation-name: fadeInLeftRotatedBlue;
+ }
+
+ .animate-fade-in-left-rotated-pink {
+ animation-name: fadeInLeftRotatedPink;
+ }
+
+ .animate-fade-in-left-no-rotation {
+ animation-name: fadeInLeftNoRotation;
+ }
+
+ // 鍩虹鍑犱綍褰㈢姸
+ .circle-outline {
+ top: 10%;
+ left: 25%;
+ width: 42px;
+ height: 42px;
+ border: 2px solid $primary-light-8;
+ border-radius: 50%;
+ }
+
+ .square-rotated {
+ top: 50%;
+ left: 16%;
+ width: 60px;
+ height: 60px;
+ background-color: $bg-mix-light-8;
+
+ &.animate-fade-in-left {
+ animation-name: fadeInLeftRotated;
+ }
+ }
+
+ .circle-small {
+ bottom: 26%;
+ left: 30%;
+ width: 18px;
+ height: 18px;
+ background-color: $primary-light-8;
+ border-radius: 50%;
+ }
+
+ // 澶槼/鏈堜寒鏁堟灉
+ .circle-top-right {
+ top: 3%;
+ right: 3%;
+ z-index: 100;
+ width: 50px;
+ height: 50px;
+ cursor: pointer;
+ background: $bg-mix-light-7;
+ border-radius: 50%;
+ transition: all 0.3s;
+
+ &::after {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 100%;
+ height: 100%;
+ content: '';
+ background: linear-gradient(to right, #fcbb04, #fffc00);
+ border-radius: 50%;
+ opacity: 0;
+ transition: all 0.5s;
+ transform: translate(-50%, -50%);
+ }
+
+ &:hover {
+ box-shadow: 0 0 36px #fffc00;
+
+ &::after {
+ opacity: 1;
+ }
+ }
+ }
+
+ .square-bottom-right {
+ right: 10%;
+ bottom: 10%;
+ width: 50px;
+ height: 50px;
+ background-color: $primary-light-8;
+
+ &.animate-fade-in-right {
+ animation-name: fadeInRightRotated;
+ }
+ }
+
+ // 鑳屾櫙娉℃场
+ .bg-bubble {
+ top: -120px;
+ right: -120px;
+ width: 360px;
+ height: 360px;
+ background-color: $bg-mix-light-8;
+ border-radius: 50%;
+ }
+
+ // 瑁呴グ鐐�
+ .dot {
+ width: 14px;
+ height: 14px;
+ background-color: $primary-light-7;
+ border-radius: 50%;
+
+ &.dot-top-left {
+ top: 140px;
+ left: 100px;
+ }
+
+ &.dot-top-right {
+ top: 140px;
+ right: 120px;
+ }
+
+ &.dot-center-right {
+ top: 46%;
+ right: 22%;
+ background-color: $primary-light-8;
+ }
+ }
+
+ // 鍙犲姞鏂瑰潡缁�
+ .squares-group {
+ position: absolute;
+ bottom: 18px;
+ left: 20px;
+ width: 140px;
+ height: 140px;
+ pointer-events: none;
+
+ .square {
+ position: absolute;
+ display: block;
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgb(64 87 167 / 12%);
+
+ &.square-blue {
+ top: 12px;
+ left: 30px;
+ z-index: 2;
+ width: 50px;
+ height: 50px;
+ background-color: rgb(from $primary-base r g b / 30%);
+ }
+
+ &.square-pink {
+ top: 30px;
+ left: 48px;
+ z-index: 1;
+ width: 70px;
+ height: 70px;
+ background-color: rgb(from $primary-base r g b / 15%);
+ }
+
+ &.square-purple {
+ top: 66px;
+ left: 86px;
+ z-index: 3;
+ width: 32px;
+ height: 32px;
+ background-color: rgb(from $primary-base r g b / 45%);
+ }
+ }
+
+ // 瑁呴グ绾挎潯
+ &::after {
+ position: absolute;
+ top: 86px;
+ left: 72px;
+ width: 80px;
+ height: 1px;
+ content: '';
+ background: linear-gradient(90deg, var(--el-color-primary-light-6), transparent);
+ opacity: 0;
+ transform: rotate(50deg);
+ animation: lineGrow 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
+ animation-delay: 1.2s;
+ }
+ }
+ }
+
+ @media only screen and (max-width: 1600px) {
+ width: 60vw;
+
+ .text-wrap {
+ bottom: 40px;
+ }
+ }
+
+ @media only screen and (max-width: 1280px) {
+ width: auto;
+ height: auto;
+ padding: 0;
+ // 闅愯棌鑳屾櫙鍜屽叾浠栧唴瀹癸紝鍙繚鐣� logo
+ background: transparent;
+
+ .left-img,
+ .text-wrap,
+ .geometric-decorations {
+ display: none;
+ }
+
+ .logo {
+ position: fixed;
+ top: 15px;
+ left: 25px;
+ z-index: 1000;
+ }
+ }
+}
+
+// 鏆楄壊涓婚
+.dark .wave-bg {
+ background-color: color-mix(in srgb, $primary-light-9 60%, #070707);
+
+ @media only screen and (max-width: 1280px) {
+ background: transparent;
+ }
+
+ .geometric-decorations {
+ // 鏈堜寒鏁堟灉
+ .circle-top-right {
+ background-color: $bg-mix-light-8;
+ box-shadow: 0 0 25px #333 inset;
+ transition: all 0.3s ease-in-out 0.1s;
+ rotate: -48deg;
+
+ &::before {
+ position: absolute;
+ top: 0;
+ left: 15px;
+ width: 50px;
+ height: 50px;
+ content: '';
+ background-color: $bg-mix-light-9;
+ border-radius: 50%;
+ transition: all 0.3s ease-in-out;
+ }
+
+ &:hover {
+ background-color: transparent;
+ box-shadow: 0 40px 25px #ddd inset;
+
+ &::before {
+ left: 18px;
+ }
+
+ &::after {
+ opacity: 0;
+ }
+ }
+ }
+
+ .bg-bubble {
+ background-color: $bg-mix-light-9;
+ }
+
+ // 鍏朵粬鍏冪礌棰滆壊璋冩暣
+ .square-rotated {
+ background-color: $bg-mix-light-9;
+ }
+
+ .circle-small,
+ .dot {
+ background-color: $primary-light-8;
+ }
+
+ .square-bottom-right {
+ background-color: $primary-light-9;
+ }
+
+ .dot.dot-top-right {
+ background-color: $primary-light-8;
+ }
+ }
+
+ // 鏂瑰潡缁勬殫鑹茶皟鏁�
+ .squares-group {
+ .square {
+ box-shadow: none;
+
+ &.square-blue {
+ background-color: rgb(from $primary-base r g b / 18%);
+ }
+
+ &.square-pink {
+ background-color: rgb(from $primary-base r g b / 10%);
+ }
+
+ &.square-purple {
+ background-color: rgb(from $primary-base r g b / 20%);
+ }
+ }
+
+ &::after {
+ background: linear-gradient(90deg, $primary-light-8, transparent);
+ }
+ }
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/constants/app.ts b/ruoyi-plus-soybean/src/constants/app.ts
new file mode 100755
index 0000000..a607e61
--- /dev/null
+++ b/ruoyi-plus-soybean/src/constants/app.ts
@@ -0,0 +1,79 @@
+import { transformRecordToOption } from '@/utils/common';
+
+export const GLOBAL_HEADER_MENU_ID = '__GLOBAL_HEADER_MENU__';
+
+export const GLOBAL_SIDER_MENU_ID = '__GLOBAL_SIDER_MENU__';
+
+export const themeSchemaRecord: Record<UnionKey.ThemeScheme, App.I18n.I18nKey> = {
+ light: 'theme.appearance.themeSchema.light',
+ dark: 'theme.appearance.themeSchema.dark',
+ auto: 'theme.appearance.themeSchema.auto'
+};
+
+export const themeSchemaOptions = transformRecordToOption(themeSchemaRecord);
+
+export const loginModuleRecord: Record<UnionKey.LoginModule, App.I18n.I18nKey> = {
+ 'pwd-login': 'page.login.pwdLogin.title',
+ 'code-login': 'page.login.codeLogin.title',
+ register: 'page.login.register.title',
+ 'reset-pwd': 'page.login.resetPwd.title',
+ 'bind-wechat': 'page.login.bindWeChat.title'
+};
+
+export const themeLayoutModeRecord: Record<UnionKey.ThemeLayoutMode, App.I18n.I18nKey> = {
+ vertical: 'theme.layout.layoutMode.vertical',
+ 'vertical-mix': 'theme.layout.layoutMode.vertical-mix',
+ 'vertical-hybrid-header-first': 'theme.layout.layoutMode.vertical-hybrid-header-first',
+ horizontal: 'theme.layout.layoutMode.horizontal',
+ 'top-hybrid-sidebar-first': 'theme.layout.layoutMode.top-hybrid-sidebar-first',
+ 'top-hybrid-header-first': 'theme.layout.layoutMode.top-hybrid-header-first'
+};
+
+export const themeLayoutModeOptions = transformRecordToOption(themeLayoutModeRecord);
+
+export const themeScrollModeRecord: Record<UnionKey.ThemeScrollMode, App.I18n.I18nKey> = {
+ wrapper: 'theme.layout.content.scrollMode.wrapper',
+ content: 'theme.layout.content.scrollMode.content'
+};
+
+export const themeScrollModeOptions = transformRecordToOption(themeScrollModeRecord);
+
+export const themeTabModeRecord: Record<UnionKey.ThemeTabMode, App.I18n.I18nKey> = {
+ chrome: 'theme.layout.tab.mode.chrome',
+ button: 'theme.layout.tab.mode.button',
+ slider: 'theme.layout.tab.mode.slider'
+};
+
+export const themeTabModeOptions = transformRecordToOption(themeTabModeRecord);
+
+export const themePageAnimationModeRecord: Record<UnionKey.ThemePageAnimateMode, App.I18n.I18nKey> = {
+ 'fade-slide': 'theme.layout.content.page.mode.fade-slide',
+ fade: 'theme.layout.content.page.mode.fade',
+ 'fade-bottom': 'theme.layout.content.page.mode.fade-bottom',
+ 'fade-scale': 'theme.layout.content.page.mode.fade-scale',
+ 'zoom-fade': 'theme.layout.content.page.mode.zoom-fade',
+ 'zoom-out': 'theme.layout.content.page.mode.zoom-out',
+ none: 'theme.layout.content.page.mode.none'
+};
+
+export const themePageAnimationModeOptions = transformRecordToOption(themePageAnimationModeRecord);
+
+export const DARK_CLASS = 'dark';
+
+export const watermarkTimeFormatOptions = [
+ { label: 'YYYY-MM-DD HH:mm', value: 'YYYY-MM-DD HH:mm' },
+ { label: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss' },
+ { label: 'YYYY/MM/DD HH:mm', value: 'YYYY/MM/DD HH:mm' },
+ { label: 'YYYY/MM/DD HH:mm:ss', value: 'YYYY/MM/DD HH:mm:ss' },
+ { label: 'HH:mm', value: 'HH:mm' },
+ { label: 'HH:mm:ss', value: 'HH:mm:ss' },
+ { label: 'MM-DD HH:mm', value: 'MM-DD HH:mm' }
+];
+
+export const themeTableSizeRecord: Record<UnionKey.ThemeTableSize, App.I18n.I18nKey> = {
+ small: 'theme.table.size.small',
+ medium: 'theme.table.size.medium',
+ large: 'theme.table.size.large'
+};
+
+export const themeTableSizeOptions = transformRecordToOption(themeTableSizeRecord);
diff --git a/ruoyi-plus-soybean/src/constants/business.ts b/ruoyi-plus-soybean/src/constants/business.ts
new file mode 100755
index 0000000..6c3a709
--- /dev/null
+++ b/ruoyi-plus-soybean/src/constants/business.ts
@@ -0,0 +1,138 @@
+import { transformRecordToOption } from '@/utils/common';
+
+/** enable status */
+export const enableStatusRecord: Record<Api.Common.EnableStatus, string> = {
+ '0': '姝e父',
+ '1': '鍋滅敤'
+};
+
+export const enableStatusOptions = transformRecordToOption(enableStatusRecord);
+
+/** yes or no status */
+export const yesOrNoStatusRecord: Record<Api.Common.YesOrNoStatus, string> = {
+ Y: '鏄�',
+ N: '鍚�'
+};
+
+export const yesOrNoStatusOptions = transformRecordToOption(yesOrNoStatusRecord);
+
+/** menu type */
+export const menuTypeRecord: Record<Api.System.MenuType, string> = {
+ M: '鐩綍',
+ C: '鑿滃崟',
+ F: '鎸夐挳'
+};
+
+export const menuTypeOptions = transformRecordToOption(menuTypeRecord);
+
+/** menu is frame */
+export const menuIsFrameRecord: Record<Api.System.IsMenuFrame, string> = {
+ '0': '鏄�',
+ '1': '鍚�',
+ '2': 'iframe'
+};
+
+export const menuIsFrameOptions = transformRecordToOption(menuIsFrameRecord);
+
+/** menu icon type */
+export const menuIconTypeRecord: Record<Api.System.IconType, string> = {
+ '1': 'iconify',
+ '2': '鏈湴鍥炬爣'
+};
+
+export const menuIconTypeOptions = transformRecordToOption(menuIconTypeRecord);
+
+/** menu layout */
+export const menuLayoutRecord: Record<Api.System.MenuLayout, string> = {
+ '0': '榛樿甯冨眬',
+ '1': '绌虹櫧甯冨眬'
+};
+
+export const menuLayoutOptions = transformRecordToOption(menuLayoutRecord);
+
+/** gen java type */
+export const genJavaTypeRecord: Record<Api.Tool.JavaType, string> = {
+ Long: 'Long',
+ String: 'String',
+ Integer: 'Integer',
+ Double: 'Double',
+ BigDecimal: 'BigDecimal',
+ Date: 'Date',
+ Boolean: 'Boolean'
+};
+
+export const genJavaTypeOptions = transformRecordToOption(genJavaTypeRecord);
+
+/** gen query type */
+export const genQueryTypeRecord: Record<Api.Tool.QueryType, string> = {
+ EQ: '=',
+ NE: '!=',
+ GT: '>',
+ GE: '>=',
+ LT: '<',
+ LE: '<=',
+ LIKE: 'LIKE',
+ BETWEEN: 'BETWEEN'
+};
+
+export const genQueryTypeOptions = transformRecordToOption(genQueryTypeRecord);
+
+/** gen html type */
+export const genHtmlTypeRecord: Record<Api.Tool.HtmlType, string> = {
+ input: '鏂囨湰妗�',
+ textarea: '鏂囨湰鍩�',
+ select: '涓嬫媺妗�',
+ radio: '鍗曢�夋',
+ checkbox: '澶嶉�夋',
+ datetime: '鏃ユ湡鏃堕棿鎺т欢',
+ imageUpload: '鍥剧墖涓婁紶',
+ fileUpload: '鏂囦欢涓婁紶',
+ editor: '瀵屾枃鏈帶浠�'
+};
+
+export const genHtmlTypeOptions = transformRecordToOption(genHtmlTypeRecord);
+
+/** gen type */
+export const genTypeRecord: Record<Api.Tool.GenType, string> = {
+ '0': 'ZIP 鍘嬬缉鍖�',
+ '1': '鑷畾涔夎矾寰�'
+};
+
+export const genTypeOptions = transformRecordToOption(genTypeRecord);
+
+/** gen type */
+export const genTplCategoryRecord: Record<Api.Tool.TplCategory, string> = {
+ crud: '鍗曡〃锛堝鍒犳敼鏌ワ級',
+ tree: '鏍戣〃锛堝鍒犳敼鏌ワ級'
+};
+
+export const genTplCategoryOptions = transformRecordToOption(genTplCategoryRecord);
+
+/** oss config is https */
+export const ossConfigIsHttpsRecord: Record<Api.Common.YesOrNoStatus, string> = {
+ Y: 'https://',
+ N: 'http://'
+};
+
+export const ossConfigIsHttpsOptions = transformRecordToOption(ossConfigIsHttpsRecord);
+
+/** oss access policy */
+export const ossAccessPolicyRecord: Record<Api.System.OssAccessPolicy, string> = {
+ '0': '绉佹湁',
+ '1': '鍏湁',
+ '2': '鑷畾涔�'
+};
+
+export const ossAccessPolicyOptions = transformRecordToOption(ossAccessPolicyRecord);
+
+/** data scope */
+export const dataScopeRecord: Record<Api.System.DataScope, string> = {
+ '1': '鍏ㄩ儴鏁版嵁鏉冮檺',
+ '2': '鑷畾鏁版嵁鏉冮檺',
+ '3': '鏈儴闂ㄦ暟鎹潈闄�',
+ '4': '鏈儴闂ㄥ強浠ヤ笅鏁版嵁鏉冮檺',
+ '5': '浠呮湰浜烘暟鎹潈闄�',
+ '6': '閮ㄩ棬鍙婁互涓嬫垨鏈汉鏁版嵁鏉冮檺'
+};
+
+export const dataScopeOptions = transformRecordToOption(dataScopeRecord);
diff --git a/ruoyi-plus-soybean/src/constants/common.ts b/ruoyi-plus-soybean/src/constants/common.ts
new file mode 100755
index 0000000..f4a45b1
--- /dev/null
+++ b/ruoyi-plus-soybean/src/constants/common.ts
@@ -0,0 +1,15 @@
+import { transformRecordToOption } from '@/utils/common';
+
+export const yesOrNoRecord: Record<CommonType.YesOrNo, App.I18n.I18nKey> = {
+ Y: 'common.yesOrNo.yes',
+ N: 'common.yesOrNo.no'
+};
+
+export const yesOrNoOptions = transformRecordToOption(yesOrNoRecord);
+
+export const errorCodeRecord: Record<CommonType.ErrorCode, string> = {
+ '401': '璁よ瘉澶辫触锛屾棤娉曡闂郴缁熻祫婧�',
+ '403': '褰撳墠鎿嶄綔娌℃湁鏉冮檺',
+ '404': '璁块棶璧勬簮涓嶅瓨鍦�',
+ default: '绯荤粺鏈煡閿欒锛岃鍙嶉缁欑鐞嗗憳'
+};
diff --git a/ruoyi-plus-soybean/src/constants/reg.ts b/ruoyi-plus-soybean/src/constants/reg.ts
new file mode 100755
index 0000000..d8bc32b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/constants/reg.ts
@@ -0,0 +1,25 @@
+export const REG_USER_NAME = /^[\u4E00-\u9FA5a-zA-Z0-9_-]{4,16}$/;
+
+/** Phone reg */
+export const REG_PHONE =
+ /^[1](([3][0-9])|([4][01456789])|([5][012356789])|([6][2567])|([7][0-8])|([8][0-9])|([9][012356789]))[0-9]{8}$/;
+
+/**
+ * Password reg
+ *
+ * 6-18 characters, including letters, numbers, and underscores
+ */
+export const REG_PWD = /^(?![a-zA-Z]+$)(?!\d+$)(?![^\da-zA-Z\s]+$).{6,18}$/;
+
+/** Email reg */
+export const REG_EMAIL = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
+
+/** Six digit code reg */
+export const REG_CODE_SIX = /^\d{6}$/;
+
+/** Four digit code reg */
+export const REG_CODE_FOUR = /^\d{4}$/;
+
+/** Url reg */
+export const REG_URL =
+ /(((^https?:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
diff --git a/ruoyi-plus-soybean/src/enum/business.ts b/ruoyi-plus-soybean/src/enum/business.ts
new file mode 100755
index 0000000..e5b62ec
--- /dev/null
+++ b/ruoyi-plus-soybean/src/enum/business.ts
@@ -0,0 +1,4 @@
+export enum AcceptType {
+ Image = '.jpg,.jpeg,.png,.gif,.bmp,.webp',
+ File = '.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.pdf,.zip,.rar,.7z'
+}
diff --git a/ruoyi-plus-soybean/src/enum/index.ts b/ruoyi-plus-soybean/src/enum/index.ts
new file mode 100755
index 0000000..7b6c20a
--- /dev/null
+++ b/ruoyi-plus-soybean/src/enum/index.ts
@@ -0,0 +1,9 @@
+export enum SetupStoreId {
+ App = 'app-store',
+ Theme = 'theme-store',
+ Auth = 'auth-store',
+ Route = 'route-store',
+ Tab = 'tab-store',
+ Notice = 'notice-store',
+ Dict = 'dict-store'
+}
diff --git a/ruoyi-plus-soybean/src/hooks/business/auth.ts b/ruoyi-plus-soybean/src/hooks/business/auth.ts
new file mode 100755
index 0000000..633818b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/hooks/business/auth.ts
@@ -0,0 +1,46 @@
+import { useAuthStore } from '@/store/modules/auth';
+
+export function useAuth() {
+ const authStore = useAuthStore();
+
+ function hasAuth(codes: string | string[]) {
+ if (!authStore.isLogin) {
+ return false;
+ }
+
+ const { permissions } = authStore.userInfo;
+
+ // 瓒呯骇绠$悊鍛樻嫢鏈夋墍鏈夋潈闄�
+ if (permissions.includes('*:*:*')) {
+ return true;
+ }
+
+ // 灏嗗崟涓潈闄愯浆鎹负鏁扮粍缁熶竴澶勭悊
+ const codeList = Array.isArray(codes) ? codes : [codes];
+
+ return codeList.some(code => permissions.includes(code));
+ }
+
+ function hasRole(roleCodes: string | string[]) {
+ if (!authStore.isLogin) {
+ return false;
+ }
+
+ const { roles } = authStore.userInfo;
+
+ // 瓒呯骇绠$悊鍛樻嫢鏈夋墍鏈夎鑹叉潈闄�
+ if (roles.includes('superadmin') || roles.includes('admin')) {
+ return true;
+ }
+
+ // 灏嗗崟涓鑹茶浆鎹负鏁扮粍缁熶竴澶勭悊
+ const codeList = Array.isArray(roleCodes) ? roleCodes : [roleCodes];
+
+ return codeList.some(code => roles.includes(code));
+ }
+
+ return {
+ hasAuth,
+ hasRole
+ };
+}
diff --git a/ruoyi-plus-soybean/src/hooks/business/captcha.ts b/ruoyi-plus-soybean/src/hooks/business/captcha.ts
new file mode 100755
index 0000000..98124d9
--- /dev/null
+++ b/ruoyi-plus-soybean/src/hooks/business/captcha.ts
@@ -0,0 +1,71 @@
+import { computed } from 'vue';
+import { useCountDown, useLoading } from '@sa/hooks';
+import { REG_PHONE } from '@/constants/reg';
+import { $t } from '@/locales';
+
+export function useCaptcha() {
+ const { loading, startLoading, endLoading } = useLoading();
+ const { count, start, stop, isCounting } = useCountDown(10);
+
+ const label = computed(() => {
+ let text = $t('page.login.codeLogin.getCode');
+
+ const countingLabel = $t('page.login.codeLogin.reGetCode', { time: count.value });
+
+ if (loading.value) {
+ text = '';
+ }
+
+ if (isCounting.value) {
+ text = countingLabel;
+ }
+
+ return text;
+ });
+
+ function isPhoneValid(phone: string) {
+ if (phone.trim() === '') {
+ window.$message?.error?.($t('form.phone.required'));
+
+ return false;
+ }
+
+ if (!REG_PHONE.test(phone)) {
+ window.$message?.error?.($t('form.phone.invalid'));
+
+ return false;
+ }
+
+ return true;
+ }
+
+ async function getCaptcha(phone: string) {
+ const valid = isPhoneValid(phone);
+
+ if (!valid || loading.value) {
+ return;
+ }
+
+ startLoading();
+
+ // request
+ await new Promise(resolve => {
+ setTimeout(resolve, 500);
+ });
+
+ window.$message?.success?.($t('page.login.codeLogin.sendCodeSuccess'));
+
+ start();
+
+ endLoading();
+ }
+
+ return {
+ label,
+ start,
+ stop,
+ isCounting,
+ loading,
+ getCaptcha
+ };
+}
diff --git a/ruoyi-plus-soybean/src/hooks/business/dict.ts b/ruoyi-plus-soybean/src/hooks/business/dict.ts
new file mode 100755
index 0000000..03c0b49
--- /dev/null
+++ b/ruoyi-plus-soybean/src/hooks/business/dict.ts
@@ -0,0 +1,85 @@
+import { ref, watch } from 'vue';
+import { storeToRefs } from 'pinia';
+import { fetchGetDictDataByType } from '@/service/api/system';
+import { useDictStore } from '@/store/modules/dict';
+import { isNull } from '@/utils/common';
+import { $t } from '@/locales';
+
+export function useDict(dictType: string, immediate: boolean = true) {
+ const dictStore = useDictStore();
+ const { dictData: dictList } = storeToRefs(dictStore);
+
+ const data = ref<Api.System.DictData[]>([]);
+ const record = ref<Record<string, string>>({});
+ const options = ref<CommonType.Option[]>([]);
+
+ async function getData() {
+ const dicts = dictStore.getDict(dictType);
+ if (dicts) {
+ data.value = dicts;
+ return;
+ }
+ const { data: dictData, error } = await fetchGetDictDataByType(dictType);
+ if (error) return;
+ dictData.forEach(dict => {
+ if (dict.dictLabel?.startsWith(`dict.${dictType}.`)) {
+ dict.dictLabel = $t(dict.dictLabel as App.I18n.I18nKey);
+ }
+ });
+ dictStore.setDict(dictType, dictData);
+ data.value = dictData;
+ }
+
+ async function getRecord() {
+ if (!data.value.length) {
+ await getData();
+ }
+ data.value.forEach(dict => {
+ record.value[dict.dictValue!] = dict.dictLabel!;
+ });
+ }
+
+ async function getOptions() {
+ if (!data.value.length) {
+ await getData();
+ }
+
+ options.value = data.value.map(dict => ({ label: dict.dictLabel!, value: dict.dictValue! }));
+ }
+
+ function transformDictData(dictValue: string[] | number[] | string | number) {
+ if (!data.value.length || isNull(dictValue)) return undefined;
+ if (Array.isArray(dictValue)) {
+ return data.value.filter(dict => dictValue.some(value => dict.dictValue === value.toString()));
+ }
+ return data.value.filter(dict => dict.dictValue === dictValue.toString());
+ }
+
+ if (immediate) {
+ getData().then(() => {
+ getRecord();
+ getOptions();
+ });
+ } else {
+ watch(
+ () => dictList.value[dictType],
+ val => {
+ if (val && val.length) {
+ getRecord();
+ getOptions();
+ }
+ },
+ { immediate: true }
+ );
+ }
+
+ return {
+ data,
+ record,
+ options,
+ getData,
+ getRecord,
+ getOptions,
+ transformDictData
+ };
+}
diff --git a/ruoyi-plus-soybean/src/hooks/business/download.ts b/ruoyi-plus-soybean/src/hooks/business/download.ts
new file mode 100755
index 0000000..357680d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/hooks/business/download.ts
@@ -0,0 +1,166 @@
+import StreamSaver from 'streamsaver';
+import { errorCodeRecord } from '@/constants/common';
+import { localStg } from '@/utils/storage';
+import { getServiceBaseURL } from '@/utils/service';
+import { transformToURLSearchParams } from '@/utils/common';
+
+interface RequestConfig {
+ method: 'GET' | 'POST';
+ url: string;
+ params?: Record<string, any>;
+ filename?: string;
+ contentType?: string;
+}
+
+export function useDownload() {
+ const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
+ const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
+
+ const isHttps = () => {
+ const protocol = document.location.protocol;
+ const hostname = document.location.hostname;
+ return protocol === 'https' || hostname === 'localhost' || hostname === '127.0.0.1';
+ };
+
+ /** 鑾峰彇閫氱敤璇锋眰澶� */
+ const getCommonHeaders = (contentType = 'application/octet-stream') => ({
+ Authorization: `Bearer ${localStg.get('token')}`,
+ Clientid: import.meta.env.VITE_APP_CLIENT_ID!,
+ 'Content-Type': contentType
+ });
+
+ /** 閫氱敤涓嬭浇鏂规硶 */
+ function downloadByData(data: BlobPart, filename: string, type = 'application/octet-stream') {
+ const blob = new Blob([data], { type });
+ const blobURL = window.URL.createObjectURL(blob);
+
+ const tempLink = Object.assign(document.createElement('a'), {
+ style: { display: 'none' },
+ href: blobURL,
+ download: filename
+ });
+
+ if (typeof tempLink.download === 'undefined') {
+ tempLink.setAttribute('target', '_blank');
+ }
+
+ document.body.appendChild(tempLink);
+ tempLink.click();
+ document.body.removeChild(tempLink);
+ window.URL.revokeObjectURL(blobURL);
+ }
+
+ /** 娴佸紡涓嬭浇 */
+ async function downloadByStream(
+ readableStream: ReadableStream<Uint8Array>,
+ filename: string,
+ contentLength?: number
+ ): Promise<void> {
+ window.$loading?.endLoading();
+ StreamSaver.mitm = '/streamsaver/mitm.html?version=2.0.0';
+ const fileStream = StreamSaver.createWriteStream(filename, { size: contentLength });
+
+ if (window.WritableStream && readableStream?.pipeTo) {
+ await readableStream.pipeTo(fileStream);
+ window.$message?.success('涓嬭浇瀹屾垚');
+ return;
+ }
+
+ // 闄嶇骇澶勭悊
+ const writer = fileStream.getWriter();
+ const reader = readableStream.getReader();
+
+ const pump = async (): Promise<void> => {
+ const { done, value } = await reader.read();
+ if (done) return writer.close();
+ await writer.write(value);
+ return pump();
+ };
+
+ await pump();
+ }
+
+ /** 澶勭悊鍝嶅簲 */
+ async function handleResponse(response: Response) {
+ if (response.headers.get('Content-Type')?.includes('application/json')) {
+ const res = await response.json();
+ const code = res.code as CommonType.ErrorCode;
+ throw new Error(errorCodeRecord[code] || res.msg || errorCodeRecord.default);
+ }
+ }
+
+ /** 鏍稿績涓嬭浇閫昏緫 */
+ async function executeDownload(config: RequestConfig): Promise<void> {
+ const { method, url, params, filename, contentType } = config;
+ const timestamp = Date.now();
+ const fullUrl = `${baseURL}${url}${url.includes('?') ? '&' : '?'}t=${timestamp}`;
+
+ window.$loading?.startLoading('姝e湪涓嬭浇鏁版嵁锛岃绋嶅��...');
+
+ try {
+ const requestOptions: RequestInit = {
+ method,
+ headers: getCommonHeaders(contentType)
+ };
+
+ if (method === 'POST' && params) {
+ requestOptions.body = transformToURLSearchParams(params);
+ requestOptions.headers = {
+ ...requestOptions.headers,
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ };
+ }
+
+ const response = await fetch(fullUrl, requestOptions);
+
+ if (response.status !== 200) {
+ throw new Error(errorCodeRecord.default);
+ }
+
+ await handleResponse(response);
+
+ const rawHeader = response.headers.get('Download-Filename');
+ const finalFilename = filename || (rawHeader ? decodeURIComponent(rawHeader) : null) || `download-${timestamp}`;
+
+ if (response.body && isHttps()) {
+ const contentLength = Number(response.headers.get('Content-Length'));
+ await downloadByStream(response.body, finalFilename, contentLength);
+ return;
+ }
+
+ const responseContentType = response.headers.get('Content-Type');
+ const mainType = responseContentType?.split(';')[0]?.trim() || 'application/octet-stream';
+ downloadByData(await response.blob(), finalFilename, mainType);
+ } catch (error: any) {
+ window.$message?.error(error.message);
+ } finally {
+ window.$loading?.endLoading();
+ }
+ }
+
+ /** 鍏叡涓嬭浇鎺ュ彛 */
+ const download = (url: string, params: Record<string, any>, filename: string) =>
+ executeDownload({ method: 'POST', url, params, filename });
+
+ /** OSS鏂囦欢涓嬭浇 */
+ const oss = (ossId: CommonType.IdType) =>
+ executeDownload({
+ method: 'GET',
+ url: `/resource/oss/download/${ossId}`
+ });
+
+ /** ZIP鏂囦欢涓嬭浇 */
+ const zip = (url: string, filename: string) =>
+ executeDownload({
+ method: 'GET',
+ url,
+ filename,
+ contentType: 'application/octet-stream'
+ });
+
+ return {
+ oss,
+ zip,
+ download
+ };
+}
diff --git a/ruoyi-plus-soybean/src/hooks/common/echarts.ts b/ruoyi-plus-soybean/src/hooks/common/echarts.ts
new file mode 100755
index 0000000..8e7c728
--- /dev/null
+++ b/ruoyi-plus-soybean/src/hooks/common/echarts.ts
@@ -0,0 +1,230 @@
+import { computed, effectScope, nextTick, onScopeDispose, shallowRef, watch } from 'vue';
+import { useElementSize } from '@vueuse/core';
+import * as echarts from 'echarts/core';
+import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts';
+import type {
+ BarSeriesOption,
+ GaugeSeriesOption,
+ LineSeriesOption,
+ PictorialBarSeriesOption,
+ PieSeriesOption,
+ RadarSeriesOption,
+ ScatterSeriesOption
+} from 'echarts/charts';
+import {
+ DatasetComponent,
+ GridComponent,
+ LegendComponent,
+ TitleComponent,
+ ToolboxComponent,
+ TooltipComponent,
+ TransformComponent
+} from 'echarts/components';
+import type {
+ DatasetComponentOption,
+ GridComponentOption,
+ LegendComponentOption,
+ TitleComponentOption,
+ ToolboxComponentOption,
+ TooltipComponentOption
+} from 'echarts/components';
+import { LabelLayout, UniversalTransition } from 'echarts/features';
+import { CanvasRenderer } from 'echarts/renderers';
+import { useThemeStore } from '@/store/modules/theme';
+
+export type ECOption = echarts.ComposeOption<
+ | BarSeriesOption
+ | LineSeriesOption
+ | PieSeriesOption
+ | ScatterSeriesOption
+ | PictorialBarSeriesOption
+ | RadarSeriesOption
+ | GaugeSeriesOption
+ | TitleComponentOption
+ | LegendComponentOption
+ | TooltipComponentOption
+ | GridComponentOption
+ | ToolboxComponentOption
+ | DatasetComponentOption
+>;
+
+echarts.use([
+ TitleComponent,
+ LegendComponent,
+ TooltipComponent,
+ GridComponent,
+ DatasetComponent,
+ TransformComponent,
+ ToolboxComponent,
+ BarChart,
+ LineChart,
+ PieChart,
+ ScatterChart,
+ PictorialBarChart,
+ RadarChart,
+ GaugeChart,
+ LabelLayout,
+ UniversalTransition,
+ CanvasRenderer
+]);
+
+interface ChartHooks {
+ onRender?: (chart: echarts.ECharts) => void | Promise<void>;
+ onUpdated?: (chart: echarts.ECharts) => void | Promise<void>;
+ onDestroy?: (chart: echarts.ECharts) => void | Promise<void>;
+}
+
+/**
+ * use echarts
+ *
+ * @param optionsFactory echarts options factory function
+ * @param darkMode dark mode
+ */
+export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: ChartHooks = {}) {
+ const scope = effectScope();
+
+ const themeStore = useThemeStore();
+ const darkMode = computed(() => themeStore.darkMode);
+
+ const domRef = shallowRef<HTMLElement | null>(null);
+ const initialSize = { width: 0, height: 0 };
+ const { width, height } = useElementSize(domRef, initialSize);
+
+ const chart = shallowRef<echarts.ECharts | null>(null);
+ const chartOptions: T = optionsFactory();
+
+ const {
+ onRender = instance => {
+ const textColor = darkMode.value ? 'rgb(224, 224, 224)' : 'rgb(31, 31, 31)';
+ const maskColor = darkMode.value ? 'rgba(0, 0, 0, 0.4)' : 'rgba(255, 255, 255, 0.8)';
+
+ instance.showLoading({
+ color: themeStore.themeColor,
+ textColor,
+ fontSize: 14,
+ maskColor
+ });
+ },
+ onUpdated = instance => {
+ instance.hideLoading();
+ },
+ onDestroy
+ } = hooks;
+
+ /** is chart rendered */
+ function isRendered() {
+ return Boolean(domRef.value && chart.value);
+ }
+
+ /**
+ * update chart options
+ *
+ * @param callback callback function
+ */
+ async function updateOptions(callback: (opts: T, optsFactory: () => T) => ECOption = () => chartOptions) {
+ const updatedOpts = callback(chartOptions, optionsFactory);
+
+ Object.assign(chartOptions, updatedOpts);
+
+ await nextTick();
+
+ if (!isRendered()) return;
+
+ if (isRendered()) {
+ chart.value?.clear();
+ }
+
+ chart.value?.setOption({ ...updatedOpts, backgroundColor: 'transparent' });
+
+ await onUpdated?.(chart.value!);
+ }
+
+ function setOptions(options: T) {
+ chart.value?.setOption(options);
+ }
+
+ /** render chart */
+ async function render() {
+ if (isRendered()) return;
+
+ const chartTheme = darkMode.value ? 'dark' : 'light';
+
+ chart.value = echarts.init(domRef.value, chartTheme);
+
+ chart.value?.setOption({ ...chartOptions, backgroundColor: 'transparent' });
+
+ await onRender?.(chart.value!);
+ }
+
+ /** resize chart */
+ function resize() {
+ chart.value?.resize();
+ }
+
+ /** destroy chart */
+ async function destroy() {
+ if (!chart.value) return;
+
+ await onDestroy?.(chart.value);
+ chart.value?.dispose();
+ chart.value = null;
+ }
+
+ /** change chart theme */
+ async function changeTheme() {
+ await destroy();
+ await render();
+ await onUpdated?.(chart.value!);
+ }
+
+ /**
+ * render chart by size
+ *
+ * @param w width
+ * @param h height
+ */
+ async function renderChartBySize(w: number, h: number) {
+ initialSize.width = w;
+ initialSize.height = h;
+
+ // resize chart
+ if (isRendered()) {
+ resize();
+
+ return;
+ }
+
+ // render chart
+ await render();
+
+ if (chart.value) {
+ await onUpdated?.(chart.value);
+ }
+ }
+
+ scope.run(() => {
+ watch(
+ [width, height],
+ ([newWidth, newHeight]) => {
+ renderChartBySize(newWidth, newHeight);
+ },
+ { flush: 'post' }
+ );
+
+ watch(darkMode, () => {
+ changeTheme();
+ });
+ });
+
+ onScopeDispose(() => {
+ destroy();
+ scope.stop();
+ });
+
+ return {
+ domRef,
+ chart,
+ updateOptions,
+ setOptions
+ };
+}
diff --git a/ruoyi-plus-soybean/src/hooks/common/form.ts b/ruoyi-plus-soybean/src/hooks/common/form.ts
new file mode 100755
index 0000000..fa6ce6f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/hooks/common/form.ts
@@ -0,0 +1,112 @@
+import { ref, toValue } from 'vue';
+import type { ComputedRef, Ref } from 'vue';
+import type { FormInst } from 'naive-ui';
+import { REG_CODE_SIX, REG_EMAIL, REG_PHONE, REG_PWD, REG_USER_NAME } from '@/constants/reg';
+import { isNull } from '@/utils/common';
+import { $t } from '@/locales';
+
+export function useFormRules() {
+ const patternRules = {
+ userName: {
+ pattern: REG_USER_NAME,
+ message: $t('form.userName.invalid'),
+ trigger: ['change', 'blur']
+ },
+ phone: {
+ pattern: REG_PHONE,
+ message: $t('form.phone.invalid'),
+ trigger: 'change'
+ },
+ pwd: {
+ pattern: REG_PWD,
+ message: $t('form.pwd.invalid'),
+ trigger: ['change', 'blur']
+ },
+ code: {
+ pattern: REG_CODE_SIX,
+ message: $t('form.code.invalid'),
+ trigger: 'change'
+ },
+ email: {
+ pattern: REG_EMAIL,
+ message: $t('form.email.invalid'),
+ trigger: 'change'
+ }
+ } satisfies Record<string, App.Global.FormRule>;
+
+ const formRules = {
+ userName: [createRequiredRule($t('form.userName.required')), patternRules.userName],
+ phone: [createRequiredRule($t('form.phone.required')), patternRules.phone],
+ pwd: [createRequiredRule($t('form.pwd.required')), patternRules.pwd],
+ code: [createRequiredRule($t('form.code.required')), patternRules.code],
+ email: [createRequiredRule($t('form.email.required')), patternRules.email],
+ tenantId: [createRequiredRule('璇烽�夋嫨/杈撳叆鍏徃鍚嶇О')]
+ } satisfies Record<string, App.Global.FormRule[]>;
+
+ /** the default required rule */
+ const defaultRequiredRule = createRequiredRule($t('form.required'));
+
+ /** the default number required rule */
+
+ function createRequiredRule(message: string): App.Global.FormRule {
+ return {
+ required: true,
+ trigger: ['input', 'blur'],
+ validator: (_rule: any, value: any) => {
+ if (isNull(value) || (Array.isArray(value) && value.length === 0)) {
+ return new Error(message);
+ }
+ return true;
+ }
+ };
+ }
+
+ function createNumberRequiredRule(message: string): App.Global.FormRule {
+ return { ...createRequiredRule(message), type: 'number' };
+ }
+
+ /** create a rule for confirming the password */
+ function createConfirmPwdRule(pwd: string | Ref<string> | ComputedRef<string>) {
+ const confirmPwdRule: App.Global.FormRule[] = [
+ { required: true, message: $t('form.confirmPwd.required') },
+ {
+ asyncValidator: (rule, value) => {
+ if (value.trim() !== '' && value !== toValue(pwd)) {
+ return Promise.reject(rule.message);
+ }
+ return Promise.resolve();
+ },
+ message: $t('form.confirmPwd.invalid'),
+ trigger: 'input'
+ }
+ ];
+ return confirmPwdRule;
+ }
+
+ return {
+ patternRules,
+ formRules,
+ defaultRequiredRule,
+ createRequiredRule,
+ createConfirmPwdRule,
+ createNumberRequiredRule
+ };
+}
+
+export function useNaiveForm() {
+ const formRef = ref<FormInst | null>(null);
+
+ async function validate() {
+ await formRef.value?.validate();
+ }
+
+ async function restoreValidation() {
+ formRef.value?.restoreValidation();
+ }
+
+ return {
+ formRef,
+ validate,
+ restoreValidation
+ };
+}
diff --git a/ruoyi-plus-soybean/src/hooks/common/icon.ts b/ruoyi-plus-soybean/src/hooks/common/icon.ts
new file mode 100755
index 0000000..8998f60
--- /dev/null
+++ b/ruoyi-plus-soybean/src/hooks/common/icon.ts
@@ -0,0 +1,10 @@
+import { useSvgIconRender } from '@sa/hooks';
+import SvgIcon from '@/components/custom/svg-icon.vue';
+
+export function useSvgIcon() {
+ const { SvgIconVNode } = useSvgIconRender(SvgIcon);
+
+ return {
+ SvgIconVNode
+ };
+}
diff --git a/ruoyi-plus-soybean/src/hooks/common/loading.ts b/ruoyi-plus-soybean/src/hooks/common/loading.ts
new file mode 100755
index 0000000..27ddc41
--- /dev/null
+++ b/ruoyi-plus-soybean/src/hooks/common/loading.ts
@@ -0,0 +1,25 @@
+import { ref } from 'vue';
+import { useLoading } from '@sa/hooks';
+
+/** Content Loading */
+export default function useContentLoading() {
+ const description = ref<string>('loading...');
+ const loading = useLoading();
+
+ function startLoading(desc: string = 'loading...') {
+ description.value = desc;
+ loading.startLoading();
+ }
+
+ function endLoading() {
+ description.value = 'loading...';
+ loading.endLoading();
+ }
+
+ return {
+ loading: loading.loading,
+ description,
+ startLoading,
+ endLoading
+ };
+}
diff --git a/ruoyi-plus-soybean/src/hooks/common/router.ts b/ruoyi-plus-soybean/src/hooks/common/router.ts
new file mode 100755
index 0000000..3fadcbe
--- /dev/null
+++ b/ruoyi-plus-soybean/src/hooks/common/router.ts
@@ -0,0 +1,116 @@
+import { useRouter } from 'vue-router';
+import type { RouteLocationRaw } from 'vue-router';
+import type { RouteKey } from '@elegant-router/types';
+import { router as globalRouter } from '@/router';
+
+/**
+ * Router push
+ *
+ * Jump to the specified route, it can replace function router.push
+ *
+ * @param inSetup Whether is in vue script setup
+ */
+export function useRouterPush(inSetup = true) {
+ const router = inSetup ? useRouter() : globalRouter;
+ const route = globalRouter.currentRoute;
+
+ const routerPush = router.push;
+
+ const routerBack = router.back;
+
+ async function routerPushByKey(key: RouteKey, options?: App.Global.RouterPushOptions) {
+ const { query, params } = options || {};
+
+ const routeLocation: RouteLocationRaw = {
+ name: key
+ };
+
+ if (Object.keys(query || {}).length) {
+ routeLocation.query = query;
+ }
+
+ if (Object.keys(params || {}).length) {
+ routeLocation.params = params;
+ }
+
+ return routerPush(routeLocation);
+ }
+
+ function routerPushByKeyWithMetaQuery(key: RouteKey) {
+ const allRoutes = router.getRoutes();
+ const meta = allRoutes.find(item => item.name === key)?.meta || null;
+
+ const query: Record<string, string> = {};
+
+ meta?.query?.forEach(item => {
+ query[item.key] = item.value;
+ });
+
+ return routerPushByKey(key, { query });
+ }
+
+ async function toHome() {
+ return routerPushByKey('root');
+ }
+
+ /**
+ * Navigate to login page
+ *
+ * @param loginModule The login module
+ * @param redirectUrl The redirect url, if not specified, it will be the current route fullPath
+ */
+ async function toLogin(loginModule?: UnionKey.LoginModule, redirectUrl?: string) {
+ const module = loginModule || 'pwd-login';
+
+ const options: App.Global.RouterPushOptions = {
+ params: {
+ module
+ }
+ };
+
+ const redirect = redirectUrl || route.value.fullPath;
+
+ options.query = {
+ redirect
+ };
+
+ return routerPushByKey('login', options);
+ }
+
+ /**
+ * Toggle login module
+ *
+ * @param module
+ */
+ async function toggleLoginModule(module: UnionKey.LoginModule) {
+ const query = route.value.query as Record<string, string>;
+
+ return routerPushByKey('login', { query, params: { module } });
+ }
+
+ /**
+ * Redirect from login
+ *
+ * @param [needRedirect=true] Whether to redirect after login. Default is `true`
+ */
+ async function redirectFromLogin(needRedirect = true) {
+ const redirect = route.value.query?.redirect as string;
+
+ if (needRedirect && redirect) {
+ await routerPush(redirect);
+ } else {
+ await toHome();
+ }
+ }
+
+ return {
+ routerPush,
+ routerBack,
+ routerPushByKey,
+ routerPushByKeyWithMetaQuery,
+ toLogin,
+ toggleLoginModule,
+ redirectFromLogin,
+ toHome
+ };
+}
diff --git a/ruoyi-plus-soybean/src/hooks/common/table.ts b/ruoyi-plus-soybean/src/hooks/common/table.ts
new file mode 100755
index 0000000..096fcf5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/hooks/common/table.ts
@@ -0,0 +1,477 @@
+import { computed, effectScope, onScopeDispose, reactive, ref, shallowRef, watch } from 'vue';
+import type { Ref } from 'vue';
+import type { PaginationProps } from 'naive-ui';
+import { useBoolean, useTable } from '@sa/hooks';
+import type { PaginationData, TableColumnCheck, UseTableOptions } from '@sa/hooks';
+import type { FlatResponseData } from '@sa/axios';
+import { jsonClone } from '@sa/utils';
+import { useAppStore } from '@/store/modules/app';
+import { handleTree } from '@/utils/common';
+import { $t } from '@/locales';
+
+export type UseNaiveTableOptions<ResponseData, ApiData, Pagination extends boolean> = Omit<
+ UseTableOptions<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, Pagination>,
+ 'pagination' | 'getColumnChecks' | 'getColumns'
+> & {
+ /**
+ * get column visible
+ *
+ * @param column
+ *
+ * @default true
+ *
+ * @returns true if the column is visible, false otherwise
+ */
+ getColumnVisible?: (column: NaiveUI.TableColumn<ApiData>) => boolean;
+};
+
+const SELECTION_KEY = '__selection__';
+
+const EXPAND_KEY = '__expand__';
+
+export function useNaiveTable<ResponseData, ApiData>(options: UseNaiveTableOptions<ResponseData, ApiData, false>) {
+ const scope = effectScope();
+ const appStore = useAppStore();
+
+ const result = useTable<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, false>({
+ ...options,
+ getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible),
+ getColumns
+ });
+
+ // calculate the total width of the table this is used for horizontal scrolling
+ const scrollX = computed(() => {
+ return result.columns.value.reduce((acc, column) => {
+ return acc + Number(column.width ?? column.minWidth ?? 120);
+ }, 0);
+ });
+
+ scope.run(() => {
+ watch(
+ () => appStore.locale,
+ () => {
+ result.reloadColumns();
+ }
+ );
+ });
+
+ onScopeDispose(() => {
+ scope.stop();
+ });
+
+ return {
+ ...result,
+ scrollX
+ };
+}
+
+type PaginationParams = Pick<PaginationProps, 'page' | 'pageSize'>;
+
+type UseNaivePaginatedTableOptions<ResponseData, ApiData> = UseNaiveTableOptions<ResponseData, ApiData, true> & {
+ paginationProps?: Omit<PaginationProps, 'page' | 'pageSize' | 'itemCount'>;
+ /**
+ * whether to show the total count of the table
+ *
+ * @default true
+ */
+ showTotal?: boolean;
+ onPaginationParamsChange?: (params: PaginationParams) => void | Promise<void>;
+};
+
+export function useNaivePaginatedTable<ResponseData, ApiData>(
+ options: UseNaivePaginatedTableOptions<ResponseData, ApiData>
+) {
+ const scope = effectScope();
+ const appStore = useAppStore();
+
+ const isMobile = computed(() => appStore.isMobile);
+
+ const showTotal = computed(() => options.showTotal ?? true);
+
+ const pagination = reactive({
+ page: 1,
+ pageSize: 10,
+ itemCount: 0,
+ showSizePicker: true,
+ pageSizes: [10, 15, 20, 25, 30],
+ prefix: showTotal.value ? page => $t('datatable.itemCount', { total: page.itemCount }) : undefined,
+ onUpdatePage(page) {
+ pagination.page = page;
+ },
+ onUpdatePageSize(pageSize) {
+ pagination.pageSize = pageSize;
+ pagination.page = 1;
+ },
+ ...options.paginationProps
+ }) as PaginationProps;
+
+ // this is for mobile, if the system does not support mobile, you can use `pagination` directly
+ const mobilePagination = computed(() => {
+ const p: PaginationProps = {
+ ...pagination,
+ pageSlot: isMobile.value ? 3 : 9,
+ prefix: !isMobile.value && showTotal.value ? pagination.prefix : undefined
+ };
+
+ return p;
+ });
+
+ const paginationParams = computed(() => {
+ const { page, pageSize } = pagination;
+
+ return {
+ page,
+ pageSize
+ };
+ });
+
+ const result = useTable<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, true>({
+ ...options,
+ pagination: true,
+ getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible),
+ getColumns,
+ onFetched: data => {
+ pagination.itemCount = data.total;
+ }
+ });
+
+ // calculate the total width of the table this is used for horizontal scrolling
+ const scrollX = computed(() => {
+ return result.columns.value.reduce((acc, column) => {
+ return acc + Number(column.width ?? column.minWidth ?? 120);
+ }, 0);
+ });
+
+ async function getDataByPage(page: number = 1) {
+ if (page !== pagination.page) {
+ pagination.page = page;
+
+ return;
+ }
+
+ await result.getData();
+ }
+
+ scope.run(() => {
+ watch(
+ () => appStore.locale,
+ () => {
+ result.reloadColumns();
+ }
+ );
+
+ watch(paginationParams, async newVal => {
+ await options.onPaginationParamsChange?.(newVal);
+
+ await result.getData();
+ });
+ });
+
+ onScopeDispose(() => {
+ scope.stop();
+ });
+
+ return {
+ ...result,
+ scrollX,
+ getDataByPage,
+ pagination,
+ mobilePagination
+ };
+}
+
+export function useTableOperate<TableData>(
+ data: Ref<TableData[]>,
+ idKey: keyof TableData,
+ getData: () => Promise<void>
+) {
+ const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
+
+ const operateType = shallowRef<NaiveUI.TableOperateType>('add');
+
+ function handleAdd() {
+ operateType.value = 'add';
+ openDrawer();
+ }
+
+ /** the editing row data */
+ const editingData = shallowRef<TableData | null>(null);
+
+ function handleEdit(id: TableData[keyof TableData]) {
+ operateType.value = 'edit';
+ const findItem = data.value.find(item => item[idKey] === id) || null;
+ editingData.value = jsonClone(findItem);
+
+ openDrawer();
+ }
+
+ /** the checked row keys of table */
+ const checkedRowKeys = shallowRef<CommonType.IdType[]>([]);
+
+ /** the hook after the batch delete operation is completed */
+ async function onBatchDeleted() {
+ window.$message?.success($t('common.deleteSuccess'));
+
+ checkedRowKeys.value = [];
+
+ await getData();
+ }
+
+ /** the hook after the delete operation is completed */
+ async function onDeleted() {
+ window.$message?.success($t('common.deleteSuccess'));
+
+ await getData();
+ }
+
+ return {
+ drawerVisible,
+ openDrawer,
+ closeDrawer,
+ operateType,
+ handleAdd,
+ editingData,
+ handleEdit,
+ checkedRowKeys,
+ onBatchDeleted,
+ onDeleted
+ };
+}
+
+export function defaultTransform<ApiData>(
+ response: FlatResponseData<any, Api.Common.PaginatingQueryRecord<ApiData>>
+): PaginationData<ApiData> {
+ const { data, error } = response;
+
+ if (error) {
+ return {
+ data: [],
+ pageNum: 1,
+ total: 0
+ };
+ }
+
+ const { rows: records, pageNum: current, total } = data;
+
+ return {
+ data: records,
+ pageNum: current,
+ total
+ };
+}
+
+type TreeTableTransformResult<ApiData> = {
+ /** tree data for display */
+ tree: ApiData[];
+ /** flat data for operations */
+ flatData: ApiData[];
+};
+
+type UseNaiveTreeTableOptions<ResponseData, ApiData> = Omit<
+ UseNaiveTableOptions<ResponseData, ApiData, false>,
+ 'transform'
+> & {
+ keyField: keyof ApiData;
+ defaultExpandAll?: boolean;
+ /**
+ * transform api response to tree table data
+ */
+ transform: (response: ResponseData) => TreeTableTransformResult<ApiData>;
+};
+
+export function useNaiveTreeTable<ResponseData, ApiData>(options: UseNaiveTreeTableOptions<ResponseData, ApiData>) {
+ const scope = effectScope();
+ const appStore = useAppStore();
+ const rows: Ref<ApiData[]> = ref([]);
+
+ const result = useTable<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, false>({
+ ...options,
+ pagination: false,
+ transform: response => {
+ const transformed = options.transform(response);
+ // save flat data for operations
+ rows.value = transformed.flatData;
+ // return tree data for display
+ return transformed.tree;
+ },
+ getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible),
+ getColumns
+ });
+
+ // calculate the total width of the table this is used for horizontal scrolling
+ const scrollX = computed(() => {
+ return result.columns.value.reduce((acc, column) => {
+ return acc + Number(column.width ?? column.minWidth ?? 120);
+ }, 0);
+ });
+
+ const { keyField = 'id', defaultExpandAll = false } = options;
+
+ const expandedRowKeys = ref<ApiData[keyof ApiData][]>([]);
+ const { bool: isCollapse, toggle: toggleCollapse } = useBoolean(defaultExpandAll);
+
+ /** expand all nodes */
+ function expandAll() {
+ toggleCollapse();
+ expandedRowKeys.value = rows.value.map(item => item[keyField as keyof ApiData]);
+ }
+
+ /** collapse all nodes */
+ function collapseAll() {
+ toggleCollapse();
+ expandedRowKeys.value = [];
+ }
+
+ scope.run(() => {
+ watch(
+ () => appStore.locale,
+ () => {
+ result.reloadColumns();
+ }
+ );
+ });
+
+ onScopeDispose(() => {
+ scope.stop();
+ });
+
+ return {
+ ...result,
+ scrollX,
+ rows,
+ isCollapse,
+ expandedRowKeys,
+ expandAll,
+ collapseAll
+ };
+}
+
+export function useTreeTableOperate<ApiData>(data: Ref<ApiData[]>, idKey: keyof ApiData, getData: () => Promise<void>) {
+ const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
+
+ const operateType = shallowRef<NaiveUI.TableOperateType>('add');
+
+ function handleAdd() {
+ operateType.value = 'add';
+ openDrawer();
+ }
+
+ /** the editing row data */
+ const editingData = shallowRef<ApiData | null>(null);
+
+ function handleEdit(id: ApiData[keyof ApiData]) {
+ operateType.value = 'edit';
+ const findItem = data.value.find(item => item[idKey] === id) || null;
+ editingData.value = jsonClone(findItem);
+
+ openDrawer();
+ }
+
+ /** the checked row keys of table */
+ const checkedRowKeys = shallowRef<string[]>([]);
+
+ /** the hook after the batch delete operation is completed */
+ async function onBatchDeleted() {
+ window.$message?.success($t('common.deleteSuccess'));
+
+ checkedRowKeys.value = [];
+
+ await getData();
+ }
+
+ /** the hook after the delete operation is completed */
+ async function onDeleted() {
+ window.$message?.success($t('common.deleteSuccess'));
+
+ await getData();
+ }
+
+ return {
+ drawerVisible,
+ openDrawer,
+ closeDrawer,
+ operateType,
+ handleAdd,
+ editingData,
+ handleEdit,
+ checkedRowKeys,
+ onBatchDeleted,
+ onDeleted
+ };
+}
+
+export function treeTransform<ApiData>(
+ response: FlatResponseData<any, ApiData[]>,
+ options: CommonType.TreeConfig<ApiData> = {}
+): TreeTableTransformResult<ApiData> {
+ const { data, error } = response;
+
+ if (!error) {
+ return handleTree(data, options);
+ }
+
+ return {
+ tree: [],
+ flatData: []
+ };
+}
+
+function getColumnChecks<Column extends NaiveUI.TableColumn<any>>(
+ cols: Column[],
+ getColumnVisible?: (column: Column) => boolean
+) {
+ const checks: TableColumnCheck[] = [];
+
+ cols.forEach(column => {
+ if (isTableColumnHasKey(column)) {
+ checks.push({
+ key: column.key as string,
+ title: column.title!,
+ checked: true,
+ visible: getColumnVisible?.(column) ?? true
+ });
+ } else if (column.type === 'selection') {
+ checks.push({
+ key: SELECTION_KEY,
+ title: $t('common.check'),
+ checked: true,
+ visible: getColumnVisible?.(column) ?? false
+ });
+ } else if (column.type === 'expand') {
+ checks.push({
+ key: EXPAND_KEY,
+ title: $t('common.expandColumn'),
+ checked: true,
+ visible: getColumnVisible?.(column) ?? false
+ });
+ }
+ });
+
+ return checks;
+}
+
+function getColumns<Column extends NaiveUI.TableColumn<any>>(cols: Column[], checks: TableColumnCheck[]) {
+ const columnMap = new Map<string, Column>();
+
+ cols.forEach(column => {
+ if (isTableColumnHasKey(column)) {
+ columnMap.set(column.key as string, column);
+ } else if (column.type === 'selection') {
+ columnMap.set(SELECTION_KEY, column);
+ } else if (column.type === 'expand') {
+ columnMap.set(EXPAND_KEY, column);
+ }
+ });
+
+ // Filter out any checks that do not have a corresponding column (can happen when column definitions change).
+ const filteredColumns = checks
+ .filter(item => item.checked)
+ .map(check => columnMap.get(check.key))
+ .filter((col): col is Column => Boolean(col));
+
+ return filteredColumns;
+}
+
+export function isTableColumnHasKey<T>(column: NaiveUI.TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
+ return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
+}
diff --git a/ruoyi-plus-soybean/src/layouts/base-layout/index.vue b/ruoyi-plus-soybean/src/layouts/base-layout/index.vue
new file mode 100755
index 0000000..99b163e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/base-layout/index.vue
@@ -0,0 +1,170 @@
+<script setup lang="ts">
+import { computed, defineAsyncComponent, onMounted } from 'vue';
+import { AdminLayout, LAYOUT_SCROLL_EL_ID } from '@sa/materials';
+import type { LayoutMode } from '@sa/materials';
+import { useAppStore } from '@/store/modules/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { initWebSocket } from '@/utils/websocket';
+import { initSSE } from '@/utils/sse';
+import GlobalHeader from '../modules/global-header/index.vue';
+import GlobalSider from '../modules/global-sider/index.vue';
+import GlobalTab from '../modules/global-tab/index.vue';
+import GlobalContent from '../modules/global-content/index.vue';
+import GlobalFooter from '../modules/global-footer/index.vue';
+import ThemeDrawer from '../modules/theme-drawer/index.vue';
+import { provideMixMenuContext } from '../modules/global-menu/context';
+
+defineOptions({
+ name: 'BaseLayout'
+});
+
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+const { secondLevelMenus, childLevelMenus, isActiveFirstLevelMenuHasChildren } = provideMixMenuContext();
+
+const GlobalMenu = defineAsyncComponent(() => import('../modules/global-menu/index.vue'));
+
+const layoutMode = computed(() => {
+ const vertical: LayoutMode = 'vertical';
+ const horizontal: LayoutMode = 'horizontal';
+ return themeStore.layout.mode.includes(vertical) ? vertical : horizontal;
+});
+
+const headerProps = computed(() => {
+ const { mode } = themeStore.layout;
+
+ const headerPropsConfig: Record<UnionKey.ThemeLayoutMode, App.Global.HeaderProps> = {
+ vertical: {
+ showLogo: false,
+ showMenu: false,
+ showMenuToggler: true
+ },
+ 'vertical-mix': {
+ showLogo: false,
+ showMenu: false,
+ showMenuToggler: false
+ },
+ 'vertical-hybrid-header-first': {
+ showLogo: !isActiveFirstLevelMenuHasChildren.value,
+ showMenu: true,
+ showMenuToggler: false
+ },
+ horizontal: {
+ showLogo: true,
+ showMenu: true,
+ showMenuToggler: false
+ },
+ 'top-hybrid-sidebar-first': {
+ showLogo: true,
+ showMenu: true,
+ showMenuToggler: false
+ },
+ 'top-hybrid-header-first': {
+ showLogo: true,
+ showMenu: true,
+ showMenuToggler: isActiveFirstLevelMenuHasChildren.value
+ }
+ };
+
+ return headerPropsConfig[mode];
+});
+
+const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal');
+
+const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
+
+const isVerticalHybridHeaderFirst = computed(() => themeStore.layout.mode === 'vertical-hybrid-header-first');
+
+const isTopHybridSidebarFirst = computed(() => themeStore.layout.mode === 'top-hybrid-sidebar-first');
+
+const isTopHybridHeaderFirst = computed(() => themeStore.layout.mode === 'top-hybrid-header-first');
+
+const siderWidth = computed(() => getSiderAndCollapsedWidth(false));
+
+const siderCollapsedWidth = computed(() => getSiderAndCollapsedWidth(true));
+
+function getSiderAndCollapsedWidth(isCollapsed: boolean) {
+ const {
+ mixChildMenuWidth,
+ collapsedWidth,
+ width: themeWidth,
+ mixCollapsedWidth,
+ mixWidth: themeMixWidth
+ } = themeStore.sider;
+
+ const width = isCollapsed ? collapsedWidth : themeWidth;
+ const mixWidth = isCollapsed ? mixCollapsedWidth : themeMixWidth;
+
+ if (isTopHybridHeaderFirst.value) {
+ return isActiveFirstLevelMenuHasChildren.value ? width : 0;
+ }
+
+ if (isVerticalHybridHeaderFirst.value && !isActiveFirstLevelMenuHasChildren.value) {
+ return 0;
+ }
+
+ const isMixMode = isVerticalMix.value || isTopHybridSidebarFirst.value || isVerticalHybridHeaderFirst.value;
+ let finalWidth = isMixMode ? mixWidth : width;
+
+ if (isVerticalMix.value && appStore.mixSiderFixed && secondLevelMenus.value.length) {
+ finalWidth += mixChildMenuWidth;
+ }
+
+ if (isVerticalHybridHeaderFirst.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
+ finalWidth += mixChildMenuWidth;
+ }
+
+ return finalWidth;
+}
+
+onMounted(() => {
+ const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
+ initWebSocket(`${protocol + window.location.host + import.meta.env.VITE_APP_BASE_API}/resource/websocket`);
+ initSSE(`${import.meta.env.VITE_APP_BASE_API}/resource/sse`);
+});
+</script>
+
+<template>
+ <AdminLayout
+ v-model:sider-collapse="appStore.siderCollapse"
+ :mode="layoutMode"
+ :scroll-el-id="LAYOUT_SCROLL_EL_ID"
+ :scroll-mode="themeStore.layout.scrollMode"
+ :is-mobile="appStore.isMobile"
+ :full-content="appStore.fullContent"
+ :fixed-top="themeStore.fixedHeaderAndTab"
+ :header-height="themeStore.header.height"
+ :tab-visible="themeStore.tab.visible"
+ :tab-height="themeStore.tab.height"
+ :content-class="appStore.contentXScrollable ? 'overflow-x-hidden' : ''"
+ :sider-visible="siderVisible"
+ :sider-width="siderWidth"
+ :sider-collapsed-width="siderCollapsedWidth"
+ :footer-visible="themeStore.footer.visible"
+ :footer-height="themeStore.footer.height"
+ :fixed-footer="themeStore.footer.fixed"
+ :right-footer="themeStore.footer.right"
+ >
+ <template #header>
+ <GlobalHeader v-bind="headerProps" />
+ </template>
+ <template #tab>
+ <GlobalTab />
+ </template>
+ <template #sider>
+ <GlobalSider />
+ </template>
+ <GlobalMenu />
+ <GlobalContent />
+ <ThemeDrawer />
+ <template #footer>
+ <GlobalFooter />
+ </template>
+ </AdminLayout>
+</template>
+
+<style lang="scss">
+#__SCROLL_EL_ID__ {
+ @include scrollbar();
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/blank-layout/index.vue b/ruoyi-plus-soybean/src/layouts/blank-layout/index.vue
new file mode 100755
index 0000000..2e393f0
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/blank-layout/index.vue
@@ -0,0 +1,13 @@
+<script setup lang="ts">
+import GlobalContent from '../modules/global-content/index.vue';
+
+defineOptions({
+ name: 'BlankLayout'
+});
+</script>
+
+<template>
+ <GlobalContent :show-padding="false" />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-breadcrumb/index.vue b/ruoyi-plus-soybean/src/layouts/modules/global-breadcrumb/index.vue
new file mode 100755
index 0000000..0a17907
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-breadcrumb/index.vue
@@ -0,0 +1,47 @@
+<script setup lang="ts">
+import { createReusableTemplate } from '@vueuse/core';
+import type { RouteKey } from '@elegant-router/types';
+import { useThemeStore } from '@/store/modules/theme';
+import { useRouteStore } from '@/store/modules/route';
+import { useRouterPush } from '@/hooks/common/router';
+
+defineOptions({
+ name: 'GlobalBreadcrumb'
+});
+
+const themeStore = useThemeStore();
+const routeStore = useRouteStore();
+const { routerPushByKey } = useRouterPush();
+
+interface BreadcrumbContentProps {
+ breadcrumb: App.Global.Menu;
+}
+
+const [DefineBreadcrumbContent, BreadcrumbContent] = createReusableTemplate<BreadcrumbContentProps>();
+
+function handleClickMenu(key: RouteKey) {
+ routerPushByKey(key);
+}
+</script>
+
+<template>
+ <NBreadcrumb v-if="themeStore.header.breadcrumb.visible">
+ <!-- define component start: BreadcrumbContent -->
+ <DefineBreadcrumbContent v-slot="{ breadcrumb }">
+ <div class="i-flex-y-center align-middle">
+ <component :is="breadcrumb.icon" v-if="themeStore.header.breadcrumb.showIcon" class="mr-4px text-icon" />
+ {{ breadcrumb.label }}
+ </div>
+ </DefineBreadcrumbContent>
+ <!-- define component end: BreadcrumbContent -->
+
+ <NBreadcrumbItem v-for="item in routeStore.breadcrumbs" :key="item.key">
+ <NDropdown v-if="item.options?.length" :options="item.options" @select="handleClickMenu">
+ <BreadcrumbContent :breadcrumb="item" />
+ </NDropdown>
+ <BreadcrumbContent v-else :breadcrumb="item" />
+ </NBreadcrumbItem>
+ </NBreadcrumb>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-content/index.vue b/ruoyi-plus-soybean/src/layouts/modules/global-content/index.vue
new file mode 100755
index 0000000..cff3c44
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-content/index.vue
@@ -0,0 +1,63 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { LAYOUT_SCROLL_EL_ID } from '@sa/materials';
+import { useAppStore } from '@/store/modules/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { useRouteStore } from '@/store/modules/route';
+import { useTabStore } from '@/store/modules/tab';
+
+defineOptions({
+ name: 'GlobalContent'
+});
+
+interface Props {
+ /** Show padding for content */
+ showPadding?: boolean;
+}
+
+withDefaults(defineProps<Props>(), {
+ showPadding: true
+});
+
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+const routeStore = useRouteStore();
+const tabStore = useTabStore();
+
+const transitionName = computed(() => (themeStore.page.animate ? themeStore.page.animateMode : ''));
+const footerVar = computed(() => themeStore.footer.visible);
+
+function resetScroll() {
+ const el = document.querySelector(`#${LAYOUT_SCROLL_EL_ID}`);
+
+ el?.scrollTo({ left: 0, top: 0 });
+}
+</script>
+
+<template>
+ <RouterView v-slot="{ Component, route }">
+ <Transition
+ :name="transitionName"
+ mode="out-in"
+ @before-leave="appStore.setContentXScrollable(true)"
+ @after-leave="resetScroll"
+ @after-enter="appStore.setContentXScrollable(false)"
+ >
+ <KeepAlive :include="routeStore.cacheRoutes" :exclude="routeStore.excludeCacheRoutes">
+ <component
+ :is="Component"
+ v-if="appStore.reloadFlag"
+ :key="tabStore.getTabIdByRoute(route)"
+ :class="{ 'p-16px': showPadding, 'footer-var': footerVar }"
+ class="flex-grow bg-layout transition-300"
+ />
+ </KeepAlive>
+ </Transition>
+ </RouterView>
+</template>
+
+<style>
+.footer-var {
+ --calc-footer-height: var(--soy-footer-height);
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-footer/index.vue b/ruoyi-plus-soybean/src/layouts/modules/global-footer/index.vue
new file mode 100755
index 0000000..891edd4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-footer/index.vue
@@ -0,0 +1,13 @@
+<script setup lang="ts">
+defineOptions({
+ name: 'GlobalFooter'
+});
+</script>
+
+<template>
+ <DarkModeContainer class="h-full flex-center">
+ <a href="" target="_blank" rel="noopener noreferrer">Copyright MIT 漏 2026 涓婃捣鍏板疂</a>
+ </DarkModeContainer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-header/components/message-button.vue b/ruoyi-plus-soybean/src/layouts/modules/global-header/components/message-button.vue
new file mode 100755
index 0000000..c0bf4ac
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-header/components/message-button.vue
@@ -0,0 +1,152 @@
+<script setup lang="ts">
+import { computed, ref } from 'vue';
+import { storeToRefs } from 'pinia';
+import { useNoticeStore } from '@/store/modules/notice';
+
+defineOptions({
+ name: 'MessgaeButton'
+});
+
+const show = ref(false);
+const noticeStore = useNoticeStore();
+const { state } = storeToRefs(noticeStore);
+
+const noticeNum = computed(() => {
+ return state.value.notices.filter(notice => !notice.read).length || 0;
+});
+
+const toGitee = () => {
+ // window.open('', '_blank');
+};
+</script>
+
+<template>
+ <NPopover v-model:show="show" trigger="click" arrow-point-to-center raw class="border-rounded-6px">
+ <template #trigger>
+ <NTooltip :disabled="show">
+ <template #trigger>
+ <NButton quaternary class="bell-button h-36px text-icon" :focusable="false">
+ <NBadge :value="noticeNum" :max="99" :offset="[2, -2]">
+ <div class="bell-icon flex-center gap-8px">
+ <SvgIcon local-icon="bell" />
+ </div>
+ </NBadge>
+ </NButton>
+ </template>
+ {{ $t('page.home.message') }}
+ </NTooltip>
+ </template>
+ <NCard
+ size="small"
+ :bordered="false"
+ class="w-345px"
+ header-class="p-0"
+ :segmented="{ content: true, footer: 'soft' }"
+ >
+ <template #header>
+ <span>閫氱煡鍏憡</span>
+ </template>
+ <template #header-extra>
+ <NTooltip placement="left" :z-index="98">
+ <template #trigger>
+ <NPopconfirm @positive-click="() => noticeStore.readAll()">
+ <template #trigger>
+ <NButton quaternary>
+ <div class="flex-center gap-8px">
+ <SvgIcon icon="lucide:mail-check" class="text-16px" />
+ </div>
+ </NButton>
+ </template>
+ 纭畾鍏ㄩ儴宸茶鍚楋紵
+ </NPopconfirm>
+ </template>
+ 涓�閿凡璇�
+ </NTooltip>
+ </template>
+ <NScrollbar class="h-260px">
+ <template v-if="state?.notices?.length">
+ <template v-for="(message, index) in state?.notices" :key="index">
+ <NDivider v-show="index !== 0" />
+ <div class="flex cursor-pointer" @click="() => noticeStore.readNotice(message)">
+ <div class="flex-col justify-between gap-3px">
+ <NEllipsis class="w-260px">{{ message.message }}</NEllipsis>
+ <span class="text-#898989">
+ {{ message.time }}
+ </span>
+ </div>
+ <div>
+ <NTag :type="message.read ? 'success' : 'error'">{{ message.read ? '宸茶' : '鏈' }}</NTag>
+ </div>
+ </div>
+ </template>
+ </template>
+ <NEmpty v-else class="h-180px flex-center" />
+ </NScrollbar>
+ <template #footer>
+ <div class="flex items-center justify-end">
+ <NButton type="primary" size="small" @click="toGitee">鍓嶅線</NButton>
+ </div>
+ </template>
+ </NCard>
+ </NPopover>
+</template>
+
+<style scoped lang="scss">
+:deep(.n-divider) {
+ margin: 12px 0;
+}
+
+:deep(.n-thing-header) {
+ margin-bottom: 1px !important;
+}
+
+:deep(.n-thing-main__content) {
+ margin-top: 0 !important;
+}
+
+:deep(.messgae-popover) {
+ padding: 0 !important;
+}
+
+:deep(.n-badge-sup) {
+ padding: 0 5px !important;
+ font-size: 10px !important;
+ height: 15px !important;
+ line-height: 15px !important;
+}
+
+.bell-button {
+ &:hover {
+ .bell-icon {
+ animation: bell-ring 1s both;
+ }
+ }
+}
+
+@keyframes bell-ring {
+ 0%,
+ 100% {
+ transform-origin: top;
+ }
+
+ 15% {
+ transform: rotateZ(10deg);
+ }
+
+ 30% {
+ transform: rotateZ(-10deg);
+ }
+
+ 45% {
+ transform: rotateZ(5deg);
+ }
+
+ 60% {
+ transform: rotateZ(-5deg);
+ }
+
+ 75% {
+ transform: rotateZ(2deg);
+ }
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-header/components/theme-button.vue b/ruoyi-plus-soybean/src/layouts/modules/global-header/components/theme-button.vue
new file mode 100755
index 0000000..06c40ec
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-header/components/theme-button.vue
@@ -0,0 +1,20 @@
+<script setup lang="ts">
+import { useAppStore } from '@/store/modules/app';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'ThemeButton'
+});
+
+const appStore = useAppStore();
+</script>
+
+<template>
+ <ButtonIcon
+ icon="majesticons:color-swatch-line"
+ :tooltip-content="$t('icon.themeConfig')"
+ @click="appStore.openThemeDrawer"
+ />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-header/components/user-avatar.vue b/ruoyi-plus-soybean/src/layouts/modules/global-header/components/user-avatar.vue
new file mode 100755
index 0000000..9cc2971
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-header/components/user-avatar.vue
@@ -0,0 +1,142 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import type { VNode } from 'vue';
+import { useBoolean } from '@sa/hooks';
+import { useAuthStore } from '@/store/modules/auth';
+import { useRouterPush } from '@/hooks/common/router';
+import { useSvgIcon } from '@/hooks/common/icon';
+import defaultAvatar from '@/assets/imgs/soybean.jpg';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'UserAvatar'
+});
+
+const authStore = useAuthStore();
+const { routerPushByKey, toLogin } = useRouterPush();
+const { SvgIconVNode } = useSvgIcon();
+
+const { bool: avatarError, setTrue: setError, setFalse: clearError } = useBoolean(false);
+
+function loginOrRegister() {
+ toLogin();
+}
+
+function handleAvatarLoad() {
+ clearError();
+}
+
+function handleAvatarError() {
+ setError();
+}
+
+type DropdownKey = 'user-center' | 'logout';
+
+type DropdownOption =
+ | {
+ key: DropdownKey;
+ label: string;
+ icon?: () => VNode;
+ }
+ | {
+ type: 'divider';
+ key: string;
+ };
+
+const options = computed(() => {
+ const opts: DropdownOption[] = [
+ {
+ label: $t('common.userCenter'),
+ key: 'user-center',
+ icon: SvgIconVNode({ icon: 'ph:user-circle', fontSize: 18 })
+ },
+ {
+ type: 'divider',
+ key: 'divider'
+ },
+ {
+ label: $t('common.logout'),
+ key: 'logout',
+ icon: SvgIconVNode({ icon: 'ph:sign-out', fontSize: 18 })
+ }
+ ];
+ return opts;
+});
+
+function logout() {
+ window.$dialog?.info({
+ title: $t('common.tip'),
+ content: $t('common.logoutConfirm'),
+ positiveText: $t('common.confirm'),
+ negativeText: $t('common.cancel'),
+ onPositiveClick: () => {
+ authStore.logout();
+ }
+ });
+}
+
+function handleDropdown(key: DropdownKey) {
+ if (key === 'logout') {
+ logout();
+ } else {
+ routerPushByKey(key);
+ }
+}
+</script>
+
+<template>
+ <NButton v-if="!authStore.isLogin" quaternary @click="loginOrRegister">
+ {{ $t('page.login.common.loginOrRegister') }}
+ </NButton>
+ <NDropdown v-else placement="bottom" trigger="click" :options="options" @select="handleDropdown">
+ <div class="flex cursor-pointer items-center rounded-md px-2 py-1 transition-colors duration-300 hover:bg-black/6">
+ <div class="flex items-center gap-2" :class="{ 'opacity-50': avatarError }">
+ <NAvatar
+ v-if="authStore.userInfo.user?.avatar"
+ :size="24"
+ round
+ :src="authStore.userInfo.user?.avatar"
+ @load="handleAvatarLoad"
+ @error="handleAvatarError"
+ />
+ <NAvatar v-else :size="32" round :src="defaultAvatar" @load="handleAvatarLoad" @error="handleAvatarError" />
+ <span class="max-w-120px truncate text-14px font-medium">
+ {{ authStore.userInfo.user?.nickName }}
+ </span>
+ </div>
+ </div>
+ </NDropdown>
+</template>
+
+<style lang="scss" scoped>
+.avatar-wrapper {
+ display: flex;
+ align-items: center;
+ padding: 4px 8px;
+ border-radius: 6px;
+ transition: all 0.3s ease;
+ cursor: pointer;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.06);
+ }
+}
+
+.avatar-container {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ &.avatar-error {
+ opacity: 0.5;
+ }
+}
+
+.user-name {
+ font-size: 14px;
+ max-width: 120px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-header/index.vue b/ruoyi-plus-soybean/src/layouts/modules/global-header/index.vue
new file mode 100755
index 0000000..786047b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-header/index.vue
@@ -0,0 +1,68 @@
+<script setup lang="ts">
+import { ref } from 'vue';
+import { useFullscreen } from '@vueuse/core';
+import { GLOBAL_HEADER_MENU_ID } from '@/constants/app';
+import { useAppStore } from '@/store/modules/app';
+import { useAuthStore } from '@/store/modules/auth';
+import { useThemeStore } from '@/store/modules/theme';
+import GlobalLogo from '../global-logo/index.vue';
+import GlobalBreadcrumb from '../global-breadcrumb/index.vue';
+import GlobalSearch from '../global-search/index.vue';
+import ThemeButton from './components/theme-button.vue';
+import UserAvatar from './components/user-avatar.vue';
+import MessageButton from './components/message-button.vue';
+
+defineOptions({
+ name: 'GlobalHeader'
+});
+
+interface Props {
+ /** Whether to show the logo */
+ showLogo?: App.Global.HeaderProps['showLogo'];
+ /** Whether to show the menu toggler */
+ showMenuToggler?: App.Global.HeaderProps['showMenuToggler'];
+ /** Whether to show the menu */
+ showMenu?: App.Global.HeaderProps['showMenu'];
+}
+
+defineProps<Props>();
+
+const appStore = useAppStore();
+const authStore = useAuthStore();
+const themeStore = useThemeStore();
+const { isFullscreen, toggle } = useFullscreen();
+
+const tenantId = ref<CommonType.IdType>(authStore.userInfo?.user?.tenantId || '000000');
+</script>
+
+<template>
+ <DarkModeContainer class="h-full flex-y-center px-12px shadow-header">
+ <GlobalLogo v-if="showLogo" class="h-full" :style="{ width: themeStore.sider.width + 'px' }" />
+ <MenuToggler v-if="showMenuToggler" :collapsed="appStore.siderCollapse" @click="appStore.toggleSiderCollapse" />
+ <div v-if="showMenu" :id="GLOBAL_HEADER_MENU_ID" class="h-full flex-y-center flex-1-hidden"></div>
+ <div v-else class="h-full flex-y-center flex-1-hidden">
+ <GlobalBreadcrumb v-if="!appStore.isMobile" class="ml-12px" />
+ </div>
+ <div class="h-full flex-y-center justify-end">
+ <TenantSelect v-if="!appStore.isMobile" v-model:value="tenantId" class="mr-12px w-150px" />
+ <GlobalSearch v-if="themeStore.header.globalSearch.visible && !appStore.isMobile" />
+ <MessageButton />
+ <FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
+ <LangSwitch
+ v-if="themeStore.header.multilingual.visible"
+ :lang="appStore.locale"
+ :lang-options="appStore.localeOptions"
+ @change-lang="appStore.changeLocale"
+ />
+ <ThemeSchemaSwitch
+ :theme-schema="themeStore.themeScheme"
+ :is-dark="themeStore.darkMode"
+ @switch="themeStore.toggleThemeScheme"
+ />
+ <ThemeButton v-if="!appStore.isMobile" />
+ <UserAvatar />
+ </div>
+ </DarkModeContainer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-logo/index.vue b/ruoyi-plus-soybean/src/layouts/modules/global-logo/index.vue
new file mode 100755
index 0000000..1f854c3
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-logo/index.vue
@@ -0,0 +1,27 @@
+<script setup lang="ts">
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'GlobalLogo'
+});
+
+interface Props {
+ /** Whether to show the title */
+ showTitle?: boolean;
+}
+
+withDefaults(defineProps<Props>(), {
+ showTitle: true
+});
+</script>
+
+<template>
+ <RouterLink to="/" class="w-full flex-center nowrap-hidden">
+ <SystemLogo class="fill-primary text-30px" />
+ <h2 v-show="showTitle" class="pl-12px text-16px text-primary font-bold transition duration-300 ease-in-out">
+ {{ $t('system.title') }}
+ </h2>
+ </RouterLink>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-menu/components/first-level-menu.vue b/ruoyi-plus-soybean/src/layouts/modules/global-menu/components/first-level-menu.vue
new file mode 100755
index 0000000..d0a6fdf
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-menu/components/first-level-menu.vue
@@ -0,0 +1,109 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { createReusableTemplate } from '@vueuse/core';
+import { SimpleScrollbar } from '@sa/materials';
+import { transformColorWithOpacity } from '@sa/color';
+import type { RouteKey } from '@elegant-router/types';
+
+defineOptions({
+ name: 'FirstLevelMenu'
+});
+
+interface Props {
+ menus: App.Global.Menu[];
+ activeMenuKey?: string;
+ inverted?: boolean;
+ siderCollapse?: boolean;
+ darkMode?: boolean;
+ themeColor: string;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'select', menuKey: RouteKey): boolean;
+ (e: 'toggleSiderCollapse'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+interface MixMenuItemProps {
+ /** Menu item label */
+ label: App.Global.Menu['label'];
+ /** Menu item icon */
+ icon: App.Global.Menu['icon'];
+ /** Active menu item */
+ active: boolean;
+ /** Mini size */
+ isMini?: boolean;
+}
+const [DefineMixMenuItem, MixMenuItem] = createReusableTemplate<MixMenuItemProps>();
+
+const selectedBgColor = computed(() => {
+ const { darkMode, themeColor } = props;
+
+ const light = transformColorWithOpacity(themeColor, 0.1, '#ffffff');
+ const dark = transformColorWithOpacity(themeColor, 0.3, '#000000');
+
+ return darkMode ? dark : light;
+});
+
+function handleClickMixMenu(menuKey: RouteKey) {
+ emit('select', menuKey);
+}
+
+function toggleSiderCollapse() {
+ emit('toggleSiderCollapse');
+}
+</script>
+
+<template>
+ <!-- define component: MixMenuItem -->
+ <DefineMixMenuItem v-slot="{ label, icon, active, isMini }">
+ <div
+ class="mx-4px mb-6px flex-col-center cursor-pointer rounded-8px bg-transparent px-4px py-8px transition-300 hover:bg-[rgb(0,0,0,0.08)]"
+ :class="{
+ 'text-primary selected-mix-menu': active,
+ 'text-white:65 hover:text-white': inverted,
+ '!text-white !bg-primary': active && inverted
+ }"
+ >
+ <component :is="icon" :class="[isMini ? 'text-icon-small' : 'text-icon-large']" />
+ <p
+ class="w-full ellipsis-text text-center text-12px transition-height-300"
+ :class="[isMini ? 'h-0 pt-0' : 'h-20px pt-4px']"
+ >
+ {{ label }}
+ </p>
+ </div>
+ </DefineMixMenuItem>
+ <!-- define component end: MixMenuItem -->
+
+ <div class="h-full flex-col-stretch flex-1-hidden">
+ <slot></slot>
+ <SimpleScrollbar>
+ <MixMenuItem
+ v-for="menu in menus"
+ :key="menu.key"
+ :label="menu.label"
+ :icon="menu.icon"
+ :active="menu.key === activeMenuKey"
+ :is-mini="siderCollapse"
+ @click="handleClickMixMenu(menu.routeKey)"
+ />
+ </SimpleScrollbar>
+ <MenuToggler
+ arrow-icon
+ :collapsed="siderCollapse"
+ :z-index="99"
+ :class="{ 'text-white:88 !hover:text-white': inverted }"
+ @click="toggleSiderCollapse"
+ />
+ </div>
+</template>
+
+<style scoped>
+.selected-mix-menu {
+ background-color: v-bind(selectedBgColor);
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-menu/context/index.ts b/ruoyi-plus-soybean/src/layouts/modules/global-menu/context/index.ts
new file mode 100755
index 0000000..351b5f1
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-menu/context/index.ts
@@ -0,0 +1,215 @@
+import { computed, ref, watch } from 'vue';
+import { useRoute } from 'vue-router';
+import { useContext } from '@sa/hooks';
+import type { RouteKey } from '@elegant-router/types';
+import { useRouteStore } from '@/store/modules/route';
+import { useThemeStore } from '@/store/modules/theme';
+import { useRouterPush } from '@/hooks/common/router';
+
+export const [provideMixMenuContext, useMixMenuContext] = useContext('MixMenu', useMixMenu);
+
+function useMixMenu() {
+ const route = useRoute();
+ const routeStore = useRouteStore();
+ const themeStore = useThemeStore();
+ const { selectedKey } = useMenu();
+ const { routerPushByKeyWithMetaQuery } = useRouterPush();
+
+ const allMenus = computed<App.Global.Menu[]>(() => routeStore.menus);
+
+ const firstLevelMenus = computed<App.Global.Menu[]>(() =>
+ routeStore.menus.map(menu => {
+ const { children: _, ...rest } = menu;
+
+ return rest;
+ })
+ );
+
+ const activeFirstLevelMenuKey = ref('');
+
+ function setActiveFirstLevelMenuKey(key: string) {
+ activeFirstLevelMenuKey.value = key;
+ }
+
+ function getActiveFirstLevelMenuKey() {
+ const currentKey = selectedKey.value;
+
+ const firstLevelMenu = allMenus.value.find(menu => {
+ if (menu.key === currentKey) {
+ return true;
+ }
+
+ if (menu.children && menu.children.length > 0) {
+ return findMenuByKey(menu.children, currentKey);
+ }
+
+ return false;
+ });
+
+ if (firstLevelMenu) {
+ setActiveFirstLevelMenuKey(firstLevelMenu.key);
+ } else {
+ const [firstLevelRouteName] = currentKey.split('_');
+ setActiveFirstLevelMenuKey(firstLevelRouteName);
+ }
+ }
+
+ function findMenuByKey(menus: App.Global.Menu[], key: string): boolean {
+ return menus.some(menu => {
+ if (menu.key === key) {
+ return true;
+ }
+
+ if (menu.children && menu.children.length > 0) {
+ return findMenuByKey(menu.children, key);
+ }
+
+ return false;
+ });
+ }
+
+ const isActiveFirstLevelMenuHasChildren = computed(() => {
+ if (!activeFirstLevelMenuKey.value) {
+ return false;
+ }
+
+ const findItem = allMenus.value.find(item => item.key === activeFirstLevelMenuKey.value);
+
+ return Boolean(findItem?.children?.length);
+ });
+
+ function handleSelectFirstLevelMenu(key: RouteKey) {
+ setActiveFirstLevelMenuKey(key);
+
+ if (!isActiveFirstLevelMenuHasChildren.value) {
+ routerPushByKeyWithMetaQuery(key);
+ }
+ }
+
+ const secondLevelMenus = computed<App.Global.Menu[]>(
+ () => allMenus.value.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || []
+ );
+
+ const activeSecondLevelMenuKey = ref('');
+
+ function setActiveSecondLevelMenuKey(key: string) {
+ activeSecondLevelMenuKey.value = key;
+ }
+
+ function getActiveSecondLevelMenuKey() {
+ const keys = selectedKey.value.split('_');
+
+ if (keys.length < 2) {
+ setActiveSecondLevelMenuKey('');
+ return;
+ }
+
+ const [firstLevelRouteName, level2SuffixName] = keys;
+
+ const secondLevelRouteName = `${firstLevelRouteName}_${level2SuffixName}`;
+
+ setActiveSecondLevelMenuKey(secondLevelRouteName);
+ }
+
+ const isActiveSecondLevelMenuHasChildren = computed(() => {
+ if (!activeSecondLevelMenuKey.value) {
+ return false;
+ }
+
+ const findItem = secondLevelMenus.value.find(item => item.key === activeSecondLevelMenuKey.value);
+
+ return Boolean(findItem?.children?.length);
+ });
+
+ function handleSelectSecondLevelMenu(key: RouteKey) {
+ setActiveSecondLevelMenuKey(key);
+
+ if (!isActiveSecondLevelMenuHasChildren.value) {
+ routerPushByKeyWithMetaQuery(key);
+ }
+ }
+
+ const childLevelMenus = computed<App.Global.Menu[]>(
+ () => secondLevelMenus.value.find(menu => menu.key === activeSecondLevelMenuKey.value)?.children || []
+ );
+
+ const hasChildLevelMenus = computed(() => childLevelMenus.value.length > 0);
+
+ function getDeepestLevelMenuKey(): RouteKey | null {
+ if (!secondLevelMenus.value.length || !themeStore.sider.autoSelectFirstMenu) {
+ return null;
+ }
+
+ const secondLevelFirstMenu = secondLevelMenus.value[0];
+
+ if (!secondLevelFirstMenu) {
+ return null;
+ }
+
+ function findDeepest(menu: App.Global.Menu): RouteKey {
+ if (!menu.children?.length) {
+ return menu.routeKey;
+ }
+
+ return findDeepest(menu.children[0]);
+ }
+
+ return findDeepest(secondLevelFirstMenu);
+ }
+
+ function activeDeepestLevelMenuKey() {
+ const deepestLevelMenuKey = getDeepestLevelMenuKey();
+ if (!deepestLevelMenuKey) return;
+
+ // select the deepest second level menu
+ handleSelectSecondLevelMenu(deepestLevelMenuKey);
+ }
+
+ watch(
+ () => route.name,
+ () => {
+ getActiveFirstLevelMenuKey();
+ // if there are child level menus, get the active second level menu key
+ if (hasChildLevelMenus.value) {
+ getActiveSecondLevelMenuKey();
+ }
+ },
+ { immediate: true }
+ );
+
+ return {
+ firstLevelMenus,
+ activeFirstLevelMenuKey,
+ setActiveFirstLevelMenuKey,
+ isActiveFirstLevelMenuHasChildren,
+ handleSelectFirstLevelMenu,
+ getActiveFirstLevelMenuKey,
+ secondLevelMenus,
+ activeSecondLevelMenuKey,
+ setActiveSecondLevelMenuKey,
+ isActiveSecondLevelMenuHasChildren,
+ handleSelectSecondLevelMenu,
+ getActiveSecondLevelMenuKey,
+ childLevelMenus,
+ hasChildLevelMenus,
+ getDeepestLevelMenuKey,
+ activeDeepestLevelMenuKey
+ };
+}
+
+export function useMenu() {
+ const route = useRoute();
+
+ const selectedKey = computed(() => {
+ const { hideInMenu, activeMenu } = route.meta;
+ const name = route.name as string;
+
+ const routeName = (hideInMenu ? activeMenu : name) || name;
+
+ return routeName;
+ });
+
+ return {
+ selectedKey
+ };
+}
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-menu/index.vue b/ruoyi-plus-soybean/src/layouts/modules/global-menu/index.vue
new file mode 100755
index 0000000..571e19f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-menu/index.vue
@@ -0,0 +1,40 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import type { Component } from 'vue';
+import { useAppStore } from '@/store/modules/app';
+import { useThemeStore } from '@/store/modules/theme';
+import VerticalMenu from './modules/vertical-menu.vue';
+import VerticalMixMenu from './modules/vertical-mix-menu.vue';
+import VerticalHybridHeaderFirst from './modules/vertical-hybrid-header-first.vue';
+import HorizontalMenu from './modules/horizontal-menu.vue';
+import TopHybridSidebarFirst from './modules/top-hybrid-sidebar-first.vue';
+import TopHybridHeaderFirst from './modules/top-hybrid-header-first.vue';
+
+defineOptions({
+ name: 'GlobalMenu'
+});
+
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+
+const activeMenu = computed(() => {
+ const menuMap: Record<UnionKey.ThemeLayoutMode, Component> = {
+ vertical: VerticalMenu,
+ 'vertical-mix': VerticalMixMenu,
+ 'vertical-hybrid-header-first': VerticalHybridHeaderFirst,
+ horizontal: HorizontalMenu,
+ 'top-hybrid-sidebar-first': TopHybridSidebarFirst,
+ 'top-hybrid-header-first': TopHybridHeaderFirst
+ };
+
+ return menuMap[themeStore.layout.mode];
+});
+
+const reRenderVertical = computed(() => themeStore.layout.mode === 'vertical' && appStore.isMobile);
+</script>
+
+<template>
+ <component :is="activeMenu" :key="reRenderVertical" />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/horizontal-menu.vue b/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/horizontal-menu.vue
new file mode 100755
index 0000000..b696a30
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/horizontal-menu.vue
@@ -0,0 +1,29 @@
+<script setup lang="ts">
+import { GLOBAL_HEADER_MENU_ID } from '@/constants/app';
+import { useRouteStore } from '@/store/modules/route';
+import { useRouterPush } from '@/hooks/common/router';
+import { useMenu } from '../context';
+
+defineOptions({
+ name: 'HorizontalMenu'
+});
+
+const routeStore = useRouteStore();
+const { routerPushByKeyWithMetaQuery } = useRouterPush();
+const { selectedKey } = useMenu();
+</script>
+
+<template>
+ <Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
+ <NMenu
+ mode="horizontal"
+ :value="selectedKey"
+ :options="routeStore.menus"
+ :indent="18"
+ responsive
+ @update:value="routerPushByKeyWithMetaQuery"
+ />
+ </Teleport>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/top-hybrid-header-first.vue b/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/top-hybrid-header-first.vue
new file mode 100755
index 0000000..b312f42
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/top-hybrid-header-first.vue
@@ -0,0 +1,89 @@
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+import { useRoute } from 'vue-router';
+import { SimpleScrollbar } from '@sa/materials';
+import type { RouteKey } from '@elegant-router/types';
+import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
+import { useAppStore } from '@/store/modules/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { useRouteStore } from '@/store/modules/route';
+import { useRouterPush } from '@/hooks/common/router';
+import { useMenu, useMixMenuContext } from '../context';
+
+defineOptions({
+ name: 'TopHybridHeaderFirst'
+});
+
+const route = useRoute();
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+const routeStore = useRouteStore();
+const { routerPushByKeyWithMetaQuery } = useRouterPush();
+const {
+ firstLevelMenus,
+ secondLevelMenus,
+ activeFirstLevelMenuKey,
+ handleSelectFirstLevelMenu,
+ activeDeepestLevelMenuKey
+} = useMixMenuContext('TopHybridHeaderFirst');
+const { selectedKey } = useMenu();
+
+const expandedKeys = ref<string[]>([]);
+
+/**
+ * Handle first level menu select
+ * @param key RouteKey
+ */
+function handleSelectMenu(key: RouteKey) {
+ handleSelectFirstLevelMenu(key);
+
+ // if there are second level menus, select the deepest one by default
+ activeDeepestLevelMenuKey();
+}
+
+function updateExpandedKeys() {
+ if (appStore.siderCollapse || !selectedKey.value) {
+ expandedKeys.value = [];
+ return;
+ }
+ expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
+}
+
+watch(
+ () => route.name,
+ () => {
+ updateExpandedKeys();
+ },
+ { immediate: true }
+);
+</script>
+
+<template>
+ <Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
+ <NMenu
+ mode="horizontal"
+ :value="activeFirstLevelMenuKey"
+ :options="firstLevelMenus"
+ :indent="18"
+ responsive
+ @update:value="handleSelectMenu"
+ />
+ </Teleport>
+ <Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
+ <SimpleScrollbar>
+ <NMenu
+ v-model:expanded-keys="expandedKeys"
+ mode="vertical"
+ :value="selectedKey"
+ :collapsed="appStore.siderCollapse"
+ :collapsed-width="themeStore.sider.collapsedWidth"
+ :collapsed-icon-size="22"
+ :options="secondLevelMenus"
+ :indent="18"
+ @update:value="routerPushByKeyWithMetaQuery"
+ />
+ </SimpleScrollbar>
+ </Teleport>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/top-hybrid-sidebar-first.vue b/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/top-hybrid-sidebar-first.vue
new file mode 100755
index 0000000..d12189e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/top-hybrid-sidebar-first.vue
@@ -0,0 +1,64 @@
+<script setup lang="ts">
+import type { RouteKey } from '@elegant-router/types';
+import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
+import { useAppStore } from '@/store/modules/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { useRouterPush } from '@/hooks/common/router';
+import FirstLevelMenu from '../components/first-level-menu.vue';
+import { useMenu, useMixMenuContext } from '../context';
+
+defineOptions({
+ name: 'TopHybridSidebarFirst'
+});
+
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+const { routerPushByKeyWithMetaQuery } = useRouterPush();
+const {
+ firstLevelMenus,
+ secondLevelMenus,
+ activeFirstLevelMenuKey,
+ handleSelectFirstLevelMenu,
+ activeDeepestLevelMenuKey
+} = useMixMenuContext('TopHybridSidebarFirst');
+const { selectedKey } = useMenu();
+
+/**
+ * Handle first level menu select
+ * @param key RouteKey
+ */
+function handleSelectMenu(key: RouteKey) {
+ handleSelectFirstLevelMenu(key);
+
+ // if there are second level menus, select the deepest one by default
+ activeDeepestLevelMenuKey();
+}
+</script>
+
+<template>
+ <Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
+ <NMenu
+ mode="horizontal"
+ :value="selectedKey"
+ :options="secondLevelMenus"
+ :indent="18"
+ responsive
+ @update:value="routerPushByKeyWithMetaQuery"
+ />
+ </Teleport>
+ <Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
+ <div class="h-full pt-2">
+ <FirstLevelMenu
+ :menus="firstLevelMenus"
+ :active-menu-key="activeFirstLevelMenuKey"
+ :sider-collapse="appStore.siderCollapse"
+ :dark-mode="themeStore.darkMode"
+ :theme-color="themeStore.themeColor"
+ @select="handleSelectMenu"
+ @toggle-sider-collapse="appStore.toggleSiderCollapse"
+ />
+ </div>
+ </Teleport>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/vertical-hybrid-header-first.vue b/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/vertical-hybrid-header-first.vue
new file mode 100755
index 0000000..a689fd4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/vertical-hybrid-header-first.vue
@@ -0,0 +1,173 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { useRoute } from 'vue-router';
+import type { RouteKey } from '@elegant-router/types';
+import { SimpleScrollbar } from '@sa/materials';
+import { useBoolean } from '@sa/hooks';
+import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
+import { useAppStore } from '@/store/modules/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { useRouteStore } from '@/store/modules/route';
+import { useRouterPush } from '@/hooks/common/router';
+import { $t } from '@/locales';
+import { useMenu, useMixMenuContext } from '../context';
+import FirstLevelMenu from '../components/first-level-menu.vue';
+import GlobalLogo from '../../global-logo/index.vue';
+
+defineOptions({
+ name: 'VerticalHybridHeaderFirst'
+});
+
+const route = useRoute();
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+const routeStore = useRouteStore();
+const { routerPushByKeyWithMetaQuery } = useRouterPush();
+const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
+const {
+ firstLevelMenus,
+ activeFirstLevelMenuKey,
+ handleSelectFirstLevelMenu,
+ getActiveFirstLevelMenuKey,
+ secondLevelMenus,
+ activeSecondLevelMenuKey,
+ isActiveSecondLevelMenuHasChildren,
+ handleSelectSecondLevelMenu,
+ getActiveSecondLevelMenuKey,
+ childLevelMenus,
+ hasChildLevelMenus,
+ activeDeepestLevelMenuKey
+} = useMixMenuContext('VerticalHybridHeaderFirst');
+const { selectedKey } = useMenu();
+
+const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
+
+const showDrawer = computed(() => hasChildLevelMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
+
+function handleSelectMixMenu(key: RouteKey) {
+ handleSelectSecondLevelMenu(key);
+
+ if (isActiveSecondLevelMenuHasChildren.value) {
+ setDrawerVisible(true);
+ }
+}
+
+/**
+ * Handle second level menu selection based on autoSelectFirstMenu setting:
+ * - When disabled: Activate first second-level menu for display only, expand third-level menu if exists
+ * - When enabled: Navigate to the deepest menu automatically
+ */
+function handleSelectMenu(key: RouteKey) {
+ handleSelectFirstLevelMenu(key);
+
+ if (secondLevelMenus.value.length === 0) return;
+
+ const secondFirstMenuKey = secondLevelMenus.value[0].routeKey;
+
+ // Case 1: autoSelectFirstMenu disabled - only activate menu for display
+ if (!themeStore.sider.autoSelectFirstMenu) {
+ // Check if there are third-level menus
+ const hasChildren = secondLevelMenus.value.find(menu => menu.key === secondFirstMenuKey)?.children?.length;
+
+ // If there are third-level menus, expand them
+ if (hasChildren) {
+ handleSelectMixMenu(secondFirstMenuKey);
+ }
+ return;
+ }
+
+ // Case 2: autoSelectFirstMenu enabled - navigate to deepest menu
+ activeDeepestLevelMenuKey();
+ setDrawerVisible(false);
+}
+
+function handleResetActiveMenu() {
+ setDrawerVisible(false);
+
+ if (!appStore.mixSiderFixed) {
+ getActiveFirstLevelMenuKey();
+ getActiveSecondLevelMenuKey();
+ }
+}
+
+const expandedKeys = ref<string[]>([]);
+
+function updateExpandedKeys() {
+ if (appStore.siderCollapse || !selectedKey.value) {
+ expandedKeys.value = [];
+ return;
+ }
+ expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
+}
+
+watch(
+ () => route.name,
+ () => {
+ updateExpandedKeys();
+ },
+ { immediate: true }
+);
+</script>
+
+<template>
+ <Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
+ <NMenu
+ mode="horizontal"
+ :value="activeFirstLevelMenuKey"
+ :options="firstLevelMenus"
+ :indent="18"
+ responsive
+ @update:value="handleSelectMenu"
+ />
+ </Teleport>
+ <Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
+ <div class="h-full flex" @mouseleave="handleResetActiveMenu">
+ <FirstLevelMenu
+ :menus="secondLevelMenus"
+ :active-menu-key="activeSecondLevelMenuKey"
+ :inverted="inverted"
+ :sider-collapse="appStore.siderCollapse"
+ :dark-mode="themeStore.darkMode"
+ :theme-color="themeStore.themeColor"
+ @select="handleSelectMixMenu"
+ @toggle-sider-collapse="appStore.toggleSiderCollapse"
+ >
+ <GlobalLogo :show-title="false" :style="{ height: themeStore.header.height + 'px' }" />
+ </FirstLevelMenu>
+ <div
+ class="relative h-full transition-width-300"
+ :style="{
+ width: appStore.mixSiderFixed && hasChildLevelMenus ? themeStore.sider.mixChildMenuWidth + 'px' : '0px'
+ }"
+ >
+ <DarkModeContainer
+ class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300"
+ :inverted="inverted"
+ :style="{ width: showDrawer ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
+ >
+ <header class="flex-y-center justify-between px-12px" :style="{ height: themeStore.header.height + 'px' }">
+ <h2 class="text-16px text-primary font-bold">{{ $t('system.title') }}</h2>
+ <PinToggler
+ :pin="appStore.mixSiderFixed"
+ :class="{ 'text-white:88 !hover:text-white': inverted }"
+ @click="appStore.toggleMixSiderFixed"
+ />
+ </header>
+ <SimpleScrollbar>
+ <NMenu
+ v-model:expanded-keys="expandedKeys"
+ mode="vertical"
+ :value="selectedKey"
+ :options="childLevelMenus"
+ :inverted="inverted"
+ :indent="18"
+ @update:value="routerPushByKeyWithMetaQuery"
+ />
+ </SimpleScrollbar>
+ </DarkModeContainer>
+ </div>
+ </div>
+ </Teleport>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/vertical-menu.vue b/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/vertical-menu.vue
new file mode 100755
index 0000000..d42275b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/vertical-menu.vue
@@ -0,0 +1,63 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { useRoute } from 'vue-router';
+import { SimpleScrollbar } from '@sa/materials';
+import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
+import { useAppStore } from '@/store/modules/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { useRouteStore } from '@/store/modules/route';
+import { useRouterPush } from '@/hooks/common/router';
+import { useMenu } from '../context';
+
+defineOptions({
+ name: 'VerticalMenu'
+});
+
+const route = useRoute();
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+const routeStore = useRouteStore();
+const { routerPushByKeyWithMetaQuery } = useRouterPush();
+const { selectedKey } = useMenu();
+
+const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
+
+const expandedKeys = ref<string[]>([]);
+
+function updateExpandedKeys() {
+ if (appStore.siderCollapse || !selectedKey.value) {
+ expandedKeys.value = [];
+ return;
+ }
+ expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
+}
+
+watch(
+ () => route.name,
+ () => {
+ updateExpandedKeys();
+ },
+ { immediate: true }
+);
+</script>
+
+<template>
+ <Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
+ <SimpleScrollbar>
+ <NMenu
+ v-model:expanded-keys="expandedKeys"
+ mode="vertical"
+ :value="selectedKey"
+ :collapsed="appStore.siderCollapse"
+ :collapsed-width="themeStore.sider.collapsedWidth"
+ :collapsed-icon-size="22"
+ :options="routeStore.menus"
+ :inverted="inverted"
+ :indent="18"
+ @update:value="routerPushByKeyWithMetaQuery"
+ />
+ </SimpleScrollbar>
+ </Teleport>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/vertical-mix-menu.vue b/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/vertical-mix-menu.vue
new file mode 100755
index 0000000..9d60652
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-menu/modules/vertical-mix-menu.vue
@@ -0,0 +1,127 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { useRoute } from 'vue-router';
+import { SimpleScrollbar } from '@sa/materials';
+import { useBoolean } from '@sa/hooks';
+import type { RouteKey } from '@elegant-router/types';
+import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
+import { useAppStore } from '@/store/modules/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { useRouteStore } from '@/store/modules/route';
+import { useRouterPush } from '@/hooks/common/router';
+import { $t } from '@/locales';
+import { useMenu, useMixMenuContext } from '../context';
+import FirstLevelMenu from '../components/first-level-menu.vue';
+import GlobalLogo from '../../global-logo/index.vue';
+
+defineOptions({
+ name: 'VerticalMixMenu'
+});
+
+const route = useRoute();
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+const routeStore = useRouteStore();
+const { routerPushByKeyWithMetaQuery } = useRouterPush();
+const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
+const {
+ firstLevelMenus,
+ secondLevelMenus,
+ activeFirstLevelMenuKey,
+ isActiveFirstLevelMenuHasChildren,
+ getActiveFirstLevelMenuKey,
+ handleSelectFirstLevelMenu
+} = useMixMenuContext('VerticalMixMenu');
+const { selectedKey } = useMenu();
+
+const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
+
+const hasChildMenus = computed(() => secondLevelMenus.value.length > 0);
+
+const showDrawer = computed(() => hasChildMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
+
+function handleSelectMenu(key: RouteKey) {
+ handleSelectFirstLevelMenu(key);
+
+ if (isActiveFirstLevelMenuHasChildren.value) {
+ setDrawerVisible(true);
+ }
+}
+
+function handleResetActiveMenu() {
+ setDrawerVisible(false);
+
+ if (!appStore.mixSiderFixed) {
+ getActiveFirstLevelMenuKey();
+ }
+}
+
+const expandedKeys = ref<string[]>([]);
+
+function updateExpandedKeys() {
+ if (appStore.siderCollapse || !selectedKey.value) {
+ expandedKeys.value = [];
+ return;
+ }
+ expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
+}
+
+watch(
+ () => route.name,
+ () => {
+ updateExpandedKeys();
+ },
+ { immediate: true }
+);
+</script>
+
+<template>
+ <Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
+ <div class="h-full flex" @mouseleave="handleResetActiveMenu">
+ <FirstLevelMenu
+ :menus="firstLevelMenus"
+ :active-menu-key="activeFirstLevelMenuKey"
+ :inverted="inverted"
+ :sider-collapse="appStore.siderCollapse"
+ :dark-mode="themeStore.darkMode"
+ :theme-color="themeStore.themeColor"
+ @select="handleSelectMenu"
+ @toggle-sider-collapse="appStore.toggleSiderCollapse"
+ >
+ <GlobalLogo :show-title="false" :style="{ height: themeStore.header.height + 'px' }" />
+ </FirstLevelMenu>
+ <div
+ class="relative h-full transition-width-300"
+ :style="{ width: appStore.mixSiderFixed && hasChildMenus ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
+ >
+ <DarkModeContainer
+ class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300"
+ :inverted="inverted"
+ :style="{ width: showDrawer ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
+ >
+ <header class="flex-y-center justify-between px-12px" :style="{ height: themeStore.header.height + 'px' }">
+ <h2 class="text-16px text-primary font-bold">{{ $t('system.title') }}</h2>
+ <PinToggler
+ :pin="appStore.mixSiderFixed"
+ :class="{ 'text-white:88 !hover:text-white': inverted }"
+ @click="appStore.toggleMixSiderFixed"
+ />
+ </header>
+ <SimpleScrollbar>
+ <NMenu
+ v-model:expanded-keys="expandedKeys"
+ mode="vertical"
+ :value="selectedKey"
+ :options="secondLevelMenus"
+ :inverted="inverted"
+ :indent="18"
+ @update:value="routerPushByKeyWithMetaQuery"
+ />
+ </SimpleScrollbar>
+ </DarkModeContainer>
+ </div>
+ </div>
+ </Teleport>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-search/components/search-footer.vue b/ruoyi-plus-soybean/src/layouts/modules/global-search/components/search-footer.vue
new file mode 100755
index 0000000..0fa6c8f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-search/components/search-footer.vue
@@ -0,0 +1,36 @@
+<script lang="ts" setup>
+import { $t } from '@/locales';
+
+defineOptions({ name: 'SearchFooter' });
+</script>
+
+<template>
+ <div class="h-44px flex-y-center gap-14px px-24px">
+ <span class="flex-y-center">
+ <icon-mdi-keyboard-return class="operate-shadow operate-item" />
+ <span>{{ $t('common.confirm') }}</span>
+ </span>
+ <span class="flex-y-center">
+ <icon-mdi-arrow-up-thin class="operate-shadow operate-item" />
+ <icon-mdi-arrow-down-thin class="operate-shadow operate-item" />
+ <span>{{ $t('common.switch') }}</span>
+ </span>
+ <span class="flex-y-center">
+ <icon-mdi-keyboard-esc class="operate-shadow operate-item" />
+ <span>{{ $t('common.close') }}</span>
+ </span>
+ </div>
+</template>
+
+<style lang="scss" scoped>
+.operate-shadow {
+ box-shadow:
+ inset 0 -2px #cdcde6,
+ inset 0 0 1px 1px #fff,
+ 0 1px 2px 1px #1e235a66;
+}
+
+.operate-item {
+ --uno: mr-6px p-2px text-20px;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-search/components/search-modal.vue b/ruoyi-plus-soybean/src/layouts/modules/global-search/components/search-modal.vue
new file mode 100755
index 0000000..39f18a8
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-search/components/search-modal.vue
@@ -0,0 +1,123 @@
+<script lang="ts" setup>
+import { computed, ref, shallowRef } from 'vue';
+import { useRouter } from 'vue-router';
+import { onKeyStroke, useDebounceFn } from '@vueuse/core';
+import { useRouteStore } from '@/store/modules/route';
+import { useAppStore } from '@/store/modules/app';
+import { $t } from '@/locales';
+import SearchResult from './search-result.vue';
+import SearchFooter from './search-footer.vue';
+
+defineOptions({ name: 'SearchModal' });
+
+const router = useRouter();
+const appStore = useAppStore();
+const routeStore = useRouteStore();
+
+const isMobile = computed(() => appStore.isMobile);
+
+const keyword = ref('');
+const activePath = ref('');
+const resultOptions = shallowRef<App.Global.Menu[]>([]);
+
+const handleSearch = useDebounceFn(search, 300);
+
+const visible = defineModel<boolean>('show', { required: true });
+
+function search() {
+ resultOptions.value = routeStore.searchMenus.filter(menu => {
+ const trimKeyword = keyword.value.toLocaleLowerCase().trim();
+ const title = (menu.i18nKey ? $t(menu.i18nKey) : menu.label).toLocaleLowerCase();
+ return trimKeyword && title.includes(trimKeyword);
+ });
+ activePath.value = resultOptions.value[0]?.routePath ?? '';
+}
+
+function handleClose() {
+ // handle with setTimeout to prevent user from seeing some operations
+ setTimeout(() => {
+ visible.value = false;
+ resultOptions.value = [];
+ keyword.value = '';
+ }, 200);
+}
+
+/** key up */
+function handleUp() {
+ const { length } = resultOptions.value;
+ if (length === 0) return;
+
+ const index = getActivePathIndex();
+ if (index === -1) return;
+
+ const activeIndex = index === 0 ? length - 1 : index - 1;
+
+ activePath.value = resultOptions.value[activeIndex].routePath;
+}
+
+/** key down */
+function handleDown() {
+ const { length } = resultOptions.value;
+ if (length === 0) return;
+
+ const index = getActivePathIndex();
+ if (index === -1) return;
+
+ const activeIndex = index === length - 1 ? 0 : index + 1;
+
+ activePath.value = resultOptions.value[activeIndex].routePath;
+}
+
+function getActivePathIndex() {
+ return resultOptions.value.findIndex(item => item.routePath === activePath.value);
+}
+
+/** key enter */
+function handleEnter() {
+ if (resultOptions.value?.length === 0 || activePath.value === '') return;
+ handleClose();
+ router.push(activePath.value);
+}
+
+function registerShortcut() {
+ onKeyStroke('Escape', handleClose);
+ onKeyStroke('Enter', handleEnter);
+ onKeyStroke('ArrowUp', handleUp);
+ onKeyStroke('ArrowDown', handleDown);
+}
+
+registerShortcut();
+</script>
+
+<template>
+ <NModal
+ v-model:show="visible"
+ :segmented="{ footer: 'soft' }"
+ :closable="false"
+ preset="card"
+ auto-focus
+ footer-style="padding: 0; margin: 0"
+ class="fixed left-0 right-0"
+ :class="[isMobile ? 'size-full top-0px rounded-0' : 'w-630px top-50px']"
+ @after-leave="handleClose"
+ >
+ <NInputGroup>
+ <NInput v-model:value="keyword" clearable :placeholder="$t('common.keywordSearch')" @input="handleSearch">
+ <template #prefix>
+ <icon-uil-search class="text-15px text-#c2c2c2" />
+ </template>
+ </NInput>
+ <NButton v-if="isMobile" type="primary" ghost @click="handleClose">{{ $t('common.cancel') }}</NButton>
+ </NInputGroup>
+
+ <div class="mt-20px">
+ <NEmpty v-if="resultOptions.length === 0" :description="$t('common.noData')" />
+ <SearchResult v-else v-model:path="activePath" :options="resultOptions" @enter="handleEnter" />
+ </div>
+ <template #footer>
+ <SearchFooter v-if="!isMobile" />
+ </template>
+ </NModal>
+</template>
+
+<style lang="scss" scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-search/components/search-result.vue b/ruoyi-plus-soybean/src/layouts/modules/global-search/components/search-result.vue
new file mode 100755
index 0000000..bb13952
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-search/components/search-result.vue
@@ -0,0 +1,56 @@
+<script lang="ts" setup>
+import { useThemeStore } from '@/store/modules/theme';
+import { $t } from '@/locales';
+
+defineOptions({ name: 'SearchResult' });
+
+interface Props {
+ options: App.Global.Menu[];
+}
+
+defineProps<Props>();
+
+interface Emits {
+ (e: 'enter'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const theme = useThemeStore();
+
+const active = defineModel<string>('path', { required: true });
+
+async function handleMouseEnter(item: App.Global.Menu) {
+ active.value = item.routePath;
+}
+
+function handleTo() {
+ emit('enter');
+}
+</script>
+
+<template>
+ <NScrollbar>
+ <div class="pb-12px">
+ <template v-for="item in options" :key="item.routePath">
+ <div
+ class="mt-8px h-56px flex-y-center cursor-pointer justify-between rounded-4px bg-#e5e7eb px-14px dark:bg-dark"
+ :style="{
+ background: item.routePath === active ? theme.themeColor : '',
+ color: item.routePath === active ? '#fff' : ''
+ }"
+ @click="handleTo"
+ @mouseenter="handleMouseEnter(item)"
+ >
+ <component :is="item.icon" />
+ <span class="ml-5px flex-1">
+ {{ (item.i18nKey && $t(item.i18nKey)) || item.label }}
+ </span>
+ <icon-ant-design-enter-outlined class="icon mr-3px p-2px text-20px" />
+ </div>
+ </template>
+ </div>
+ </NScrollbar>
+</template>
+
+<style lang="scss" scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-search/index.vue b/ruoyi-plus-soybean/src/layouts/modules/global-search/index.vue
new file mode 100755
index 0000000..95c2a7b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-search/index.vue
@@ -0,0 +1,18 @@
+<script lang="ts" setup>
+import { useBoolean } from '@sa/hooks';
+import { $t } from '@/locales';
+import SearchModal from './components/search-modal.vue';
+
+defineOptions({ name: 'GlobalSearch' });
+
+const { bool: show, toggle } = useBoolean();
+</script>
+
+<template>
+ <ButtonIcon :tooltip-content="$t('common.search')" @click="toggle">
+ <icon-uil-search />
+ </ButtonIcon>
+ <SearchModal v-model:show="show" />
+</template>
+
+<style lang="scss" scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-sider/index.vue b/ruoyi-plus-soybean/src/layouts/modules/global-sider/index.vue
new file mode 100755
index 0000000..0deece5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-sider/index.vue
@@ -0,0 +1,36 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
+import { useAppStore } from '@/store/modules/app';
+import { useThemeStore } from '@/store/modules/theme';
+import GlobalLogo from '../global-logo/index.vue';
+
+defineOptions({
+ name: 'GlobalSider'
+});
+
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+
+const isTopHybridSidebarFirst = computed(() => themeStore.layout.mode === 'top-hybrid-sidebar-first');
+const isTopHybridHeaderFirst = computed(() => themeStore.layout.mode === 'top-hybrid-header-first');
+const darkMenu = computed(
+ () =>
+ !themeStore.darkMode && !isTopHybridSidebarFirst.value && !isTopHybridHeaderFirst.value && themeStore.sider.inverted
+);
+const showLogo = computed(() => themeStore.layout.mode === 'vertical');
+const menuWrapperClass = computed(() => (showLogo.value ? 'flex-1-hidden' : 'h-full'));
+</script>
+
+<template>
+ <DarkModeContainer class="size-full flex-col-stretch shadow-sider" :inverted="darkMenu">
+ <GlobalLogo
+ v-if="showLogo"
+ :show-title="!appStore.siderCollapse"
+ :style="{ height: themeStore.header.height + 'px' }"
+ />
+ <div :id="GLOBAL_SIDER_MENU_ID" :class="menuWrapperClass"></div>
+ </DarkModeContainer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-tab/context-menu.vue b/ruoyi-plus-soybean/src/layouts/modules/global-tab/context-menu.vue
new file mode 100755
index 0000000..1145ee2
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-tab/context-menu.vue
@@ -0,0 +1,146 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import type { VNode } from 'vue';
+import { useTabStore } from '@/store/modules/tab';
+import { useSvgIcon } from '@/hooks/common/icon';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'ContextMenu'
+});
+
+interface Props {
+ /** ClientX */
+ x: number;
+ /** ClientY */
+ y: number;
+ tabId: string;
+ excludeKeys?: App.Global.DropdownKey[];
+ disabledKeys?: App.Global.DropdownKey[];
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ excludeKeys: () => [],
+ disabledKeys: () => []
+});
+
+const visible = defineModel<boolean>('visible');
+
+const { removeTab, clearTabs, clearLeftTabs, clearRightTabs, fixTab, unfixTab, isTabRetain, homeTab } = useTabStore();
+const { SvgIconVNode } = useSvgIcon();
+
+type DropdownOption = {
+ key: App.Global.DropdownKey;
+ label: string;
+ icon?: () => VNode;
+ disabled?: boolean;
+};
+
+const options = computed(() => {
+ const opts: DropdownOption[] = [
+ {
+ key: 'closeCurrent',
+ label: $t('dropdown.closeCurrent'),
+ icon: SvgIconVNode({ icon: 'ant-design:close-outlined', fontSize: 18 })
+ },
+ {
+ key: 'closeOther',
+ label: $t('dropdown.closeOther'),
+ icon: SvgIconVNode({ icon: 'ant-design:column-width-outlined', fontSize: 18 })
+ },
+ {
+ key: 'closeLeft',
+ label: $t('dropdown.closeLeft'),
+ icon: SvgIconVNode({ icon: 'mdi:format-horizontal-align-left', fontSize: 18 })
+ },
+ {
+ key: 'closeRight',
+ label: $t('dropdown.closeRight'),
+ icon: SvgIconVNode({ icon: 'mdi:format-horizontal-align-right', fontSize: 18 })
+ },
+ {
+ key: 'closeAll',
+ label: $t('dropdown.closeAll'),
+ icon: SvgIconVNode({ icon: 'ant-design:line-outlined', fontSize: 18 })
+ }
+ ];
+
+ if (props.tabId !== homeTab?.id) {
+ if (isTabRetain(props.tabId)) {
+ opts.push({
+ key: 'unpin',
+ label: $t('dropdown.unpin'),
+ icon: SvgIconVNode({ icon: 'mdi:pin-off-outline', fontSize: 18 })
+ });
+ } else {
+ opts.push({
+ key: 'pin',
+ label: $t('dropdown.pin'),
+ icon: SvgIconVNode({ icon: 'mdi:pin-outline', fontSize: 18 })
+ });
+ }
+ }
+
+ const { excludeKeys, disabledKeys } = props;
+
+ const result = opts.filter(opt => !excludeKeys.includes(opt.key));
+
+ disabledKeys.forEach(key => {
+ const opt = result.find(item => item.key === key);
+
+ if (opt) {
+ opt.disabled = true;
+ }
+ });
+
+ return result;
+});
+
+function hideDropdown() {
+ visible.value = false;
+}
+
+const dropdownAction: Record<App.Global.DropdownKey, () => void> = {
+ closeCurrent() {
+ removeTab(props.tabId);
+ },
+ closeOther() {
+ clearTabs([props.tabId]);
+ },
+ closeLeft() {
+ clearLeftTabs(props.tabId);
+ },
+ closeRight() {
+ clearRightTabs(props.tabId);
+ },
+ closeAll() {
+ clearTabs();
+ },
+ pin() {
+ fixTab(props.tabId);
+ },
+ unpin() {
+ unfixTab(props.tabId);
+ }
+};
+
+function handleDropdown(optionKey: App.Global.DropdownKey) {
+ dropdownAction[optionKey]?.();
+ hideDropdown();
+}
+</script>
+
+<template>
+ <NDropdown
+ :show="visible"
+ placement="bottom-start"
+ trigger="manual"
+ :x="x"
+ :y="y"
+ :options="options"
+ @clickoutside="hideDropdown"
+ @select="handleDropdown"
+ />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/global-tab/index.vue b/ruoyi-plus-soybean/src/layouts/modules/global-tab/index.vue
new file mode 100755
index 0000000..5e3c92d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/global-tab/index.vue
@@ -0,0 +1,233 @@
+<script setup lang="ts">
+import { nextTick, reactive, ref, watch } from 'vue';
+import { useRoute } from 'vue-router';
+import { useElementBounding } from '@vueuse/core';
+import { PageTab } from '@sa/materials';
+import { useAppStore } from '@/store/modules/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { useTabStore } from '@/store/modules/tab';
+import { isPC } from '@/utils/agent';
+import BetterScroll from '@/components/custom/better-scroll.vue';
+import ContextMenu from './context-menu.vue';
+
+defineOptions({
+ name: 'GlobalTab'
+});
+
+const route = useRoute();
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+const tabStore = useTabStore();
+
+const bsWrapper = ref<HTMLElement>();
+const { width: bsWrapperWidth, left: bsWrapperLeft } = useElementBounding(bsWrapper);
+const bsScroll = ref<InstanceType<typeof BetterScroll>>();
+const tabRef = ref<HTMLElement>();
+const isPCFlag = isPC();
+
+const TAB_DATA_ID = 'data-tab-id';
+const MIDDLE_MOUSE_BUTTON = 1;
+const RIGHT_MOUSE_BUTTON = 2;
+
+type TabNamedNodeMap = NamedNodeMap & {
+ [TAB_DATA_ID]: Attr;
+};
+
+async function scrollToActiveTab() {
+ await nextTick();
+ if (!tabRef.value) return;
+
+ const { children } = tabRef.value;
+
+ for (let i = 0; i < children.length; i += 1) {
+ const child = children[i];
+
+ const { value: tabId } = (child.attributes as TabNamedNodeMap)[TAB_DATA_ID];
+
+ if (tabId === tabStore.activeTabId) {
+ const { left, width } = child.getBoundingClientRect();
+ const clientX = left + width / 2;
+
+ setTimeout(() => {
+ scrollByClientX(clientX);
+ }, 50);
+
+ break;
+ }
+ }
+}
+
+function scrollByClientX(clientX: number) {
+ const currentX = clientX - bsWrapperLeft.value;
+ const deltaX = currentX - bsWrapperWidth.value / 2;
+
+ if (bsScroll.value?.instance) {
+ const { maxScrollX, x: leftX, scrollBy } = bsScroll.value.instance;
+
+ const rightX = maxScrollX - leftX;
+ const update = deltaX > 0 ? Math.max(-deltaX, rightX) : Math.min(-deltaX, -leftX);
+
+ scrollBy(update, 0, 300);
+ }
+}
+
+function getContextMenuDisabledKeys(tabId: string) {
+ const disabledKeys: App.Global.DropdownKey[] = [];
+
+ if (tabStore.isTabRetain(tabId)) {
+ const homeDisable: App.Global.DropdownKey[] = ['closeCurrent', 'closeLeft'];
+ disabledKeys.push(...homeDisable);
+ }
+
+ return disabledKeys;
+}
+
+function handleCloseTab(tab: App.Global.Tab) {
+ tabStore.removeTab(tab.id);
+}
+
+function handleMousedown(e: MouseEvent, tab: App.Global.Tab) {
+ const isMiddleClick = e.button === MIDDLE_MOUSE_BUTTON;
+ if (!isMiddleClick || !themeStore.tab.closeTabByMiddleClick) {
+ return;
+ }
+
+ if (tabStore.isTabRetain(tab.id)) {
+ return;
+ }
+
+ e.preventDefault();
+ handleCloseTab(tab);
+}
+
+function switchTab(e: MouseEvent, tab: App.Global.Tab) {
+ if ([MIDDLE_MOUSE_BUTTON, RIGHT_MOUSE_BUTTON].includes(e.button)) return;
+
+ tabStore.switchRouteByTab(tab);
+}
+
+async function refresh() {
+ appStore.reloadPage(500);
+}
+
+interface DropdownConfig {
+ visible: boolean;
+ x: number;
+ y: number;
+ tabId: string;
+}
+
+const dropdown: DropdownConfig = reactive({
+ visible: false,
+ x: 0,
+ y: 0,
+ tabId: ''
+});
+
+function setDropdown(config: Partial<DropdownConfig>) {
+ Object.assign(dropdown, config);
+}
+
+let isClickContextMenu = false;
+
+function handleDropdownVisible(visible: boolean | undefined) {
+ if (!isClickContextMenu) {
+ setDropdown({ visible });
+ }
+}
+
+async function handleContextMenu(e: MouseEvent, tabId: string) {
+ e.preventDefault();
+
+ const { clientX, clientY } = e;
+
+ isClickContextMenu = true;
+
+ const DURATION = dropdown.visible ? 150 : 0;
+
+ setDropdown({ visible: false });
+
+ setTimeout(() => {
+ setDropdown({
+ visible: true,
+ x: clientX,
+ y: clientY,
+ tabId
+ });
+ isClickContextMenu = false;
+ }, DURATION);
+}
+
+function init() {
+ tabStore.initTabStore(route);
+}
+
+function removeFocus() {
+ (document.activeElement as HTMLElement)?.blur();
+}
+
+// watch
+watch(
+ () => route.fullPath,
+ () => {
+ tabStore.addTab(route);
+ }
+);
+watch(
+ () => tabStore.activeTabId,
+ () => {
+ scrollToActiveTab();
+ }
+);
+
+// init
+init();
+</script>
+
+<template>
+ <DarkModeContainer class="size-full flex-y-center px-16px shadow-tab">
+ <div ref="bsWrapper" class="h-full flex-1-hidden">
+ <BetterScroll ref="bsScroll" :options="{ scrollX: true, scrollY: false, click: !isPCFlag }" @click="removeFocus">
+ <div
+ ref="tabRef"
+ class="h-full flex pr-18px"
+ :class="[
+ themeStore.tab.mode === 'chrome' || themeStore.tab.mode === 'slider' ? 'items-end' : 'items-center gap-12px'
+ ]"
+ >
+ <PageTab
+ v-for="tab in tabStore.tabs"
+ :key="tab.id"
+ :[TAB_DATA_ID]="tab.id"
+ :mode="themeStore.tab.mode"
+ :dark-mode="themeStore.darkMode"
+ :active="tab.id === tabStore.activeTabId"
+ :active-color="themeStore.themeColor"
+ :closable="!tabStore.isTabRetain(tab.id)"
+ @pointerdown="switchTab($event, tab)"
+ @mousedown="handleMousedown($event, tab)"
+ @close="handleCloseTab(tab)"
+ @contextmenu="handleContextMenu($event, tab.id)"
+ >
+ <template #prefix>
+ <SvgIcon :icon="tab.icon" :local-icon="tab.localIcon" class="inline-block align-text-bottom text-16px" />
+ </template>
+ <div class="max-w-240px ellipsis-text">{{ tab.label }}</div>
+ </PageTab>
+ </div>
+ </BetterScroll>
+ </div>
+ <ReloadButton :loading="!appStore.reloadFlag" @click="refresh" />
+ <FullScreen :full="appStore.fullContent" @click="appStore.toggleFullContent" />
+ </DarkModeContainer>
+ <ContextMenu
+ :visible="dropdown.visible"
+ :tab-id="dropdown.tabId"
+ :disabled-keys="getContextMenuDisabledKeys(dropdown.tabId)"
+ :x="dropdown.x"
+ :y="dropdown.y"
+ @update:visible="handleDropdownVisible"
+ />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/components/layout-mode-card.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/components/layout-mode-card.vue
new file mode 100755
index 0000000..e056350
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/components/layout-mode-card.vue
@@ -0,0 +1,101 @@
+<script setup lang="ts">
+import type { PopoverPlacement } from 'naive-ui';
+import { themeLayoutModeRecord } from '@/constants/app';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'LayoutModeCard'
+});
+
+interface Props {
+ /** Layout mode */
+ mode: UnionKey.ThemeLayoutMode;
+ /** Disabled */
+ disabled?: boolean;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ /** Layout mode change */
+ (e: 'update:mode', mode: UnionKey.ThemeLayoutMode): void;
+}
+
+const emit = defineEmits<Emits>();
+
+type LayoutConfig = Record<
+ UnionKey.ThemeLayoutMode,
+ {
+ placement: PopoverPlacement;
+ menuClass: string;
+ mainClass: string;
+ }
+>;
+
+const layoutConfig: LayoutConfig = {
+ vertical: {
+ placement: 'bottom',
+ menuClass: 'w-1/3 h-full',
+ mainClass: 'w-2/3 h-3/4'
+ },
+ 'vertical-mix': {
+ placement: 'bottom',
+ menuClass: 'w-1/4 h-full',
+ mainClass: 'w-2/3 h-3/4'
+ },
+ 'vertical-hybrid-header-first': {
+ placement: 'bottom',
+ menuClass: 'w-1/4 h-full',
+ mainClass: 'w-2/3 h-3/4'
+ },
+ horizontal: {
+ placement: 'bottom',
+ menuClass: 'w-full h-1/4',
+ mainClass: 'w-full h-3/4'
+ },
+ 'top-hybrid-sidebar-first': {
+ placement: 'bottom',
+ menuClass: 'w-full h-1/4',
+ mainClass: 'w-2/3 h-3/4'
+ },
+ 'top-hybrid-header-first': {
+ placement: 'bottom',
+ menuClass: 'w-full h-1/4',
+ mainClass: 'w-2/3 h-3/4'
+ }
+};
+
+function handleChangeMode(mode: UnionKey.ThemeLayoutMode) {
+ if (props.disabled) return;
+
+ emit('update:mode', mode);
+}
+</script>
+
+<template>
+ <div class="grid grid-cols-2 gap-x-16px gap-y-12px md:grid-cols-3">
+ <div
+ v-for="(item, key) in layoutConfig"
+ :key="key"
+ class="flex-col-center cursor-pointer"
+ @click="handleChangeMode(key)"
+ >
+ <IconTooltip :placement="item.placement">
+ <template #trigger>
+ <div
+ class="h-64px w-96px gap-6px rd-4px p-6px shadow ring-2 ring-transparent transition-all hover:ring-primary"
+ :class="{ '!ring-primary': mode === key }"
+ >
+ <div class="h-full w-full gap-1" :class="[key.includes('vertical') ? 'flex' : 'flex-col']">
+ <slot :name="key"></slot>
+ </div>
+ </div>
+ </template>
+ {{ $t(`theme.layout.layoutMode.${key}_detail`) }}
+ </IconTooltip>
+ <p class="mt-8px text-12px">{{ $t(themeLayoutModeRecord[key]) }}</p>
+ </div>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/components/setting-item.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/components/setting-item.vue
new file mode 100755
index 0000000..07316a6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/components/setting-item.vue
@@ -0,0 +1,24 @@
+<script setup lang="ts">
+defineOptions({
+ name: 'SettingItem'
+});
+
+interface Props {
+ /** Label */
+ label: string;
+}
+
+defineProps<Props>();
+</script>
+
+<template>
+ <div class="w-full flex-y-center justify-between">
+ <div class="flex-y-center">
+ <span class="pr-8px text-base-text">{{ label }}</span>
+ <slot name="suffix"></slot>
+ </div>
+ <slot></slot>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/index.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/index.vue
new file mode 100755
index 0000000..b874671
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/index.vue
@@ -0,0 +1,66 @@
+<script setup lang="ts">
+import { computed, ref } from 'vue';
+import { useAppStore } from '@/store/modules/app';
+import { $t } from '@/locales';
+import AppearanceSettings from './modules/appearance/index.vue';
+import LayoutSettings from './modules/layout/index.vue';
+import GeneralSettings from './modules/general/index.vue';
+import ConfigOperation from './modules/config-operation.vue';
+import PresetSettings from './modules/preset/index.vue';
+
+defineOptions({
+ name: 'ThemeDrawer'
+});
+
+const appStore = useAppStore();
+const activeTab = ref('appearance');
+
+const drawerWidth = computed(() => {
+ const width = 400;
+
+ // On mobile devices, use 90% of viewport width with a maximum of 400px
+ if (appStore.isMobile) {
+ return `min(90vw, ${width}px)`;
+ }
+
+ return width;
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="appStore.themeDrawerVisible" display-directive="show" :width="drawerWidth">
+ <NDrawerContent :title="$t('theme.themeDrawerTitle')" :native-scrollbar="false" closable>
+ <NTabs v-model:value="activeTab" type="segment" size="medium" class="mb-16px">
+ <NTab name="appearance" :tab="$t('theme.tabs.appearance')"></NTab>
+ <NTab name="layout" :tab="$t('theme.tabs.layout')"></NTab>
+ <NTab name="general" :tab="$t('theme.tabs.general')"></NTab>
+ <NTab name="preset" :tab="$t('theme.tabs.preset')"></NTab>
+ </NTabs>
+
+ <div class="min-h-400px">
+ <KeepAlive>
+ <AppearanceSettings v-if="activeTab === 'appearance'" />
+ <LayoutSettings v-else-if="activeTab === 'layout'" />
+ <GeneralSettings v-else-if="activeTab === 'general'" />
+ <PresetSettings v-else-if="activeTab === 'preset'" />
+ </KeepAlive>
+ </div>
+
+ <template #footer>
+ <ConfigOperation />
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped>
+:deep(.n-tab) {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+:deep(.n-tab-pane) {
+ padding: 0;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/index.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/index.vue
new file mode 100755
index 0000000..a7f65e4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/index.vue
@@ -0,0 +1,19 @@
+<script setup lang="ts">
+import ThemeSchema from './modules/theme-schema.vue';
+import ThemeColor from './modules/theme-color.vue';
+import ThemeRadius from './modules/theme-radius.vue';
+
+defineOptions({
+ name: 'AppearanceSettings'
+});
+</script>
+
+<template>
+ <div class="flex-col-stretch gap-16px">
+ <ThemeSchema />
+ <ThemeColor />
+ <ThemeRadius />
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/modules/theme-color.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/modules/theme-color.vue
new file mode 100755
index 0000000..6c38897
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/modules/theme-color.vue
@@ -0,0 +1,83 @@
+<script setup lang="ts">
+import { useThemeStore } from '@/store/modules/theme';
+import { $t } from '@/locales';
+import SettingItem from '../../../components/setting-item.vue';
+
+defineOptions({
+ name: 'ThemeColor'
+});
+
+const themeStore = useThemeStore();
+
+function handleUpdateColor(color: string, key: App.Theme.ThemeColorKey) {
+ themeStore.updateThemeColors(key, color);
+}
+
+const swatches: string[] = [
+ '#3b82f6',
+ '#6366f1',
+ '#8b5cf6',
+ '#a855f7',
+ '#0ea5e9',
+ '#06b6d4',
+ '#f43f5e',
+ '#ef4444',
+ '#ec4899',
+ '#d946ef',
+ '#f97316',
+ '#f59e0b',
+ '#eab308',
+ '#84cc16',
+ '#22c55e',
+ '#10b981'
+];
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.appearance.themeColor.title') }}</NDivider>
+ <div class="flex-col-stretch gap-12px">
+ <SettingItem key="recommend-color" :label="$t('theme.appearance.recommendColor')">
+ <template #suffix>
+ <IconTooltip>
+ <p>
+ <span class="pr-12px">{{ $t('theme.appearance.recommendColorDesc') }}</span>
+ <br />
+ <NButton
+ text
+ tag="a"
+ href="https://uicolors.app/create"
+ target="_blank"
+ rel="noopener noreferrer"
+ class="text-gray"
+ >
+ https://uicolors.app/create
+ </NButton>
+ </p>
+ </IconTooltip>
+ </template>
+ <NSwitch v-model:value="themeStore.recommendColor" />
+ </SettingItem>
+
+ <SettingItem
+ v-for="(_, key) in themeStore.themeColors"
+ :key="key"
+ :label="$t(`theme.appearance.themeColor.${key}`)"
+ >
+ <template v-if="key === 'info'" #suffix>
+ <NCheckbox v-model:checked="themeStore.isInfoFollowPrimary">
+ {{ $t('theme.appearance.themeColor.followPrimary') }}
+ </NCheckbox>
+ </template>
+ <NColorPicker
+ class="w-90px"
+ :value="themeStore.themeColors[key]"
+ :disabled="key === 'info' && themeStore.isInfoFollowPrimary"
+ :show-alpha="false"
+ :swatches="swatches"
+ @update:value="handleUpdateColor($event, key)"
+ />
+ </SettingItem>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/modules/theme-radius.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/modules/theme-radius.vue
new file mode 100755
index 0000000..499403b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/modules/theme-radius.vue
@@ -0,0 +1,22 @@
+<script setup lang="ts">
+import { useThemeStore } from '@/store/modules/theme';
+import { $t } from '@/locales';
+import SettingItem from '../../../components/setting-item.vue';
+
+defineOptions({
+ name: 'ThemeRadius'
+});
+
+const themeStore = useThemeStore();
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.appearance.themeRadius.title') }}</NDivider>
+ <TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
+ <SettingItem key="1" :label="$t('theme.appearance.themeRadius.title')">
+ <NInputNumber v-model:value="themeStore.themeRadius" size="small" :step="1" :min="0" :max="16" class="w-120px" />
+ </SettingItem>
+ </TransitionGroup>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/modules/theme-schema.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/modules/theme-schema.vue
new file mode 100755
index 0000000..c8b689a
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/appearance/modules/theme-schema.vue
@@ -0,0 +1,76 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { themeSchemaRecord } from '@/constants/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { $t } from '@/locales';
+import SettingItem from '../../../components/setting-item.vue';
+
+defineOptions({
+ name: 'ThemeSchema'
+});
+
+const themeStore = useThemeStore();
+
+const icons: Record<UnionKey.ThemeScheme, string> = {
+ light: 'material-symbols:sunny',
+ dark: 'material-symbols:nightlight-rounded',
+ auto: 'material-symbols:hdr-auto'
+};
+
+function handleSegmentChange(value: string | number) {
+ themeStore.setThemeScheme(value as UnionKey.ThemeScheme);
+}
+
+function handleGrayscaleChange(value: boolean) {
+ themeStore.setGrayscale(value);
+}
+
+function handleColourWeaknessChange(value: boolean) {
+ themeStore.setColourWeakness(value);
+}
+
+const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layout.mode.includes('vertical'));
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.appearance.themeSchema.title') }}</NDivider>
+ <div class="flex-col-stretch gap-16px">
+ <div class="i-flex-center">
+ <NTabs
+ :key="themeStore.themeScheme"
+ type="segment"
+ size="small"
+ class="relative w-214px"
+ :value="themeStore.themeScheme"
+ @update:value="handleSegmentChange"
+ >
+ <NTab v-for="(_, key) in themeSchemaRecord" :key="key" :name="key">
+ <SvgIcon :icon="icons[key]" class="h-23px text-icon-small" />
+ </NTab>
+ </NTabs>
+ </div>
+ <Transition name="sider-inverted">
+ <SettingItem v-if="showSiderInverted" :label="$t('theme.layout.sider.inverted')">
+ <NSwitch v-model:value="themeStore.sider.inverted" />
+ </SettingItem>
+ </Transition>
+ <SettingItem :label="$t('theme.appearance.grayscale')">
+ <NSwitch :value="themeStore.grayscale" @update:value="handleGrayscaleChange" />
+ </SettingItem>
+ <SettingItem :label="$t('theme.appearance.colourWeakness')">
+ <NSwitch :value="themeStore.colourWeakness" @update:value="handleColourWeaknessChange" />
+ </SettingItem>
+ </div>
+</template>
+
+<style scoped>
+.sider-inverted-enter-active,
+.sider-inverted-leave-active {
+ --uno: h-22px transition-all-300;
+}
+
+.sider-inverted-enter-from,
+.sider-inverted-leave-to {
+ --uno: translate-x-20px opacity-0 h-0;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/config-operation.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/config-operation.vue
new file mode 100755
index 0000000..f2df602
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/config-operation.vue
@@ -0,0 +1,58 @@
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue';
+import Clipboard from 'clipboard';
+import { useThemeStore } from '@/store/modules/theme';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'ConfigOperation'
+});
+
+const themeStore = useThemeStore();
+
+const domRef = ref<HTMLElement | null>(null);
+
+function initClipboard() {
+ if (!domRef.value) return;
+
+ const clipboard = new Clipboard(domRef.value);
+
+ clipboard.on('success', () => {
+ window.$message?.success($t('theme.configOperation.copySuccessMsg'));
+ });
+}
+
+function getClipboardText() {
+ const reg = /"\w+":/g;
+
+ const json = themeStore.settingsJson;
+
+ return json.replace(reg, match => match.replace(/"/g, ''));
+}
+
+function handleReset() {
+ themeStore.resetStore();
+
+ setTimeout(() => {
+ window.$message?.success($t('theme.configOperation.resetSuccessMsg'));
+ }, 50);
+}
+
+const dataClipboardText = computed(() => getClipboardText());
+
+onMounted(() => {
+ initClipboard();
+});
+</script>
+
+<template>
+ <div class="w-full flex justify-between">
+ <textarea id="themeConfigCopyTarget" v-model="dataClipboardText" class="absolute opacity-0 -z-1" />
+ <NButton type="error" ghost @click="handleReset">{{ $t('theme.configOperation.resetConfig') }}</NButton>
+ <div ref="domRef" data-clipboard-target="#themeConfigCopyTarget">
+ <NButton type="primary">{{ $t('theme.configOperation.copyConfig') }}</NButton>
+ </div>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/general/index.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/general/index.vue
new file mode 100755
index 0000000..ee7366e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/general/index.vue
@@ -0,0 +1,17 @@
+<script setup lang="ts">
+import GlobalSettings from './modules/global-settings.vue';
+import WatermarkSettings from './modules/watermark-settings.vue';
+
+defineOptions({
+ name: 'GeneralSettings'
+});
+</script>
+
+<template>
+ <div class="flex-col-stretch gap-16px">
+ <GlobalSettings />
+ <WatermarkSettings />
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/general/modules/global-settings.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/general/modules/global-settings.vue
new file mode 100755
index 0000000..15ee392
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/general/modules/global-settings.vue
@@ -0,0 +1,39 @@
+<script setup lang="ts">
+import { useThemeStore } from '@/store/modules/theme';
+import { $t } from '@/locales';
+import SettingItem from '../../../components/setting-item.vue';
+
+defineOptions({
+ name: 'GlobalSettings'
+});
+
+const themeStore = useThemeStore();
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.general.title') }}</NDivider>
+ <SettingItem :label="$t('theme.general.multilingual.visible')">
+ <NSwitch v-model:value="themeStore.header.multilingual.visible" />
+ </SettingItem>
+
+ <SettingItem :label="$t('theme.general.globalSearch.visible')">
+ <NSwitch v-model:value="themeStore.header.globalSearch.visible" />
+ </SettingItem>
+</template>
+
+<style scoped>
+.setting-list-move,
+.setting-list-enter-active,
+.setting-list-leave-active {
+ --uno: transition-all-300;
+}
+
+.setting-list-enter-from,
+.setting-list-leave-to {
+ --uno: opacity-0 -translate-x-30px;
+}
+
+.setting-list-leave-active {
+ --uno: absolute;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/general/modules/watermark-settings.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/general/modules/watermark-settings.vue
new file mode 100755
index 0000000..f2a6aff
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/general/modules/watermark-settings.vue
@@ -0,0 +1,66 @@
+<script setup lang="ts">
+import { watermarkTimeFormatOptions } from '@/constants/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { $t } from '@/locales';
+import SettingItem from '../../../components/setting-item.vue';
+
+defineOptions({
+ name: 'WatermarkSettings'
+});
+
+const themeStore = useThemeStore();
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.general.watermark.title') }}</NDivider>
+ <TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
+ <SettingItem key="1" :label="$t('theme.general.watermark.visible')">
+ <NSwitch v-model:value="themeStore.watermark.visible" />
+ </SettingItem>
+ <SettingItem v-if="themeStore.watermark.visible" key="2" :label="$t('theme.general.watermark.enableUserName')">
+ <NSwitch :value="themeStore.watermark.enableUserName" @update:value="themeStore.setWatermarkEnableUserName" />
+ </SettingItem>
+ <SettingItem v-if="themeStore.watermark.visible" key="3" :label="$t('theme.general.watermark.enableTime')">
+ <NSwitch :value="themeStore.watermark.enableTime" @update:value="themeStore.setWatermarkEnableTime" />
+ </SettingItem>
+ <SettingItem
+ v-if="themeStore.watermark.visible && themeStore.watermark.enableTime"
+ key="4"
+ :label="$t('theme.general.watermark.timeFormat')"
+ >
+ <NSelect
+ v-model:value="themeStore.watermark.timeFormat"
+ :options="watermarkTimeFormatOptions"
+ size="small"
+ class="w-210px"
+ />
+ </SettingItem>
+ <SettingItem key="5" :label="$t('theme.general.watermark.text')">
+ <NInput
+ v-model:value="themeStore.watermark.text"
+ autosize
+ type="text"
+ size="small"
+ class="w-120px"
+ placeholder="SoybeanAdmin"
+ />
+ </SettingItem>
+ </TransitionGroup>
+</template>
+
+<style scoped>
+.setting-list-move,
+.setting-list-enter-active,
+.setting-list-leave-active {
+ --uno: transition-all-300;
+}
+
+.setting-list-enter-from,
+.setting-list-leave-to {
+ --uno: opacity-0 -translate-x-30px;
+}
+
+.setting-list-leave-active {
+ --uno: absolute;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/index.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/index.vue
new file mode 100755
index 0000000..6021699
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/index.vue
@@ -0,0 +1,31 @@
+<script setup lang="ts">
+import { useThemeStore } from '@/store/modules/theme';
+import LayoutMode from './modules/layout-mode.vue';
+import TabSettings from './modules/tab-settings.vue';
+import HeaderSettings from './modules/header-settings.vue';
+import SiderSettings from './modules/sider-settings.vue';
+import FooterSettings from './modules/footer-settings.vue';
+import ContentSettings from './modules/content-settings.vue';
+import TableSettings from './modules/table-settings.vue';
+
+defineOptions({
+ name: 'LayoutSettings'
+});
+
+const themeStore = useThemeStore();
+</script>
+
+<template>
+ <div class="flex-col-stretch gap-16px">
+ <LayoutMode />
+ <TabSettings />
+ <HeaderSettings />
+ <!-- The top menu mode does not have a sidebar -->
+ <SiderSettings v-if="themeStore.layout.mode !== 'horizontal'" />
+ <FooterSettings />
+ <ContentSettings />
+ <TableSettings />
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/content-settings.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/content-settings.vue
new file mode 100755
index 0000000..f0fadff
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/content-settings.vue
@@ -0,0 +1,64 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { themePageAnimationModeOptions, themeScrollModeOptions } from '@/constants/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { translateOptions } from '@/utils/common';
+import { $t } from '@/locales';
+import SettingItem from '../../../components/setting-item.vue';
+
+defineOptions({
+ name: 'ContentSettings'
+});
+
+const themeStore = useThemeStore();
+
+const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wrapper');
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.layout.content.title') }}</NDivider>
+ <TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
+ <SettingItem key="1" :label="$t('theme.layout.content.scrollMode.title')">
+ <template #suffix>
+ <IconTooltip :desc="$t('theme.layout.content.scrollMode.tip')" />
+ </template>
+ <NSelect
+ v-model:value="themeStore.layout.scrollMode"
+ :options="translateOptions(themeScrollModeOptions)"
+ size="small"
+ class="w-120px"
+ />
+ </SettingItem>
+ <SettingItem key="2" :label="$t('theme.layout.content.page.animate')">
+ <NSwitch v-model:value="themeStore.page.animate" />
+ </SettingItem>
+ <SettingItem v-if="themeStore.page.animate" key="3" :label="$t('theme.layout.content.page.mode.title')">
+ <NSelect
+ v-model:value="themeStore.page.animateMode"
+ :options="translateOptions(themePageAnimationModeOptions)"
+ size="small"
+ class="w-120px"
+ />
+ </SettingItem>
+ <SettingItem v-if="isWrapperScrollMode" key="4" :label="$t('theme.layout.content.fixedHeaderAndTab')">
+ <NSwitch v-model:value="themeStore.fixedHeaderAndTab" />
+ </SettingItem>
+ </TransitionGroup>
+</template>
+
+<style scoped>
+.setting-list-move,
+.setting-list-enter-active,
+.setting-list-leave-active {
+ --uno: transition-all-300;
+}
+
+.setting-list-enter-from,
+.setting-list-leave-to {
+ --uno: opacity-0 -translate-x-30px;
+}
+
+.setting-list-leave-active {
+ --uno: absolute;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/footer-settings.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/footer-settings.vue
new file mode 100755
index 0000000..2957eae
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/footer-settings.vue
@@ -0,0 +1,61 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { useThemeStore } from '@/store/modules/theme';
+import { $t } from '@/locales';
+import SettingItem from '../../../components/setting-item.vue';
+
+defineOptions({
+ name: 'FooterSettings'
+});
+
+const themeStore = useThemeStore();
+
+const layoutMode = computed(() => themeStore.layout.mode);
+const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wrapper');
+const isMixHorizontalMode = computed(() =>
+ ['top-hybrid-sidebar-first', 'top-hybrid-header-first'].includes(layoutMode.value)
+);
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.layout.footer.title') }}</NDivider>
+ <TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
+ <SettingItem key="1" :label="$t('theme.layout.footer.visible')">
+ <NSwitch v-model:value="themeStore.footer.visible" />
+ </SettingItem>
+ <SettingItem
+ v-if="themeStore.footer.visible && isWrapperScrollMode"
+ key="2"
+ :label="$t('theme.layout.footer.fixed')"
+ >
+ <NSwitch v-model:value="themeStore.footer.fixed" />
+ </SettingItem>
+ <SettingItem v-if="themeStore.footer.visible" key="3" :label="$t('theme.layout.footer.height')">
+ <NInputNumber v-model:value="themeStore.footer.height" size="small" :step="1" class="w-120px" />
+ </SettingItem>
+ <SettingItem
+ v-if="themeStore.footer.visible && isMixHorizontalMode"
+ key="4"
+ :label="$t('theme.layout.footer.right')"
+ >
+ <NSwitch v-model:value="themeStore.footer.right" />
+ </SettingItem>
+ </TransitionGroup>
+</template>
+
+<style scoped>
+.setting-list-move,
+.setting-list-enter-active,
+.setting-list-leave-active {
+ --uno: transition-all-300;
+}
+
+.setting-list-enter-from,
+.setting-list-leave-to {
+ --uno: opacity-0 -translate-x-30px;
+}
+
+.setting-list-leave-active {
+ --uno: absolute;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/header-settings.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/header-settings.vue
new file mode 100755
index 0000000..4c24b44
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/header-settings.vue
@@ -0,0 +1,47 @@
+<script setup lang="ts">
+import { useThemeStore } from '@/store/modules/theme';
+import { $t } from '@/locales';
+import SettingItem from '../../../components/setting-item.vue';
+
+defineOptions({
+ name: 'HeaderSettings'
+});
+
+const themeStore = useThemeStore();
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.layout.header.title') }}</NDivider>
+ <TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
+ <SettingItem key="1" :label="$t('theme.layout.header.height')">
+ <NInputNumber v-model:value="themeStore.header.height" size="small" :step="1" class="w-120px" />
+ </SettingItem>
+ <SettingItem key="2" :label="$t('theme.layout.header.breadcrumb.visible')">
+ <NSwitch v-model:value="themeStore.header.breadcrumb.visible" />
+ </SettingItem>
+ <SettingItem
+ v-if="themeStore.header.breadcrumb.visible"
+ key="3"
+ :label="$t('theme.layout.header.breadcrumb.showIcon')"
+ >
+ <NSwitch v-model:value="themeStore.header.breadcrumb.showIcon" />
+ </SettingItem>
+ </TransitionGroup>
+</template>
+
+<style scoped>
+.setting-list-move,
+.setting-list-enter-active,
+.setting-list-leave-active {
+ --uno: transition-all-300;
+}
+
+.setting-list-enter-from,
+.setting-list-leave-to {
+ --uno: opacity-0 -translate-x-30px;
+}
+
+.setting-list-leave-active {
+ --uno: absolute;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/layout-mode.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/layout-mode.vue
new file mode 100755
index 0000000..70343a7
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/layout-mode.vue
@@ -0,0 +1,84 @@
+<script setup lang="ts">
+import { useAppStore } from '@/store/modules/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { $t } from '@/locales';
+import LayoutModeCard from '../../../components/layout-mode-card.vue';
+
+defineOptions({
+ name: 'LayoutMode'
+});
+
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.layout.layoutMode.title') }}</NDivider>
+ <LayoutModeCard v-model:mode="themeStore.layout.mode" :disabled="appStore.isMobile">
+ <template #vertical>
+ <div class="layout-sider h-full w-18px !bg-primary"></div>
+ <div class="vertical-wrapper">
+ <div class="layout-header bg-primary-200"></div>
+ <div class="layout-main"></div>
+ </div>
+ </template>
+ <template #vertical-mix>
+ <div class="layout-sider h-full w-8px !bg-primary"></div>
+ <div class="layout-sider h-full w-16px !bg-primary-300"></div>
+ <div class="vertical-wrapper">
+ <div class="layout-header bg-primary-200"></div>
+ <div class="layout-main"></div>
+ </div>
+ </template>
+ <template #vertical-hybrid-header-first>
+ <div class="layout-sider h-full w-8px !bg-primary"></div>
+ <div class="layout-sider h-full w-16px !bg-primary-300"></div>
+ <div class="vertical-wrapper">
+ <div class="layout-header bg-primary"></div>
+ <div class="layout-main"></div>
+ </div>
+ </template>
+ <template #horizontal>
+ <div class="layout-header !bg-primary"></div>
+ <div class="horizontal-wrapper">
+ <div class="layout-main"></div>
+ </div>
+ </template>
+ <template #top-hybrid-sidebar-first>
+ <div class="layout-header !bg-primary-300"></div>
+ <div class="horizontal-wrapper">
+ <div class="layout-sider w-18px !bg-primary"></div>
+ <div class="layout-main"></div>
+ </div>
+ </template>
+ <template #top-hybrid-header-first>
+ <div class="layout-header bg-primary"></div>
+ <div class="horizontal-wrapper">
+ <div class="layout-sider w-18px"></div>
+ <div class="layout-main"></div>
+ </div>
+ </template>
+ </LayoutModeCard>
+</template>
+
+<style scoped>
+.layout-header {
+ --uno: h-16px rd-4px;
+}
+
+.layout-sider {
+ --uno: bg-primary-300 rd-4px;
+}
+
+.layout-main {
+ --uno: flex-1 bg-primary-200 rd-4px;
+}
+
+.vertical-wrapper {
+ --uno: flex-1 flex-col gap-6px;
+}
+
+.horizontal-wrapper {
+ --uno: flex-1 flex gap-6px;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/sider-settings.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/sider-settings.vue
new file mode 100755
index 0000000..ba87bd7
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/sider-settings.vue
@@ -0,0 +1,60 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { useThemeStore } from '@/store/modules/theme';
+import { $t } from '@/locales';
+import SettingItem from '../../../components/setting-item.vue';
+
+defineOptions({
+ name: 'SiderSettings'
+});
+
+const themeStore = useThemeStore();
+
+const layoutMode = computed(() => themeStore.layout.mode);
+const isMixLayoutMode = computed(() => layoutMode.value.includes('mix') || layoutMode.value.includes('hybrid'));
+const isHybridLayoutMode = computed(() => layoutMode.value.includes('hybrid'));
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.layout.sider.title') }}</NDivider>
+ <TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
+ <SettingItem v-if="layoutMode === 'vertical'" key="1" :label="$t('theme.layout.sider.width')">
+ <NInputNumber v-model:value="themeStore.sider.width" size="small" :step="1" class="w-120px" />
+ </SettingItem>
+ <SettingItem v-if="layoutMode === 'vertical'" key="2" :label="$t('theme.layout.sider.collapsedWidth')">
+ <NInputNumber v-model:value="themeStore.sider.collapsedWidth" size="small" :step="1" class="w-120px" />
+ </SettingItem>
+ <SettingItem v-if="isMixLayoutMode" key="3" :label="$t('theme.layout.sider.mixWidth')">
+ <NInputNumber v-model:value="themeStore.sider.mixWidth" size="small" :step="1" class="w-120px" />
+ </SettingItem>
+ <SettingItem v-if="isMixLayoutMode" key="4" :label="$t('theme.layout.sider.mixCollapsedWidth')">
+ <NInputNumber v-model:value="themeStore.sider.mixCollapsedWidth" size="small" :step="1" class="w-120px" />
+ </SettingItem>
+ <SettingItem v-if="layoutMode === 'vertical-mix'" key="5" :label="$t('theme.layout.sider.mixChildMenuWidth')">
+ <NInputNumber v-model:value="themeStore.sider.mixChildMenuWidth" size="small" :step="1" class="w-120px" />
+ </SettingItem>
+ <SettingItem v-if="isHybridLayoutMode" key="6" :label="$t('theme.layout.sider.autoSelectFirstMenu')">
+ <template #suffix>
+ <IconTooltip :desc="$t('theme.layout.sider.autoSelectFirstMenuTip')" />
+ </template>
+ <NSwitch v-model:value="themeStore.sider.autoSelectFirstMenu" />
+ </SettingItem>
+ </TransitionGroup>
+</template>
+
+<style scoped>
+.setting-list-move,
+.setting-list-enter-active,
+.setting-list-leave-active {
+ --uno: transition-all-300;
+}
+
+.setting-list-enter-from,
+.setting-list-leave-to {
+ --uno: opacity-0 -translate-x-30px;
+}
+
+.setting-list-leave-active {
+ --uno: absolute;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/tab-settings.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/tab-settings.vue
new file mode 100755
index 0000000..bfa80a6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/tab-settings.vue
@@ -0,0 +1,62 @@
+<script setup lang="ts">
+import { themeTabModeOptions } from '@/constants/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { translateOptions } from '@/utils/common';
+import { $t } from '@/locales';
+import SettingItem from '../../../components/setting-item.vue';
+
+defineOptions({
+ name: 'TabSettings'
+});
+
+const themeStore = useThemeStore();
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.layout.tab.title') }}</NDivider>
+ <TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
+ <SettingItem key="1" :label="$t('theme.layout.tab.visible')">
+ <NSwitch v-model:value="themeStore.tab.visible" />
+ </SettingItem>
+ <SettingItem v-if="themeStore.tab.visible" key="2" :label="$t('theme.layout.tab.cache')">
+ <template #suffix>
+ <IconTooltip :desc="$t('theme.layout.tab.cacheTip')" />
+ </template>
+ <NSwitch v-model:value="themeStore.tab.cache" />
+ </SettingItem>
+ <SettingItem v-if="themeStore.tab.visible" key="3" :label="$t('theme.layout.tab.height')">
+ <NInputNumber v-model:value="themeStore.tab.height" size="small" :step="1" class="w-120px" />
+ </SettingItem>
+ <SettingItem v-if="themeStore.tab.visible" key="4" :label="$t('theme.layout.tab.mode.title')">
+ <NSelect
+ v-model:value="themeStore.tab.mode"
+ :options="translateOptions(themeTabModeOptions)"
+ size="small"
+ class="w-120px"
+ />
+ </SettingItem>
+ <SettingItem v-if="themeStore.tab.visible" key="5" :label="$t('theme.layout.tab.closeByMiddleClick')">
+ <template #suffix>
+ <IconTooltip :desc="$t('theme.layout.tab.closeByMiddleClickTip')" />
+ </template>
+ <NSwitch v-model:value="themeStore.tab.closeTabByMiddleClick" />
+ </SettingItem>
+ </TransitionGroup>
+</template>
+
+<style scoped>
+.setting-list-move,
+.setting-list-enter-active,
+.setting-list-leave-active {
+ --uno: transition-all-300;
+}
+
+.setting-list-enter-from,
+.setting-list-leave-to {
+ --uno: opacity-0 -translate-x-30px;
+}
+
+.setting-list-leave-active {
+ --uno: absolute;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/table-settings.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/table-settings.vue
new file mode 100755
index 0000000..bf125cf
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/layout/modules/table-settings.vue
@@ -0,0 +1,44 @@
+<script setup lang="ts">
+import { themeTableSizeOptions } from '@/constants/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { translateOptions } from '@/utils/common';
+import { $t } from '@/locales';
+import SettingItem from '../../../components/setting-item.vue';
+
+defineOptions({
+ name: 'TableProps'
+});
+
+const themeStore = useThemeStore();
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.tablePropsTitle') }}</NDivider>
+ <TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
+ <SettingItem key="0" :label="$t('theme.table.size.title')">
+ <NSelect
+ v-model:value="themeStore.table.size"
+ :options="translateOptions(themeTableSizeOptions)"
+ size="small"
+ class="w-120px"
+ />
+ </SettingItem>
+ <SettingItem key="1" :label="$t('theme.table.bordered')">
+ <NSwitch v-model:value="themeStore.table.bordered" />
+ </SettingItem>
+ <SettingItem key="2" :label="$t('theme.table.bottomBordered')">
+ <NSwitch v-model:value="themeStore.table.bottomBordered" />
+ </SettingItem>
+ <SettingItem key="3" :label="$t('theme.table.singleColumn')">
+ <NSwitch v-model:value="themeStore.table.singleColumn" :checked-value="false" :unchecked-value="true" />
+ </SettingItem>
+ <SettingItem key="4" :label="$t('theme.table.singleLine')">
+ <NSwitch v-model:value="themeStore.table.singleLine" :checked-value="false" :unchecked-value="true" />
+ </SettingItem>
+ <SettingItem key="5" :label="$t('theme.table.striped')">
+ <NSwitch v-model:value="themeStore.table.striped" />
+ </SettingItem>
+ </TransitionGroup>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/preset/index.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/preset/index.vue
new file mode 100755
index 0000000..32e9251
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/preset/index.vue
@@ -0,0 +1,15 @@
+<script setup lang="ts">
+import ThemePreset from './modules/theme-preset.vue';
+
+defineOptions({
+ name: 'PresetSettings'
+});
+</script>
+
+<template>
+ <div class="flex-col-stretch gap-16px">
+ <ThemePreset />
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/preset/modules/theme-preset.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/preset/modules/theme-preset.vue
new file mode 100755
index 0000000..f515e3b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/preset/modules/theme-preset.vue
@@ -0,0 +1,156 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { defu } from 'defu';
+import { useThemeStore } from '@/store/modules/theme';
+import { themeSettings } from '@/theme/settings';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'ThemePreset'
+});
+
+type ThemePreset = Pick<
+ App.Theme.ThemeSetting,
+ | 'themeScheme'
+ | 'grayscale'
+ | 'colourWeakness'
+ | 'recommendColor'
+ | 'themeColor'
+ | 'themeRadius'
+ | 'otherColor'
+ | 'isInfoFollowPrimary'
+ | 'layout'
+ | 'page'
+ | 'header'
+ | 'tab'
+ | 'fixedHeaderAndTab'
+ | 'sider'
+ | 'footer'
+ | 'watermark'
+ | 'tokens'
+> & {
+ name: string;
+ desc: string;
+ i18nkey?: string;
+ version: string;
+ sort: number;
+ /** Optional NaiveUI theme overrides */
+ naiveui?: App.Theme.NaiveUIThemeOverride;
+};
+
+const presetModules = import.meta.glob('@/theme/preset/*.json', { eager: true, import: 'default' });
+
+const themeStore = useThemeStore();
+
+// Extract preset data
+const presets = computed(() =>
+ Object.entries(presetModules)
+ .map(([path, presetData]) => {
+ const fileName = path.split('/').pop()?.replace('.json', '') || '';
+ return {
+ id: fileName,
+ ...(presetData as ThemePreset)
+ };
+ })
+ .sort((a, b) => {
+ return (a.sort ?? 0) - (b.sort ?? 0);
+ })
+);
+
+const getPresetName = (preset: ThemePreset): string => {
+ if (!preset.i18nkey) return preset.name;
+ try {
+ const key = `${preset.i18nkey}.name` as App.I18n.I18nKey;
+ const translated = $t(key);
+ return translated !== key ? translated : preset.name;
+ } catch {
+ return preset.name;
+ }
+};
+
+const getPresetDesc = (preset: ThemePreset): string => {
+ if (!preset.i18nkey) return preset.desc;
+ try {
+ const key = `${preset.i18nkey}.desc` as App.I18n.I18nKey;
+ const translated = $t(key);
+ return translated !== key ? translated : preset.desc;
+ } catch {
+ return preset.desc;
+ }
+};
+
+const applyPreset = (preset: ThemePreset): void => {
+ const mergedPreset = defu(preset, themeSettings);
+ const { themeScheme, grayscale, colourWeakness, layout, watermark, naiveui, ...rest } = mergedPreset;
+ themeStore.setThemeScheme(themeScheme);
+ themeStore.setGrayscale(grayscale);
+ themeStore.setColourWeakness(colourWeakness);
+ themeStore.setThemeLayout(layout.mode);
+ themeStore.setWatermarkEnableUserName(watermark.enableUserName);
+ themeStore.setWatermarkEnableTime(watermark.enableTime);
+
+ Object.assign(themeStore, {
+ ...rest,
+ layout: { ...themeStore.layout, scrollMode: layout.scrollMode },
+ page: { ...rest.page },
+ header: { ...rest.header },
+ tab: { ...rest.tab },
+ sider: { ...rest.sider },
+ footer: { ...rest.footer },
+ watermark: { ...watermark },
+ tokens: { ...rest.tokens }
+ });
+
+ // Apply NaiveUI theme overrides if present
+ themeStore.setNaiveThemeOverrides(naiveui);
+
+ window.$message?.success($t('theme.appearance.preset.applySuccess'));
+};
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.appearance.preset.title') }}</NDivider>
+
+ <div class="flex flex-col gap-3">
+ <div
+ v-for="preset in presets"
+ :key="preset.id"
+ class="border border-primary/10 rounded-lg border-solid bg-white/5 p-3 backdrop-blur-10 transition-all duration-300 hover:(shadow-md -translate-y-0.5)"
+ >
+ <div class="mb-2 flex items-center justify-between">
+ <div class="min-w-0 w-full flex flex-1 items-center justify-between gap-2">
+ <h5 class="m-0 truncate text-sm text-primary font-600">
+ {{ getPresetName(preset) }}
+ </h5>
+ <NBadge :value="`v${preset.version}`" type="info" size="small" class="flex-shrink-0 opacity-80" />
+ </div>
+ <NButton type="primary" size="tiny" ghost round class="ml-2 flex-shrink-0" @click="applyPreset(preset)">
+ {{ $t('theme.appearance.preset.apply') }}
+ </NButton>
+ </div>
+
+ <p class="line-clamp-2 mb-3 text-xs text-gray-500 leading-4">{{ getPresetDesc(preset) }}</p>
+
+ <div class="flex items-center justify-between">
+ <div class="flex gap-1">
+ <div
+ v-for="(color, key) in { primary: preset.themeColor, ...preset.otherColor }"
+ :key="key"
+ class="h-3 w-3 cursor-pointer border border-white/30 rounded-full transition-transform hover:scale-110"
+ :style="{ backgroundColor: color }"
+ :class="{ 'ring-1 ring-primary/50': key === 'primary' }"
+ :title="key"
+ />
+ </div>
+ <div class="flex items-center gap-1">
+ <div class="text-lg">
+ {{ preset.themeScheme === 'dark' ? '馃寵' : '鈽�锔�' }}
+ </div>
+ <div class="text-lg">
+ {{ preset.grayscale ? '馃帹' : '' }}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/table-props.vue b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/table-props.vue
new file mode 100755
index 0000000..ac84e5c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/layouts/modules/theme-drawer/modules/table-props.vue
@@ -0,0 +1,44 @@
+<script setup lang="ts">
+import { themeTableSizeOptions } from '@/constants/app';
+import { useThemeStore } from '@/store/modules/theme';
+import { translateOptions } from '@/utils/common';
+import { $t } from '@/locales';
+import SettingItem from '../components/setting-item.vue';
+
+defineOptions({
+ name: 'TableProps'
+});
+
+const themeStore = useThemeStore();
+</script>
+
+<template>
+ <NDivider>{{ $t('theme.tablePropsTitle') }}</NDivider>
+ <TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
+ <SettingItem key="0" :label="$t('theme.table.size.title')">
+ <NSelect
+ v-model:value="themeStore.table.size"
+ :options="translateOptions(themeTableSizeOptions)"
+ size="small"
+ class="w-120px"
+ />
+ </SettingItem>
+ <SettingItem key="1" :label="$t('theme.table.bordered')">
+ <NSwitch v-model:value="themeStore.table.bordered" />
+ </SettingItem>
+ <SettingItem key="2" :label="$t('theme.table.bottomBordered')">
+ <NSwitch v-model:value="themeStore.table.bottomBordered" />
+ </SettingItem>
+ <SettingItem key="3" :label="$t('theme.table.singleColumn')">
+ <NSwitch v-model:value="themeStore.table.singleColumn" :checked-value="false" :unchecked-value="true" />
+ </SettingItem>
+ <SettingItem key="4" :label="$t('theme.table.singleLine')">
+ <NSwitch v-model:value="themeStore.table.singleLine" :checked-value="false" :unchecked-value="true" />
+ </SettingItem>
+ <SettingItem key="5" :label="$t('theme.table.striped')">
+ <NSwitch v-model:value="themeStore.table.striped" />
+ </SettingItem>
+ </TransitionGroup>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/locales/dayjs.ts b/ruoyi-plus-soybean/src/locales/dayjs.ts
new file mode 100755
index 0000000..7dc8f8b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/locales/dayjs.ts
@@ -0,0 +1,20 @@
+import { locale } from 'dayjs';
+import 'dayjs/locale/zh-cn';
+import 'dayjs/locale/en';
+import { localStg } from '@/utils/storage';
+
+/**
+ * Set dayjs locale
+ *
+ * @param lang
+ */
+export function setDayjsLocale(lang: App.I18n.LangType = 'zh-CN') {
+ const localMap = {
+ 'zh-CN': 'zh-cn',
+ 'en-US': 'en'
+ } satisfies Record<App.I18n.LangType, string>;
+
+ const l = lang || localStg.get('lang') || 'zh-CN';
+
+ locale(localMap[l]);
+}
diff --git a/ruoyi-plus-soybean/src/locales/index.ts b/ruoyi-plus-soybean/src/locales/index.ts
new file mode 100755
index 0000000..40af8a9
--- /dev/null
+++ b/ruoyi-plus-soybean/src/locales/index.ts
@@ -0,0 +1,26 @@
+import type { App } from 'vue';
+import { createI18n } from 'vue-i18n';
+import { localStg } from '@/utils/storage';
+import messages from './locale';
+
+const i18n = createI18n({
+ locale: localStg.get('lang') || 'zh-CN',
+ fallbackLocale: 'en',
+ messages,
+ legacy: false
+});
+
+/**
+ * Setup plugin i18n
+ *
+ * @param app
+ */
+export function setupI18n(app: App) {
+ app.use(i18n);
+}
+
+export const $t = i18n.global.t as App.I18n.$T;
+
+export function setLocale(locale: App.I18n.LangType) {
+ i18n.global.locale.value = locale;
+}
diff --git a/ruoyi-plus-soybean/src/locales/langs/en-us.ts b/ruoyi-plus-soybean/src/locales/langs/en-us.ts
new file mode 100755
index 0000000..b7c8534
--- /dev/null
+++ b/ruoyi-plus-soybean/src/locales/langs/en-us.ts
@@ -0,0 +1,1279 @@
+const local: App.I18n.Schema = {
+ system: {
+ title: 'GFSC',
+ updateTitle: 'System Version Update Notification',
+ updateContent: 'A new version of the system has been detected. Do you want to refresh the page immediately?',
+ updateConfirm: 'Refresh immediately',
+ updateCancel: 'Later'
+ },
+ common: {
+ action: 'Action',
+ add: 'Add',
+ addSuccess: 'Add Success',
+ backToHome: 'Back to home',
+ batchDelete: 'Batch Delete',
+ import: 'Import',
+ export: 'Export',
+ importSuccess: 'Import Success',
+ importFail: 'Import Fail',
+ importTemplate: 'Import Template',
+ importResult: 'Import Result',
+ downloadTemplate: 'Download Template',
+ importEnd: '',
+ importFormat: 'and the format is',
+ importTip: 'Please upload a file no larger than',
+ importSize: 'Please upload a file no larger than',
+ exportSuccess: 'Export Success',
+ exportFail: 'Export Fail',
+ updateExisting: 'Whether to update the existing user data',
+ cancel: 'Cancel',
+ close: 'Close',
+ check: 'Check',
+ expandColumn: 'Expand Column',
+ columnSetting: 'Column Setting',
+ config: 'Config',
+ login: 'Login',
+ confirm: 'Confirm',
+ save: 'Save',
+ delete: 'Delete',
+ deleteSuccess: 'Delete Success',
+ confirmDelete: 'Are you sure you want to delete?',
+ edit: 'Edit',
+ download: 'Download',
+ warning: 'Warning',
+ error: 'Error',
+ index: 'Index',
+ keywordSearch: 'Please enter keyword',
+ logout: 'Logout',
+ logoutConfirm: 'Are you sure you want to log out?',
+ lookForward: 'Coming soon',
+ modify: 'Modify',
+ modifySuccess: 'Modify Success',
+ noData: 'No Data',
+ operate: 'Operate',
+ pleaseCheckValue: 'Please check whether the value is valid',
+ refresh: 'Refresh',
+ reset: 'Reset',
+ search: 'Search',
+ switch: 'Switch',
+ tip: 'Tip',
+ trigger: 'Trigger',
+ update: 'Update',
+ saveSuccess: 'Save Success',
+ updateSuccess: 'Update Success',
+ noChange: 'No actions were taken',
+ userCenter: 'User Center',
+ yesOrNo: {
+ yes: 'Yes',
+ no: 'No'
+ },
+ second: 'Second',
+ selected: 'selected',
+ anyRecords: 'records',
+ clear: 'Clear',
+ noSelectRecord: 'No Records Selected'
+ },
+ request: {
+ logout: 'Logout user after request failed',
+ logoutMsg: 'User status is invalid, please log in again',
+ logoutWithModal: 'Pop up modal after request failed and then log out user',
+ logoutWithModalMsg: 'User status is invalid, please log in again',
+ refreshToken: 'The requested token has expired, refresh the token',
+ tokenExpired: 'The requested token has expired'
+ },
+ theme: {
+ themeDrawerTitle: 'Theme Configuration',
+ tabs: {
+ appearance: 'Appearance',
+ layout: 'Layout',
+ general: 'General',
+ preset: 'Preset'
+ },
+ appearance: {
+ themeSchema: {
+ title: 'Theme Schema',
+ light: 'Light',
+ dark: 'Dark',
+ auto: 'Follow System'
+ },
+ grayscale: 'Grayscale',
+ colourWeakness: 'Colour Weakness',
+ themeColor: {
+ title: 'Theme Color',
+ primary: 'Primary',
+ info: 'Info',
+ success: 'Success',
+ warning: 'Warning',
+ error: 'Error',
+ followPrimary: 'Follow Primary'
+ },
+ themeRadius: {
+ title: 'Theme Radius'
+ },
+ recommendColor: 'Apply Recommended Color Algorithm',
+ recommendColorDesc: 'The recommended color algorithm refers to',
+ preset: {
+ title: 'Theme Presets',
+ apply: 'Apply',
+ applySuccess: 'Preset applied successfully',
+ default: {
+ name: 'Default Preset',
+ desc: 'Default theme preset with balanced settings'
+ },
+ soybean: {
+ name: 'Soybean',
+ desc: 'Default theme preset of SoybeanAdmin'
+ },
+ dark: {
+ name: 'Dark Preset',
+ desc: 'Dark theme preset for night time usage'
+ },
+ compact: {
+ name: 'Compact Preset',
+ desc: 'Compact layout preset for small screens'
+ },
+ azir: {
+ name: "Azir's Preset",
+ desc: 'It is a cold and elegant preset that Azir likes'
+ }
+ }
+ },
+ layout: {
+ layoutMode: {
+ title: 'Layout Mode',
+ vertical: 'Vertical Mode',
+ horizontal: 'Horizontal Mode',
+ 'vertical-mix': 'Vertical Mix Mode',
+ 'vertical-hybrid-header-first': 'Left Hybrid Header-First',
+ 'top-hybrid-sidebar-first': 'Top-Hybrid Sidebar-First',
+ 'top-hybrid-header-first': 'Top-Hybrid Header-First',
+ vertical_detail: 'Vertical menu layout, with the menu on the left and content on the right.',
+ 'vertical-mix_detail':
+ 'Vertical mix-menu layout, with the primary menu on the dark left side and the secondary menu on the lighter left side.',
+ 'vertical-hybrid-header-first_detail':
+ 'Left hybrid layout, with the primary menu at the top, the secondary menu on the dark left side, and the tertiary menu on the lighter left side.',
+ horizontal_detail: 'Horizontal menu layout, with the menu at the top and content below.',
+ 'top-hybrid-sidebar-first_detail':
+ 'Top hybrid layout, with the primary menu on the left and the secondary menu at the top.',
+ 'top-hybrid-header-first_detail':
+ 'Top hybrid layout, with the primary menu at the top and the secondary menu on the left.'
+ },
+ tab: {
+ title: 'Tab Settings',
+ visible: 'Tab Visible',
+ cache: 'Tag Bar Info Cache',
+ cacheTip: 'One-click to open/close global keepalive',
+ height: 'Tab Height',
+ mode: {
+ title: 'Tab Mode',
+ slider: 'Slider',
+ chrome: 'Chrome',
+ button: 'Button'
+ },
+ closeByMiddleClick: 'Close Tab by Middle Click',
+ closeByMiddleClickTip: 'Enable closing tabs by clicking with the middle mouse button'
+ },
+ header: {
+ title: 'Header Settings',
+ height: 'Header Height',
+ breadcrumb: {
+ visible: 'Breadcrumb Visible',
+ showIcon: 'Breadcrumb Icon Visible'
+ }
+ },
+ sider: {
+ title: 'Sider Settings',
+ inverted: 'Dark Sider',
+ width: 'Sider Width',
+ collapsedWidth: 'Sider Collapsed Width',
+ mixWidth: 'Mix Sider Width',
+ mixCollapsedWidth: 'Mix Sider Collapse Width',
+ mixChildMenuWidth: 'Mix Child Menu Width',
+ autoSelectFirstMenu: 'Auto Select First Submenu',
+ autoSelectFirstMenuTip:
+ 'When a first-level menu is clicked, the first submenu is automatically selected and navigated to the deepest level'
+ },
+ footer: {
+ title: 'Footer Settings',
+ visible: 'Footer Visible',
+ fixed: 'Fixed Footer',
+ height: 'Footer Height',
+ right: 'Right Footer'
+ },
+ content: {
+ title: 'Content Area Settings',
+ scrollMode: {
+ title: 'Scroll Mode',
+ tip: 'The theme scroll only scrolls the main part, the outer scroll can carry the header and footer together',
+ wrapper: 'Wrapper',
+ content: 'Content'
+ },
+ page: {
+ animate: 'Page Animate',
+ mode: {
+ title: 'Page Animate Mode',
+ fade: 'Fade',
+ 'fade-slide': 'Slide',
+ 'fade-bottom': 'Fade Zoom',
+ 'fade-scale': 'Fade Scale',
+ 'zoom-fade': 'Zoom Fade',
+ 'zoom-out': 'Zoom Out',
+ none: 'None'
+ }
+ },
+ fixedHeaderAndTab: 'Fixed Header And Tab'
+ }
+ },
+ general: {
+ title: 'General Settings',
+ watermark: {
+ title: 'Watermark Settings',
+ visible: 'Watermark Full Screen Visible',
+ text: 'Custom Watermark Text',
+ enableUserName: 'Enable User Name Watermark',
+ enableTime: 'Show Current Time',
+ timeFormat: 'Time Format'
+ },
+ multilingual: {
+ title: 'Multilingual Settings',
+ visible: 'Display multilingual button'
+ },
+ globalSearch: {
+ title: 'Global Search Settings',
+ visible: 'Display GlobalSearch button'
+ }
+ },
+ configOperation: {
+ copyConfig: 'Copy Config',
+ copySuccessMsg: 'Copy Success, Please replace the variable "themeSettings" in "src/theme/settings.ts"',
+ resetConfig: 'Reset Config',
+ resetSuccessMsg: 'Reset Success'
+ },
+ tablePropsTitle: 'Table Props',
+ table: {
+ size: {
+ title: 'Table Size',
+ small: 'Small',
+ medium: 'Medium',
+ large: 'Large'
+ },
+ bordered: 'Bordered',
+ bottomBordered: 'Bottom Bordered',
+ singleColumn: 'Single Column',
+ singleLine: 'Single Line',
+ striped: 'Striped'
+ }
+ },
+ route: {
+ login: 'Login',
+ 403: 'No Permission',
+ 404: 'Page Not Found',
+ 500: 'Server Error',
+ 'iframe-page': 'Iframe',
+ home: 'Home',
+ system: 'System Management',
+ system_user: 'User Management',
+ system_role: 'Role Management',
+ system_menu: 'Menu Management',
+ system_dept: 'Dept Management',
+ system_post: 'Post Management',
+ system_dict: 'Dict Management',
+ system_config: 'Config Management',
+ system_notice: 'Notice Management',
+ system_oss: 'File Management',
+ 'system_oss-config': 'OSS Config',
+ system_client: 'Client Management',
+ system_tenant: 'Tenant Management',
+ 'system_tenant-package': 'Tenant Package Management',
+ monitor: 'Monitor',
+ monitor_logininfor: 'Login Log',
+ monitor_operlog: 'Operate Log',
+ monitor_cache: 'Cache Monitor',
+ monitor_online: 'Online User',
+ 'user-center': 'User Center',
+ 'social-callback': 'Social Callback',
+ demo: 'Demo',
+ demo_demo: 'Demo Table',
+ demo_tree: 'Demo Tree',
+ exception: 'Exception',
+ exception_403: '403',
+ exception_404: '404',
+ exception_500: '500',
+ tool: 'System Tools',
+ tool_gen: 'Code Generation',
+ about: 'About'
+ },
+ menu: {
+ system_tenant: 'Tenant Management',
+ system_log: 'Log Management',
+ 'monitor_snail-job': 'Job Management',
+ monitor_admin: 'Admin Monitor'
+ },
+ dict: {
+ sys_user_sex: {
+ male: 'Male',
+ female: 'Female',
+ unknown: 'Unknown'
+ },
+ sys_show_hide: {
+ show: 'Show',
+ hide: 'Hide'
+ },
+ sys_normal_disable: {
+ normal: 'Normal',
+ disable: 'Disable'
+ },
+ sys_yes_no: {
+ yes: 'Yes',
+ no: 'No'
+ },
+ sys_notice_type: {
+ notice: 'Notice',
+ announcement: 'Announcement'
+ },
+ sys_notice_status: {
+ normal: 'Normal',
+ close: 'Close'
+ },
+ sys_oper_type: {
+ insert: 'Insert',
+ update: 'Update',
+ delete: 'Delete',
+ grant: 'Grant',
+ export: 'Export',
+ import: 'Import',
+ force: 'Force',
+ gencode: 'Generate Code',
+ clean: 'Clean Data',
+ other: 'Other'
+ },
+ sys_common_status: {
+ success: 'Success',
+ fail: 'Fail'
+ },
+ sys_grant_type: {
+ password: 'Password Auth',
+ sms: 'SMS Auth',
+ email: 'Email Auth',
+ miniapp: 'Mini App Auth',
+ social: 'Social Auth'
+ },
+ sys_device_type: {
+ pc: 'PC',
+ android: 'Android',
+ ios: 'iOS',
+ miniapp: 'Mini App'
+ },
+ wf_business_status: {
+ revoked: 'Revoked',
+ draft: 'Draft',
+ pending: 'Pending',
+ completed: 'Completed',
+ cancelled: 'Cancelled',
+ returned: 'Returned',
+ terminated: 'Terminated'
+ },
+ wf_form_type: {
+ custom_form: 'Custom Form',
+ dynamic_form: 'Dynamic Form'
+ },
+ wf_task_status: {
+ revoke: 'Revoke',
+ pass: 'Pass',
+ pending_review: 'Pending Review',
+ cancel: 'Cancel',
+ return: 'Return',
+ terminate: 'Terminate',
+ transfer: 'Transfer',
+ delegate: 'Delegate',
+ copy: 'Copy',
+ add_sign: 'Add Sign',
+ minus_sign: 'Minus Sign',
+ timeout: 'Timeout'
+ }
+ },
+ page: {
+ login: {
+ common: {
+ title: 'Intelligent Tobacco Factory Integrated Management Platform',
+ subTitle: 'Providing comprehensive production and management solutions for tobacco enterprises',
+ loginOrRegister: 'Login / Register',
+ register: 'Register',
+ userNamePlaceholder: 'Please enter user name',
+ phonePlaceholder: 'Please enter phone number',
+ codePlaceholder: 'Please enter verification code',
+ passwordPlaceholder: 'Please enter password',
+ confirmPasswordPlaceholder: 'Please enter password again',
+ codeLogin: 'Verification code login',
+ confirm: 'Confirm',
+ back: 'Back',
+ validateSuccess: 'Verification passed',
+ loginSuccess: 'Login successfully',
+ welcomeBack: 'Welcome back, {userName} !'
+ },
+ pwdLogin: {
+ title: 'Password Login',
+ rememberMe: 'Remember password',
+ forgetPassword: 'Forget password?',
+ register: 'Register',
+ otherAccountLogin: 'Other Account Login',
+ otherLoginMode: 'Other Login Mode',
+ superAdmin: 'Super Admin',
+ admin: 'Admin',
+ user: 'User'
+ },
+ codeLogin: {
+ title: 'Verification Code Login',
+ getCode: 'Get verification code',
+ reGetCode: 'Reacquire after {time}s',
+ sendCodeSuccess: 'Verification code sent successfully',
+ imageCodePlaceholder: 'Please enter image verification code'
+ },
+ register: {
+ title: 'Register',
+ agreement: 'I have read and agree to',
+ protocol: '銆奤ser Agreement銆�',
+ policy: '銆奝rivacy Policy銆�'
+ },
+ resetPwd: {
+ title: 'Reset Password'
+ },
+ bindWeChat: {
+ title: 'Bind WeChat'
+ }
+ },
+ home: {
+ branchDesc:
+ 'For the convenience of everyone in developing and updating the merge, we have streamlined the code of the main branch, only retaining the homepage menu, and the rest of the content has been moved to the example branch for maintenance. The preview address displays the content of the example branch.',
+ greeting: 'Good morning, {userName}, today is another day full of vitality!',
+ weatherDesc: 'Today is cloudy to clear, 20鈩� - 25鈩�!',
+ projectCount: 'Project Count',
+ todo: 'Todo',
+ message: 'Message',
+ downloadCount: 'Download Count',
+ registerCount: 'Register Count',
+ schedule: 'Work and rest Schedule',
+ study: 'Study',
+ work: 'Work',
+ rest: 'Rest',
+ entertainment: 'Entertainment',
+ visitCount: 'Visit Count',
+ turnover: 'Turnover',
+ dealCount: 'Deal Count',
+ projectNews: {
+ title: 'Project News',
+ moreNews: 'More News',
+ desc1: 'Soybean created the open source project soybean-admin on May 28, 2021!',
+ desc2: 'Yanbowe submitted a bug to soybean-admin, the multi-tab bar will not adapt.',
+ desc3: 'Soybean is ready to do sufficient preparation for the release of soybean-admin!',
+ desc4: 'Soybean is busy writing project documentation for soybean-admin!',
+ desc5: 'Soybean just wrote some of the workbench pages casually, and it was enough to see!'
+ },
+ creativity: 'Creativity'
+ },
+ common: {
+ id: 'ID',
+ createBy: 'Creator',
+ createTime: 'Create Time',
+ updateBy: 'Updater',
+ updateTime: 'Update Time',
+ remark: 'Remark',
+ form: {
+ remark: {
+ required: 'Please enter remark',
+ invalid: 'Remark cannot be empty'
+ }
+ }
+ },
+ system: {
+ client: {
+ title: 'Client List',
+ clientId: 'Client ID',
+ clientKey: 'Client Key',
+ clientSecret: 'Client Secret',
+ grantTypeList: 'Grant Type',
+ deviceType: 'Device Type',
+ activeTimeout: 'Token Active Timeout',
+ timeout: 'Token Timeout',
+ status: 'Status',
+ form: {
+ clientId: {
+ required: 'Please enter Client ID',
+ invalid: 'Client ID cannot be empty'
+ },
+ clientKey: {
+ required: 'Please enter Client Key',
+ invalid: 'Client Key cannot be empty'
+ },
+ clientSecret: {
+ required: 'Please enter Client Secret',
+ invalid: 'Client Secret cannot be empty'
+ },
+ grantTypeList: {
+ required: 'Please select Grant Type',
+ invalid: 'Grant Type cannot be empty'
+ },
+ deviceType: {
+ required: 'Please select Device Type',
+ invalid: 'Device Type cannot be empty'
+ },
+ activeTimeout: {
+ required: 'Please enter Active Timeout',
+ invalid: 'Active Timeout cannot be empty',
+ tooltip: 'Specify time without operation will expire (unit: second), default 30 minutes (1800 seconds)'
+ },
+ timeout: {
+ required: 'Please enter Timeout',
+ invalid: 'Timeout cannot be empty',
+ tooltip: 'Specify time will expire (unit: second), default 7 days (604800 seconds)'
+ },
+ status: {
+ required: 'Please select Status',
+ invalid: 'Status cannot be empty'
+ }
+ },
+ addClient: 'Add Client',
+ editClient: 'Edit Client'
+ },
+ config: {
+ title: 'Config List',
+ configName: 'Config Name',
+ configKey: 'Config Key',
+ configValue: 'Config Value',
+ configType: 'Built-in',
+ remark: 'Remark',
+ createTime: 'Create Time',
+ refreshCache: 'Refresh Cache',
+ refreshCacheSuccess: 'Refresh cache successfully',
+ form: {
+ configId: {
+ required: 'Please enter Config ID',
+ invalid: 'Config ID cannot be empty'
+ },
+ configName: {
+ required: 'Please enter Config Name',
+ invalid: 'Config Name cannot be empty'
+ },
+ configKey: {
+ required: 'Please enter Config Key',
+ invalid: 'Config Key cannot be empty'
+ },
+ configValue: {
+ required: 'Please enter Config Value',
+ invalid: 'Config Value cannot be empty'
+ },
+ configType: {
+ required: 'Please select Built-in status',
+ invalid: 'Built-in status cannot be empty'
+ },
+ remark: {
+ required: 'Please enter Remark',
+ invalid: 'Remark cannot be empty'
+ }
+ },
+ addConfig: 'Add Config',
+ editConfig: 'Edit Config'
+ },
+ dept: {
+ empty: 'No department information',
+ title: 'Department List',
+ parentId: 'Parent Department',
+ deptName: 'Department Name',
+ orderNum: 'Order Num',
+ deptCategory: 'Department Category',
+ leader: 'Leader',
+ phone: 'Phone',
+ email: 'Email',
+ status: 'Status',
+ sort: 'Sort',
+ createTime: 'Create Time',
+ expandAll: 'Expand All',
+ collapseAll: 'Collapse All',
+ form: {
+ parentId: {
+ required: 'Please select Parent Department',
+ invalid: 'Parent Department cannot be empty'
+ },
+ deptName: {
+ required: 'Please enter Department Name',
+ invalid: 'Department Name cannot be empty'
+ },
+ orderNum: {
+ required: 'Please enter Order Num',
+ invalid: 'Order num cannot be empty'
+ },
+ deptCategory: {
+ required: 'Please enter Department Category',
+ invalid: 'Department category cannot be empty'
+ },
+ leader: {
+ required: 'Please enter Leader',
+ invalid: 'Leader cannot be empty'
+ },
+ phone: {
+ required: 'Please enter Phone',
+ invalid: 'Phone cannot be empty'
+ },
+ email: {
+ required: 'Please enter Email',
+ invalid: 'Email cannot be empty'
+ },
+ status: {
+ required: 'Please select Status',
+ invalid: 'Status cannot be empty'
+ },
+ sort: {
+ required: 'Please enter Sort',
+ invalid: 'Sort cannot be empty'
+ },
+ deptId: {
+ required: 'Please enter deptId',
+ invalid: 'Dept Id cannot be empty'
+ }
+ },
+ error: {
+ getDeptDataFail: 'Get dept data fail',
+ getDeptUserDataFail: 'Get dept user data fail'
+ },
+ placeholder: {
+ defaultLeaderPlaceHolder: 'Please select leader',
+ addDataLeaderPlaceHolder: 'Department leader can be selected only when updating',
+ deptUserIsEmptyLeaderPlaceHolder: 'Current dept has no leader'
+ },
+ addDept: 'Add Department',
+ editDept: 'Edit Department'
+ },
+ dict: {
+ title: 'Dictionary List',
+ dictTypeTitle: 'Dictionary Type List',
+ dictName: 'Dictionary Name',
+ dictType: 'Dictionary Type',
+ status: 'Status',
+ remark: 'Remark',
+ createTime: 'Create Time',
+ refreshCacheSuccess: 'Refresh cache successfully',
+ refreshCache: 'Refresh Cache',
+ confirmDeleteDictType: 'Are you sure you want to delete dic type',
+ data: {
+ title: 'Dictionary Data List',
+ label: 'Dictionary Label',
+ value: 'Dictionary Value',
+ dictSort: 'Sort',
+ isDefault: 'Default',
+ listClass: 'Display Style',
+ cssClass: 'CSS Class',
+ status: 'Status',
+ remark: 'Remark',
+ createTime: 'Create Time'
+ },
+ form: {
+ dictId: {
+ required: 'Please enter Dictionary Id',
+ invalid: 'Dictionary Id cannot be empty'
+ },
+ dictCode: {
+ required: 'Please enter Dictionary Code',
+ invalid: 'Dictionary Code cannot be empty'
+ },
+ dictName: {
+ required: 'Please enter Dictionary Name',
+ invalid: 'Dictionary Name cannot be empty'
+ },
+ dictType: {
+ required: 'Please enter Dictionary Type',
+ invalid: 'Dictionary Type cannot be empty'
+ },
+ status: {
+ required: 'Please select Status',
+ invalid: 'Status cannot be empty'
+ },
+ remark: {
+ required: 'Please enter Remark',
+ invalid: 'Remark cannot be empty'
+ },
+ dictLabel: {
+ required: 'Please enter Dictionary Label',
+ invalid: 'Dictionary Label cannot be empty'
+ },
+ dictValue: {
+ required: 'Please enter Dictionary Value',
+ invalid: 'Dictionary Value cannot be empty'
+ },
+ dictSort: {
+ required: 'Please enter Sort',
+ invalid: 'Sort cannot be empty'
+ },
+ isDefault: {
+ required: 'Please select Default',
+ invalid: 'Default cannot be empty'
+ },
+ listClass: {
+ required: 'Please select Display Style',
+ invalid: 'Display Style cannot be empty'
+ },
+ cssClass: {
+ required: 'Please enter CSS Class',
+ invalid: 'CSS Class cannot be empty'
+ }
+ },
+ addDict: 'Add Dictionary',
+ editDict: 'Edit Dictionary',
+ addDictData: 'Add Dictionary Data',
+ editDictData: 'Edit Dictionary Data',
+ addDictType: 'Add Dictionary Type',
+ editDictType: 'Edit Dictionary Type',
+ exportDictType: 'Export Dictionary Type',
+ refreshDictType: 'Refresh Dictionary Type',
+ dictTypeIsEmpty: 'Dictionary type is empty'
+ },
+ menu: {
+ title: 'Menu List',
+ parentId: 'Parent Menu',
+ iconType: 'Icon Type',
+ menuName: 'Menu Name',
+ icon: 'Menu Icon',
+ orderNum: 'Sort',
+ perms: 'Permission Code',
+ component: 'Component Path',
+ path: 'Route Path',
+ layout: 'Layout',
+ externalPath: 'External Path',
+ query: 'Route Parameters',
+ iframeQuery: 'Iframe Address',
+ isFrame: 'External Link',
+ isCache: 'Cache',
+ menuType: 'Menu Type',
+ visible: 'Visible',
+ status: 'Status',
+ createTime: 'Create Time',
+ cache: 'cache',
+ noCache: 'No Cache',
+ rootName: 'Root',
+ buttonPermissionList: 'Button Permission List',
+ emptyMenu: 'Empty Menu',
+ menuDetail: 'Menu Detail',
+ cascadeDeleteContent: 'Cascade delete menu will delete the selected menu and all its sub-menus, are you sure?',
+ iconifyTip: 'iconify address锛歚https://icones.js.org`',
+ isFrameTip: 'If you choose External Link, the routing address needs to start with `http(s)://`',
+ isCacheTip:
+ 'If you select yes, it will be cached by `keep-alive`, and the `name` and address of the matching component must be consistent',
+ visibleTip: 'If you choose Hide, the route will not appear in the sidebar, but it can still be accessed.',
+ statusTip: 'If you choose to disable, the route will not appear in the sidebar and cannot be accessed.',
+ permsTip: "Permission string defined in the controller, such as: {'@'}SaCheckPermission('system:user:list')",
+ componentTip:
+ 'The component path to access, such as: `system/user/index`, which is in the `views` directory by default',
+ pathTip:
+ 'Router path锛孍xample锛歚user`锛孖f the external network address needs to be accessed in the internal link,then `http(s)://` beginning',
+ layoutTip:
+ 'Default Layout: A layout that includes common sections such as the global header, sidebar, footer, etc;\nBlank Layout: A layout without any common sections, typically used for pages like the login page',
+ form: {
+ parentId: {
+ required: 'Please select Parent Menu',
+ invalid: 'Parent Menu cannot be empty'
+ },
+ menuType: {
+ required: 'Please select Menu Type',
+ invalid: 'Menu Type cannot be empty'
+ },
+ icon: {
+ required: 'Please select Menu Icon',
+ invalid: 'Menu Icon cannot be empty'
+ },
+ menuIds: {
+ required: 'Please select Menu',
+ invalid: 'Menu cannot be empty'
+ },
+ menuName: {
+ required: 'Please enter Menu Name',
+ invalid: 'Menu Name cannot be empty'
+ },
+ perms: {
+ required: 'Please enter permission code',
+ invalid: 'Permission code cannot be empty'
+ },
+ orderNum: {
+ required: 'Please enter order num',
+ invalid: 'Order num cannot be empty'
+ },
+ isFrame: {
+ required: 'Please select External Link',
+ invalid: 'External Link cannot be empty'
+ },
+ path: {
+ required: 'Please enter Route Path',
+ invalid: 'Route Path cannot be empty'
+ },
+ component: {
+ required: 'Please enter Component Path',
+ invalid: 'Component Path cannot be empty'
+ },
+ query: {
+ required: 'Please enter Route Parameters',
+ invalid: 'Route Parameters cannot be empty'
+ },
+ isCache: {
+ required: 'Please select Cache',
+ invalid: 'Cache cannot be empty'
+ },
+ visible: {
+ required: 'Please select Visible',
+ invalid: 'Visible cannot be empty'
+ },
+ status: {
+ required: 'Please select Status',
+ invalid: 'Status cannot be empty'
+ },
+ permission: {
+ required: 'Please enter Permission',
+ invalid: 'Permission cannot be empty'
+ }
+ },
+ placeholder: {
+ iconifyIconPlaceholder: 'Please enter an icon',
+ localIconPlaceholder: 'Please select the local icon',
+ queryKey: 'Please enter a key',
+ queryValue: 'Please enter a value',
+ queryIframe: 'Please enter a iframe address'
+ },
+ directory: 'Directory',
+ menu: 'Menu',
+ button: 'Button',
+ addMenu: 'Add Menu',
+ addChildMenu: 'Add Child Menu',
+ editMenu: 'Edit Menu',
+ cascadeDelete: 'Cascade Delete Menu'
+ },
+ notice: {
+ title: 'Notice List',
+ noticeTitle: 'Notice Title',
+ noticeType: 'Notice Type',
+ noticeContent: 'Notice Content',
+ status: 'Status',
+ createTime: 'Create Time',
+ form: {
+ noticeTitle: {
+ required: 'Please enter Notice Title',
+ invalid: 'Notice Title cannot be empty'
+ },
+ noticeType: {
+ required: 'Please select Notice Type',
+ invalid: 'Notice Type cannot be empty'
+ },
+ noticeContent: {
+ required: 'Please enter Notice Content',
+ invalid: 'Notice Content cannot be empty'
+ },
+ status: {
+ required: 'Please select Status',
+ invalid: 'Status cannot be empty'
+ }
+ },
+ addNotice: 'Add Notice',
+ editNotice: 'Edit Notice'
+ },
+ oss: {
+ title: 'File List',
+ fileName: 'File Name',
+ originalName: 'Original Name',
+ fileSuffix: 'File Extension',
+ url: 'File URL',
+ createTime: 'Create Time',
+ service: 'Service Provider',
+ form: {
+ file: {
+ required: 'Please select a file',
+ invalid: 'File cannot be empty'
+ }
+ },
+ upload: 'Upload File',
+ preview: 'Preview',
+ download: 'Download',
+ copy: 'Copy Link',
+ copySuccess: 'Copy Success'
+ },
+ ossConfig: {
+ title: 'OSS Config List',
+ configKey: 'Config Key',
+ accessKey: 'Access Key',
+ secretKey: 'Secret Key',
+ bucketName: 'Bucket Name',
+ prefix: 'Prefix',
+ endpoint: 'Endpoint',
+ domain: 'Custom Domain',
+ isHttps: 'HTTPS',
+ region: 'Region',
+ status: 'Status',
+ remark: 'Remark',
+ createTime: 'Create Time',
+ form: {
+ configKey: {
+ required: 'Please enter Config Key',
+ invalid: 'Config Key cannot be empty'
+ },
+ accessKey: {
+ required: 'Please enter Access Key',
+ invalid: 'Access Key cannot be empty'
+ },
+ secretKey: {
+ required: 'Please enter Secret Key',
+ invalid: 'Secret Key cannot be empty'
+ },
+ bucketName: {
+ required: 'Please enter Bucket Name',
+ invalid: 'Bucket Name cannot be empty'
+ },
+ prefix: {
+ required: 'Please enter Prefix',
+ invalid: 'Prefix cannot be empty'
+ },
+ endpoint: {
+ required: 'Please enter Endpoint',
+ invalid: 'Endpoint cannot be empty'
+ },
+ domain: {
+ required: 'Please enter Custom Domain',
+ invalid: 'Custom Domain cannot be empty'
+ },
+ isHttps: {
+ required: 'Please select HTTPS',
+ invalid: 'HTTPS cannot be empty'
+ },
+ region: {
+ required: 'Please enter Region',
+ invalid: 'Region cannot be empty'
+ },
+ status: {
+ required: 'Please select Status',
+ invalid: 'Status cannot be empty'
+ },
+ remark: {
+ required: 'Please enter Remark',
+ invalid: 'Remark cannot be empty'
+ }
+ },
+ addOssConfig: 'Add OSS Config',
+ editOssConfig: 'Edit OSS Config'
+ },
+ post: {
+ title: 'Post List',
+ postCode: 'Post Code',
+ postName: 'Post Name',
+ postSort: 'Post Sort',
+ status: 'Status',
+ remark: 'Remark',
+ createTime: 'Create Time',
+ form: {
+ postCode: {
+ required: 'Please enter Post Code',
+ invalid: 'Post Code cannot be empty'
+ },
+ postName: {
+ required: 'Please enter Post Name',
+ invalid: 'Post Name cannot be empty'
+ },
+ postSort: {
+ required: 'Please enter Post Sort',
+ invalid: 'Post Sort cannot be empty'
+ },
+ status: {
+ required: 'Please select Status',
+ invalid: 'Status cannot be empty'
+ },
+ remark: {
+ required: 'Please enter Remark',
+ invalid: 'Remark cannot be empty'
+ }
+ },
+ addPost: 'Add Post',
+ editPost: 'Edit Post'
+ },
+ role: {
+ title: 'Role List',
+ roleName: 'Role Name',
+ roleKey: 'Role Key',
+ roleSort: 'Role Sort',
+ status: 'Status',
+ remark: 'Remark',
+ menuPermission: 'Menu Permission',
+ dataScope: 'Data Scope',
+ createTime: 'Create Time',
+ form: {
+ roleName: {
+ required: 'Please enter Role Name',
+ invalid: 'Role Name cannot be empty'
+ },
+ roleKey: {
+ required: 'Please enter Role Key',
+ invalid: 'Role Key cannot be empty'
+ },
+ roleSort: {
+ required: 'Please enter Role Sort',
+ invalid: 'Role Sort cannot be empty'
+ },
+ status: {
+ required: 'Please select Status',
+ invalid: 'Status cannot be empty'
+ },
+ remark: {
+ required: 'Please enter Remark',
+ invalid: 'Remark cannot be empty'
+ },
+ menuIds: {
+ required: 'Please select Menu Permission',
+ invalid: 'Menu Permission cannot be empty'
+ },
+ deptIds: {
+ required: 'Please select Dept Permission',
+ invalid: 'Dept Permission cannot be empty'
+ }
+ },
+ addRole: 'Add Role',
+ editRole: 'Edit Role',
+ configPermission: 'Assign Permissions',
+ authorizedUsers: 'Assign Users',
+ selectMenuPermission: 'Select Menu Permission',
+ selectDataScope: 'Select Data Scope',
+ selectDeptPermission: 'Select Dept Permission'
+ },
+ tenant: {
+ title: 'Tenant List',
+ tenantName: 'Tenant Name',
+ tenantId: 'Tenant ID',
+ contactUserName: 'Contact Person',
+ contactPhone: 'Contact Phone',
+ companyName: 'Company Name',
+ licenseNumber: 'License Number',
+ address: 'Address',
+ intro: 'Introduction',
+ domain: 'Domain',
+ packageId: 'Tenant Package',
+ expireTime: 'Expiration Time',
+ accountCount: 'Account Count',
+ status: 'Status',
+ createTime: 'Create Time',
+ form: {
+ tenantName: {
+ required: 'Please enter Tenant Name',
+ invalid: 'Tenant Name cannot be empty'
+ },
+ contactUserName: {
+ required: 'Please enter Contact Person',
+ invalid: 'Contact Person cannot be empty'
+ },
+ contactPhone: {
+ required: 'Please enter Contact Phone',
+ invalid: 'Contact Phone cannot be empty'
+ },
+ companyName: {
+ required: 'Please enter Company Name',
+ invalid: 'Company Name cannot be empty'
+ },
+ licenseNumber: {
+ required: 'Please enter License Number',
+ invalid: 'License Number cannot be empty'
+ },
+ address: {
+ required: 'Please enter Address',
+ invalid: 'Address cannot be empty'
+ },
+ intro: {
+ required: 'Please enter Introduction',
+ invalid: 'Introduction cannot be empty'
+ },
+ domain: {
+ required: 'Please enter Domain',
+ invalid: 'Domain cannot be empty'
+ },
+ packageId: {
+ required: 'Please select Tenant Package',
+ invalid: 'Tenant Package cannot be empty'
+ },
+ expireTime: {
+ required: 'Please select Expiration Time',
+ invalid: 'Expiration Time cannot be empty'
+ },
+ accountCount: {
+ required: 'Please enter Account Count',
+ invalid: 'Account Count cannot be empty'
+ },
+ status: {
+ required: 'Please select Status',
+ invalid: 'Status cannot be empty'
+ }
+ },
+ addTenant: 'Add Tenant',
+ editTenant: 'Edit Tenant'
+ },
+ tenantPackage: {
+ title: 'Tenant Package List',
+ packageName: 'Package Name',
+ menuIds: 'Menu Permission',
+ remark: 'Remark',
+ status: 'Status',
+ createTime: 'Create Time',
+ form: {
+ packageName: {
+ required: 'Please enter Package Name',
+ invalid: 'Package Name cannot be empty'
+ },
+ menuIds: {
+ required: 'Please select Menu Permission',
+ invalid: 'Menu Permission cannot be empty'
+ },
+ status: {
+ required: 'Please select Status',
+ invalid: 'Status cannot be empty'
+ },
+ remark: {
+ required: 'Please enter Remark',
+ invalid: 'Remark cannot be empty'
+ }
+ },
+ addTenantPackage: 'Add Tenant Package',
+ editTenantPackage: 'Edit Tenant Package',
+ statusChangeSuccess: 'Status modified successfully'
+ },
+ user: {
+ title: 'User List',
+ userName: 'Username',
+ nickName: 'Nickname',
+ deptName: 'Department',
+ phonenumber: 'Phone Number',
+ status: 'Status',
+ createTime: 'Create Time',
+ password: 'Password',
+ confirmPassword: 'Confirm Password',
+ sex: 'Gender',
+ roleIds: 'Roles',
+ postIds: 'Posts',
+ email: 'Email',
+ avatar: 'Avatar',
+ remark: 'Remark',
+ form: {
+ userName: {
+ required: 'Please enter Username',
+ invalid: 'Username cannot be empty'
+ },
+ nickName: {
+ required: 'Please enter Nickname',
+ invalid: 'Nickname cannot be empty'
+ },
+ deptId: {
+ required: 'Please select Department',
+ invalid: 'Department cannot be empty'
+ },
+ phonenumber: {
+ required: 'Please enter Phone Number',
+ invalid: 'Phone Number cannot be empty'
+ },
+ status: {
+ required: 'Please select Status',
+ invalid: 'Status cannot be empty'
+ },
+ password: {
+ required: 'Please enter Password',
+ invalid: 'Password cannot be empty'
+ },
+ confirmPassword: {
+ required: 'Please enter Confirm Password',
+ invalid: 'Confirm Password cannot be empty'
+ },
+ sex: {
+ required: 'Please select Gender',
+ invalid: 'Gender cannot be empty'
+ },
+ roleIds: {
+ required: 'Please select Roles',
+ invalid: 'Roles cannot be empty'
+ },
+ postIds: {
+ required: 'Please select Posts',
+ invalid: 'Posts cannot be empty'
+ },
+ email: {
+ required: 'Please enter Email',
+ invalid: 'Email cannot be empty'
+ },
+ remark: {
+ required: 'Please enter Remark',
+ invalid: 'Remark cannot be empty'
+ }
+ },
+ addUser: 'Add User',
+ editUser: 'Edit User',
+ resetPassword: 'Reset Password',
+ importUsers: 'Import Users',
+ exportTemplate: 'Export Template',
+ importSuccess: 'Import successful',
+ statusChangeSuccess: 'Status modified successfully'
+ }
+ },
+ about: {
+ title: 'About',
+ introduction: `RuoYi-Plus-Soybean is an intelligent tobacco factory integrated management platform. It combines the powerful backend capabilities of RuoYi-Vue-Plus with the modern frontend features of Soybean Admin, providing tobacco enterprises with a complete production and management solution.`,
+ projectInfo: {
+ title: 'Project Info',
+ version: 'Version',
+ latestBuildTime: 'Latest Build Time',
+ documentLink: 'Document Link',
+ previewLink: 'Preview Link',
+ repositoryLink: 'Repository Link'
+ },
+ prdDep: 'Production Dependency',
+ devDep: 'Development Dependency'
+ }
+ },
+ form: {
+ required: 'Cannot be empty',
+ userName: {
+ required: 'Please enter user name',
+ invalid: 'User name format is incorrect'
+ },
+ phone: {
+ required: 'Please enter phone number',
+ invalid: 'Phone number format is incorrect'
+ },
+ pwd: {
+ required: 'Please enter password',
+ invalid: '6-18 characters, including letters, numbers, and underscores'
+ },
+ confirmPwd: {
+ required: 'Please enter password again',
+ invalid: 'The two passwords are inconsistent'
+ },
+ code: {
+ required: 'Please enter verification code',
+ invalid: 'Verification code format is incorrect'
+ },
+ email: {
+ required: 'Please enter email',
+ invalid: 'Email format is incorrect'
+ }
+ },
+ dropdown: {
+ closeCurrent: 'Close Current',
+ closeOther: 'Close Other',
+ closeLeft: 'Close Left',
+ closeRight: 'Close Right',
+ closeAll: 'Close All',
+ pin: 'Pin Tab',
+ unpin: 'Unpin Tab'
+ },
+ icon: {
+ themeConfig: 'Theme Configuration',
+ themeSchema: 'Theme Schema',
+ lang: 'Switch Language',
+ fullscreen: 'Fullscreen',
+ fullscreenExit: 'Exit Fullscreen',
+ reload: 'Reload Page',
+ collapse: 'Collapse Menu',
+ expand: 'Expand Menu',
+ pin: 'Pin',
+ unpin: 'Unpin'
+ },
+ datatable: {
+ itemCount: 'Total {total} items'
+ }
+};
+
+export default local;
diff --git a/ruoyi-plus-soybean/src/locales/langs/zh-cn.ts b/ruoyi-plus-soybean/src/locales/langs/zh-cn.ts
new file mode 100755
index 0000000..4b3262c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/locales/langs/zh-cn.ts
@@ -0,0 +1,1271 @@
+const local: App.I18n.Schema = {
+ system: {
+ title: '璐ㄩ噺鍒嗘瀽绯荤粺',
+ updateTitle: '绯荤粺鐗堟湰鏇存柊閫氱煡',
+ updateContent: '妫�娴嬪埌绯荤粺鏈夋柊鐗堟湰鍙戝竷锛屾槸鍚︾珛鍗冲埛鏂伴〉闈紵',
+ updateConfirm: '绔嬪嵆鍒锋柊',
+ updateCancel: '绋嶅悗鍐嶈'
+ },
+ common: {
+ action: '鎿嶄綔',
+ add: '鏂板',
+ addSuccess: '娣诲姞鎴愬姛',
+ backToHome: '杩斿洖棣栭〉',
+ batchDelete: '鎵归噺鍒犻櫎',
+ import: '瀵煎叆',
+ export: '瀵煎嚭',
+ importSuccess: '瀵煎叆鎴愬姛',
+ importFail: '瀵煎叆澶辫触',
+ importTemplate: '瀵煎叆妯℃澘',
+ downloadTemplate: '涓嬭浇妯℃澘',
+ importResult: '瀵煎叆缁撴灉',
+ importSize: '璇蜂笂浼犲ぇ灏忎笉瓒呰繃',
+ importEnd: '鐨勬枃浠�',
+ importFormat: '涓旀牸寮忎负',
+ importTip: '璇蜂笂浼犲ぇ灏忎笉瓒呰繃',
+ exportSuccess: '瀵煎嚭鎴愬姛',
+ exportFail: '瀵煎嚭澶辫触',
+ updateExisting: '鏄惁鏇存柊宸茬粡瀛樺湪鐨勬暟鎹�',
+ cancel: '鍙栨秷',
+ close: '鍏抽棴',
+ check: '鍕鹃��',
+ expandColumn: '灞曞紑鍒�',
+ columnSetting: '鍒楄缃�',
+ config: '閰嶇疆',
+ login: '鐧诲綍',
+ confirm: '纭',
+ save: '淇濆瓨',
+ delete: '鍒犻櫎',
+ deleteSuccess: '鍒犻櫎鎴愬姛',
+ confirmDelete: '纭鍒犻櫎鍚楋紵',
+ edit: '缂栬緫',
+ download: '涓嬭浇',
+ warning: '璀﹀憡',
+ error: '閿欒',
+ index: '搴忓彿',
+ keywordSearch: '璇疯緭鍏ュ叧閿瘝鎼滅储',
+ logout: '閫�鍑虹櫥褰�',
+ logoutConfirm: '纭閫�鍑虹櫥褰曞悧锛�',
+ lookForward: '鏁鏈熷緟',
+ modify: '淇敼',
+ modifySuccess: '淇敼鎴愬姛',
+ noData: '鏃犳暟鎹�',
+ operate: '鎿嶄綔',
+ pleaseCheckValue: '璇锋鏌ヨ緭鍏ョ殑鍊兼槸鍚﹀悎娉�',
+ refresh: '鍒锋柊',
+ reset: '閲嶇疆',
+ search: '鎼滅储',
+ switch: '鍒囨崲',
+ tip: '鎻愮ず',
+ trigger: '瑙﹀彂',
+ update: '鏇存柊',
+ saveSuccess: '淇濆瓨鎴愬姛',
+ updateSuccess: '鏇存柊鎴愬姛',
+ noChange: '娌℃湁杩涜浠讳綍鎿嶄綔',
+ userCenter: '涓汉涓績',
+ yesOrNo: {
+ yes: '鏄�',
+ no: '鍚�'
+ },
+ second: '绉�',
+ selected: '宸查�夋嫨',
+ anyRecords: '鏉¤褰�',
+ clear: '娓呯┖',
+ noSelectRecord: '鏈�変腑浠讳綍璁板綍'
+ },
+ request: {
+ logout: '璇锋眰澶辫触鍚庣櫥鍑虹敤鎴�',
+ logoutMsg: '鐢ㄦ埛鐘舵�佸け鏁堬紝璇烽噸鏂扮櫥褰�',
+ logoutWithModal: '璇锋眰澶辫触鍚庡脊鍑烘ā鎬佹鍐嶇櫥鍑虹敤鎴�',
+ logoutWithModalMsg: '鐢ㄦ埛鐘舵�佸け鏁堬紝璇烽噸鏂扮櫥褰�',
+ refreshToken: '璇锋眰鐨則oken宸茶繃鏈燂紝鍒锋柊token',
+ tokenExpired: 'token宸茶繃鏈�'
+ },
+ theme: {
+ themeDrawerTitle: '涓婚閰嶇疆',
+ tabs: {
+ appearance: '澶栬',
+ layout: '甯冨眬',
+ general: '閫氱敤',
+ preset: '棰勮'
+ },
+ appearance: {
+ themeSchema: {
+ title: '涓婚妯″紡',
+ light: '浜壊妯″紡',
+ dark: '鏆楅粦妯″紡',
+ auto: '璺熼殢绯荤粺'
+ },
+ grayscale: '鐏拌壊妯″紡',
+ colourWeakness: '鑹插急妯″紡',
+ themeColor: {
+ title: '涓婚棰滆壊',
+ primary: '涓昏壊',
+ info: '淇℃伅鑹�',
+ success: '鎴愬姛鑹�',
+ warning: '璀﹀憡鑹�',
+ error: '閿欒鑹�',
+ followPrimary: '璺熼殢涓昏壊'
+ },
+ themeRadius: {
+ title: '涓婚鍦嗚'
+ },
+ recommendColor: '搴旂敤鎺ㄨ崘绠楁硶鐨勯鑹�',
+ recommendColorDesc: '鎺ㄨ崘棰滆壊鐨勭畻娉曞弬鐓�',
+ preset: {
+ title: '涓婚棰勮',
+ apply: '搴旂敤',
+ applySuccess: '棰勮搴旂敤鎴愬姛',
+ default: {
+ name: '榛樿棰勮',
+ desc: '绯荤粺榛樿涓婚棰勮'
+ },
+ soybean: {
+ name: 'Soybean',
+ desc: 'Soybean 榛樿涓婚棰勮'
+ },
+ dark: {
+ name: '鏆楄壊棰勮',
+ desc: '閫傜敤浜庡闂翠娇鐢ㄧ殑鏆楄壊涓婚棰勮'
+ },
+ compact: {
+ name: '绱у噾鍨�',
+ desc: '閫傜敤浜庡皬灞忓箷鐨勭揣鍑戝竷灞�棰勮'
+ },
+ azir: {
+ name: 'Azir鐨勯璁�',
+ desc: '鏄� Azir 姣旇緝鍠滄鐨勮帿鍏拌开鑹茬郴鍐锋贰椋�'
+ }
+ }
+ },
+ layout: {
+ layoutMode: {
+ title: '甯冨眬妯″紡',
+ vertical: '宸︿晶鑿滃崟妯″紡',
+ 'vertical-mix': '宸︿晶鑿滃崟娣峰悎妯″紡',
+ 'vertical-hybrid-header-first': '宸︿晶娣峰悎-椤堕儴浼樺厛',
+ horizontal: '椤堕儴鑿滃崟妯″紡',
+ 'top-hybrid-sidebar-first': '椤堕儴娣峰悎-渚ц竟浼樺厛',
+ 'top-hybrid-header-first': '椤堕儴娣峰悎-椤堕儴浼樺厛',
+ vertical_detail: '宸︿晶鑿滃崟甯冨眬锛岃彍鍗曞湪宸︼紝鍐呭鍦ㄥ彸銆�',
+ 'vertical-mix_detail': '宸︿晶鍙岃彍鍗曞竷灞�锛屼竴绾ц彍鍗曞湪宸︿晶娣辫壊鍖哄煙锛屼簩绾ц彍鍗曞湪宸︿晶娴呰壊鍖哄煙銆�',
+ 'vertical-hybrid-header-first_detail':
+ '宸︿晶娣峰悎甯冨眬锛屼竴绾ц彍鍗曞湪椤堕儴锛屼簩绾ц彍鍗曞湪宸︿晶娣辫壊鍖哄煙锛屼笁绾ц彍鍗曞湪宸︿晶娴呰壊鍖哄煙銆�',
+ horizontal_detail: '椤堕儴鑿滃崟甯冨眬锛岃彍鍗曞湪椤堕儴锛屽唴瀹瑰湪涓嬫柟銆�',
+ 'top-hybrid-sidebar-first_detail': '椤堕儴娣峰悎甯冨眬锛屼竴绾ц彍鍗曞湪宸︿晶锛屼簩绾ц彍鍗曞湪椤堕儴銆�',
+ 'top-hybrid-header-first_detail': '椤堕儴娣峰悎甯冨眬锛屼竴绾ц彍鍗曞湪椤堕儴锛屼簩绾ц彍鍗曞湪宸︿晶銆�'
+ },
+ tab: {
+ title: '鏍囩鏍忚缃�',
+ visible: '鏄剧ず鏍囩鏍�',
+ cache: '鏍囩鏍忎俊鎭紦瀛�',
+ cacheTip: '涓�閿紑鍚�/鍏抽棴鍏ㄥ眬 keepalive',
+ height: '鏍囩鏍忛珮搴�',
+ mode: {
+ title: '鏍囩鏍忛鏍�',
+ slider: '婊戝潡椋庢牸',
+ chrome: '璋锋瓕椋庢牸',
+ button: '鎸夐挳椋庢牸'
+ },
+ closeByMiddleClick: '榧犳爣涓敭鍏抽棴鏍囩椤�',
+ closeByMiddleClickTip: '鍚敤鍚庡彲浠ヤ娇鐢ㄩ紶鏍囦腑閿偣鍑绘爣绛鹃〉杩涜鍏抽棴'
+ },
+ header: {
+ title: '澶撮儴璁剧疆',
+ height: '澶撮儴楂樺害',
+ breadcrumb: {
+ visible: '鏄剧ず闈㈠寘灞�',
+ showIcon: '鏄剧ず闈㈠寘灞戝浘鏍�'
+ }
+ },
+ sider: {
+ title: '渚ц竟鏍忚缃�',
+ inverted: '娣辫壊渚ц竟鏍�',
+ width: '渚ц竟鏍忓搴�',
+ collapsedWidth: '渚ц竟鏍忔姌鍙犲搴�',
+ mixWidth: '娣峰悎甯冨眬渚ц竟鏍忓搴�',
+ mixCollapsedWidth: '娣峰悎甯冨眬渚ц竟鏍忔姌鍙犲搴�',
+ mixChildMenuWidth: '娣峰悎甯冨眬瀛愯彍鍗曞搴�',
+ autoSelectFirstMenu: '鑷姩閫夋嫨绗竴涓瓙鑿滃崟',
+ autoSelectFirstMenuTip: '鐐瑰嚮涓�绾ц彍鍗曟椂锛岃嚜鍔ㄩ�夋嫨骞跺鑸埌绗竴涓瓙鑿滃崟鐨勬渶娣卞眰绾�'
+ },
+ footer: {
+ title: '搴曢儴璁剧疆',
+ visible: '鏄剧ず搴曢儴',
+ fixed: '鍥哄畾搴曢儴',
+ height: '搴曢儴楂樺害',
+ right: '搴曢儴灞呭彸'
+ },
+ content: {
+ title: '鍐呭鍖哄煙璁剧疆',
+ scrollMode: {
+ title: '婊氬姩妯″紡',
+ tip: '涓婚婊氬姩浠� main 閮ㄥ垎婊氬姩锛屽灞傛粴鍔ㄥ彲鎼哄甫澶撮儴搴曢儴涓�璧锋粴鍔�',
+ wrapper: '澶栧眰婊氬姩',
+ content: '涓讳綋婊氬姩'
+ },
+ page: {
+ animate: '椤甸潰鍒囨崲鍔ㄧ敾',
+ mode: {
+ title: '椤甸潰鍒囨崲鍔ㄧ敾绫诲瀷',
+ 'fade-slide': '婊戝姩',
+ fade: '娣″叆娣″嚭',
+ 'fade-bottom': '搴曢儴娑堥��',
+ 'fade-scale': '缂╂斁娑堥��',
+ 'zoom-fade': '娓愬彉',
+ 'zoom-out': '闂幇',
+ none: '鏃�'
+ }
+ },
+ fixedHeaderAndTab: '鍥哄畾澶撮儴鍜屾爣绛炬爮'
+ }
+ },
+ general: {
+ title: '閫氱敤璁剧疆',
+ watermark: {
+ title: '姘村嵃璁剧疆',
+ visible: '鏄剧ず鍏ㄥ睆姘村嵃',
+ text: '鑷畾涔夋按鍗版枃鏈�',
+ enableUserName: '鍚敤鐢ㄦ埛鍚嶆按鍗�',
+ enableTime: '鏄剧ず褰撳墠鏃堕棿',
+ timeFormat: '鏃堕棿鏍煎紡'
+ },
+ multilingual: {
+ title: '澶氳瑷�璁剧疆',
+ visible: '鏄剧ず澶氳瑷�鎸夐挳'
+ },
+ globalSearch: {
+ title: '鍏ㄥ眬鎼滅储璁剧疆',
+ visible: '鏄剧ず鍏ㄥ眬鎼滅储鎸夐挳'
+ }
+ },
+ configOperation: {
+ copyConfig: '澶嶅埗閰嶇疆',
+ copySuccessMsg: '澶嶅埗鎴愬姛锛岃鏇挎崲 src/theme/settings.ts 涓殑鍙橀噺 themeSettings',
+ resetConfig: '閲嶇疆閰嶇疆',
+ resetSuccessMsg: '閲嶇疆鎴愬姛'
+ },
+ tablePropsTitle: '琛ㄦ牸閰嶇疆',
+ table: {
+ size: {
+ title: '琛ㄦ牸澶у皬',
+ small: '灏�',
+ medium: '涓�',
+ large: '澶�'
+ },
+ bordered: '杈规',
+ bottomBordered: '搴曢儴杈规',
+ singleColumn: '璁惧畾琛岀殑鍒嗗壊绾�',
+ singleLine: '璁惧畾鍒楃殑鍒嗗壊绾�',
+ striped: '鏂戦┈绾挎潯绾�'
+ }
+ },
+ route: {
+ login: '鐧诲綍',
+ 403: '鏃犳潈闄�',
+ 404: '椤甸潰涓嶅瓨鍦�',
+ 500: '鏈嶅姟鍣ㄩ敊璇�',
+ 'iframe-page': '澶栭摼椤甸潰',
+ home: '棣栭〉',
+ system: '绯荤粺绠$悊',
+ system_user: '鐢ㄦ埛绠$悊',
+ system_role: '瑙掕壊绠$悊',
+ system_menu: '鑿滃崟绠$悊',
+ system_dept: '閮ㄩ棬绠$悊',
+ system_post: '宀椾綅绠$悊',
+ system_dict: '瀛楀吀绠$悊',
+ system_config: '鍙傛暟璁剧疆',
+ system_notice: '閫氱煡鍏憡',
+ system_oss: '鏂囦欢绠$悊',
+ 'system_oss-config': 'OSS 閰嶇疆',
+ system_client: '瀹㈡埛绔鐞�',
+ system_tenant: '绉熸埛绠$悊',
+ 'system_tenant-package': '绉熸埛濂楅',
+ monitor: '绯荤粺鐩戞帶',
+ monitor_cache: '缂撳瓨鐩戞帶',
+ monitor_logininfor: '鐧诲綍鏃ュ織',
+ monitor_operlog: '鎿嶄綔鏃ュ織',
+ monitor_online: '鍦ㄧ嚎鐢ㄦ埛',
+ 'social-callback': '鍗曠偣鐧诲綍鍥炶皟',
+ 'user-center': '涓汉涓績',
+ demo: '娴嬭瘯',
+ demo_demo: '娴嬭瘯鍗曡〃',
+ demo_tree: '娴嬭瘯鏍戣〃',
+ exception: '寮傚父椤�',
+ exception_403: '403',
+ exception_404: '404',
+ exception_500: '500',
+ tool: '绯荤粺宸ュ叿',
+ tool_gen: '浠g爜鐢熸垚',
+ about: '鍏充簬'
+ },
+ menu: {
+ system_tenant: '绉熸埛绠$悊',
+ system_log: '鏃ュ織绠$悊',
+ 'monitor_snail-job': '浠诲姟璋冨害涓績',
+ monitor_admin: 'Admin 鐩戞帶'
+ },
+ dict: {
+ sys_user_sex: {
+ male: '鐢�',
+ female: '濂�',
+ unknown: '鏈煡'
+ },
+ sys_show_hide: {
+ show: '鏄剧ず',
+ hide: '闅愯棌'
+ },
+ sys_normal_disable: {
+ normal: '姝e父',
+ disable: '鍋滅敤'
+ },
+ sys_yes_no: {
+ yes: '鏄�',
+ no: '鍚�'
+ },
+ sys_notice_type: {
+ notice: '閫氱煡',
+ announcement: '鍏憡'
+ },
+ sys_notice_status: {
+ normal: '姝e父',
+ close: '鍏抽棴'
+ },
+ sys_oper_type: {
+ insert: '鏂板',
+ update: '淇敼',
+ delete: '鍒犻櫎',
+ grant: '鎺堟潈',
+ export: '瀵煎嚭',
+ import: '瀵煎叆',
+ force: '寮洪��',
+ gencode: '鐢熸垚浠g爜',
+ clean: '娓呯┖鏁版嵁',
+ other: '鍏朵粬'
+ },
+ sys_common_status: {
+ success: '鎴愬姛',
+ fail: '澶辫触'
+ },
+ sys_grant_type: {
+ password: '瀵嗙爜璁よ瘉',
+ sms: '鐭俊璁よ瘉',
+ email: '閭欢璁よ瘉',
+ miniapp: '灏忕▼搴忚璇�',
+ social: '涓夋柟鐧诲綍璁よ瘉'
+ },
+ sys_device_type: {
+ pc: 'PC',
+ android: '瀹夊崜',
+ ios: 'iOS',
+ miniapp: '灏忕▼搴�'
+ },
+ wf_business_status: {
+ revoked: '宸叉挙閿�',
+ draft: '鑽夌',
+ pending: '寰呭鏍�',
+ completed: '宸插畬鎴�',
+ cancelled: '宸蹭綔搴�',
+ returned: '宸查��鍥�',
+ terminated: '宸茬粓姝�'
+ },
+ wf_form_type: {
+ custom_form: '鑷畾涔夎〃鍗�',
+ dynamic_form: '鍔ㄦ�佽〃鍗�'
+ },
+ wf_task_status: {
+ revoke: '鎾ら攢',
+ pass: '閫氳繃',
+ pending_review: '寰呭鏍�',
+ cancel: '浣滃簾',
+ return: '閫�鍥�',
+ terminate: '缁堟',
+ transfer: '杞姙',
+ delegate: '濮旀墭',
+ copy: '鎶勯��',
+ add_sign: '鍔犵',
+ minus_sign: '鍑忕',
+ timeout: '瓒呮椂'
+ }
+ },
+ page: {
+ login: {
+ common: {
+ title: '鏅鸿兘鍖栫儫鍘傜患鍚堢鐞嗗钩鍙�',
+ subTitle: '涓虹儫鑽変紒涓氭彁渚涘叏鏂逛綅鐨勭敓浜т笌绠$悊瑙e喅鏂规',
+ loginOrRegister: '鐧诲綍 / 娉ㄥ唽',
+ register: '娉ㄥ唽',
+ userNamePlaceholder: '璇疯緭鍏ョ敤鎴峰悕',
+ phonePlaceholder: '璇疯緭鍏ユ墜鏈哄彿',
+ codePlaceholder: '璇疯緭鍏ラ獙璇佺爜',
+ passwordPlaceholder: '璇疯緭鍏ュ瘑鐮�',
+ confirmPasswordPlaceholder: '璇峰啀娆¤緭鍏ュ瘑鐮�',
+ codeLogin: '楠岃瘉鐮佺櫥褰�',
+ confirm: '纭畾',
+ back: '杩斿洖',
+ validateSuccess: '楠岃瘉鎴愬姛',
+ loginSuccess: '鐧诲綍鎴愬姛',
+ welcomeBack: '娆㈣繋鍥炴潵锛寋userName} 锛�'
+ },
+ pwdLogin: {
+ title: '瀵嗙爜鐧诲綍',
+ rememberMe: '璁颁綇瀵嗙爜',
+ forgetPassword: '蹇樿瀵嗙爜锛�',
+ register: '娉ㄥ唽璐﹀彿',
+ otherAccountLogin: '鍏朵粬璐﹀彿鐧诲綍',
+ otherLoginMode: '鍏朵粬鐧诲綍鏂瑰紡',
+ superAdmin: '瓒呯骇绠$悊鍛�',
+ admin: '绠$悊鍛�',
+ user: '鏅�氱敤鎴�'
+ },
+ codeLogin: {
+ title: '楠岃瘉鐮佺櫥褰�',
+ getCode: '鑾峰彇楠岃瘉鐮�',
+ reGetCode: '{time}绉掑悗閲嶆柊鑾峰彇',
+ sendCodeSuccess: '楠岃瘉鐮佸彂閫佹垚鍔�',
+ imageCodePlaceholder: '璇疯緭鍏ュ浘鐗囬獙璇佺爜'
+ },
+ register: {
+ title: '娉ㄥ唽璐﹀彿',
+ agreement: '鎴戝凡缁忎粩缁嗛槄璇诲苟鎺ュ彈',
+ protocol: '銆婄敤鎴峰崗璁��',
+ policy: '銆婇殣绉佹潈鏀跨瓥銆�'
+ },
+ resetPwd: {
+ title: '閲嶇疆瀵嗙爜'
+ },
+ bindWeChat: {
+ title: '缁戝畾寰俊'
+ }
+ },
+ home: {
+ branchDesc:
+ '涓轰簡鏂逛究澶у寮�鍙戝拰鏇存柊鍚堝苟锛屾垜浠main鍒嗘敮鐨勪唬鐮佽繘琛屼簡绮剧畝锛屽彧淇濈暀浜嗛椤佃彍鍗曪紝鍏朵綑鍐呭宸茬Щ鑷砮xample鍒嗘敮杩涜缁存姢銆傞瑙堝湴鍧�鏄剧ず鐨勫唴瀹瑰嵆涓篹xample鍒嗘敮鐨勫唴瀹广��',
+ greeting: '鏃╁畨锛寋userName}, 浠婂ぉ鍙堟槸鍏呮弧娲诲姏鐨勪竴澶�!',
+ weatherDesc: '浠婃棩澶氫簯杞櫞锛�20鈩� - 25鈩�!',
+ projectCount: '椤圭洰鏁�',
+ todo: '寰呭姙',
+ message: '娑堟伅',
+ downloadCount: '涓嬭浇閲�',
+ registerCount: '娉ㄥ唽閲�',
+ schedule: '浣滄伅瀹夋帓',
+ study: '瀛︿範',
+ work: '宸ヤ綔',
+ rest: '浼戞伅',
+ entertainment: '濞变箰',
+ visitCount: '璁块棶閲�',
+ turnover: '鎴愪氦棰�',
+ dealCount: '鎴愪氦閲�',
+ projectNews: {
+ title: '椤圭洰鍔ㄦ��',
+ moreNews: '鏇村鍔ㄦ��',
+ desc1: 'Soybean 鍦�2021骞�5鏈�28鏃ュ垱寤轰簡寮�婧愰」鐩� soybean-admin!',
+ desc2: 'Yanbowe 鍚� soybean-admin 鎻愪氦浜嗕竴涓猙ug锛屽鏍囩鏍忎笉浼氳嚜閫傚簲銆�',
+ desc3: 'Soybean 鍑嗗涓� soybean-admin 鐨勫彂甯冨仛鍏呭垎鐨勫噯澶囧伐浣�!',
+ desc4: 'Soybean 姝e湪蹇欎簬涓簊oybean-admin鍐欓」鐩鏄庢枃妗o紒',
+ desc5: 'Soybean 鍒氭墠鎶婂伐浣滃彴椤甸潰闅忎究鍐欎簡涓�浜涳紝鍑戝悎鑳界湅浜嗭紒'
+ },
+ creativity: '鍒涙剰'
+ },
+ common: {
+ id: 'ID',
+ createBy: '鍒涘缓鑰�',
+ createTime: '鍒涘缓鏃堕棿',
+ updateBy: '鏇存柊鑰�',
+ updateTime: '鏇存柊鏃堕棿',
+ remark: '澶囨敞',
+ form: {
+ remark: {
+ required: '璇疯緭鍏ュ娉�',
+ invalid: '澶囨敞涓嶈兘涓虹┖'
+ }
+ }
+ },
+ system: {
+ client: {
+ title: '瀹㈡埛绔垪琛�',
+ clientId: '瀹㈡埛绔� ID',
+ clientKey: '瀹㈡埛绔� Key',
+ clientSecret: '瀹㈡埛绔閽�',
+ grantTypeList: '鎺堟潈绫诲瀷',
+ deviceType: '璁惧绫诲瀷',
+ activeTimeout: 'Token 娲昏穬瓒呮椂鏃堕棿',
+ timeout: 'Token 鍥哄畾瓒呮椂',
+ status: '鐘舵��',
+ form: {
+ clientId: {
+ required: '璇疯緭鍏ュ鎴风 ID',
+ invalid: '瀹㈡埛绔� ID 涓嶈兘涓虹┖'
+ },
+ clientKey: {
+ required: '璇疯緭鍏ュ鎴风 Key',
+ invalid: '瀹㈡埛绔� Key 涓嶈兘涓虹┖'
+ },
+ clientSecret: {
+ required: '璇疯緭鍏ュ鎴风绉橀挜',
+ invalid: '瀹㈡埛绔閽ヤ笉鑳戒负绌�'
+ },
+ grantTypeList: {
+ required: '璇烽�夋嫨鎺堟潈绫诲瀷',
+ invalid: '鎺堟潈绫诲瀷涓嶈兘涓虹┖'
+ },
+ deviceType: {
+ required: '璇烽�夋嫨璁惧绫诲瀷',
+ invalid: '璁惧绫诲瀷涓嶈兘涓虹┖'
+ },
+ activeTimeout: {
+ required: '璇疯緭鍏� Token 娲昏穬瓒呮椂鏃堕棿',
+ invalid: 'Token 娲昏穬瓒呮椂鏃堕棿涓嶈兘涓虹┖',
+ tooltip: '鎸囧畾鏃堕棿鏃犳搷浣滃垯杩囨湡(鍗曚綅锛氱), 榛樿30鍒嗛挓(1800绉�)'
+ },
+ timeout: {
+ required: '璇疯緭鍏� Token 鍥哄畾瓒呮椂',
+ invalid: 'Token 鍥哄畾瓒呮椂涓嶈兘涓虹┖',
+ tooltip: '鎸囧畾鏃堕棿蹇呭畾杩囨湡(鍗曚綅锛氱)锛岄粯璁や竷澶�(604800绉�)'
+ },
+ status: {
+ required: '璇烽�夋嫨鐘舵��',
+ invalid: '鐘舵�佷笉鑳戒负绌�'
+ }
+ },
+ addClient: '鏂板瀹㈡埛绔�',
+ editClient: '缂栬緫瀹㈡埛绔�'
+ },
+ config: {
+ title: '鍙傛暟閰嶇疆鍒楄〃',
+ configName: '鍙傛暟鍚嶇О',
+ configKey: '鍙傛暟閿悕',
+ configValue: '鍙傛暟閿��',
+ configType: '鏄惁鍐呯疆',
+ remark: '澶囨敞',
+ createTime: '鍒涘缓鏃堕棿',
+ refreshCache: '鍒锋柊缂撳瓨',
+ refreshCacheSuccess: '鍒锋柊缂撳瓨鎴愬姛',
+ form: {
+ configId: {
+ required: '璇疯緭鍏ュ弬鏁颁富閿�',
+ invalid: '鍙傛暟涓婚敭涓嶈兘涓虹┖'
+ },
+ configName: {
+ required: '璇疯緭鍏ュ弬鏁板悕绉�',
+ invalid: '鍙傛暟鍚嶇О涓嶈兘涓虹┖'
+ },
+ configKey: {
+ required: '璇疯緭鍏ュ弬鏁伴敭鍚�',
+ invalid: '鍙傛暟閿悕涓嶈兘涓虹┖'
+ },
+ configValue: {
+ required: '璇疯緭鍏ュ弬鏁伴敭鍊�',
+ invalid: '鍙傛暟閿�间笉鑳戒负绌�'
+ },
+ configType: {
+ required: '璇烽�夋嫨鏄惁鍐呯疆',
+ invalid: '鏄惁鍐呯疆涓嶈兘涓虹┖'
+ },
+ remark: {
+ required: '璇疯緭鍏ュ娉�',
+ invalid: '澶囨敞涓嶈兘涓虹┖'
+ }
+ },
+ addConfig: '鏂板鍙傛暟閰嶇疆',
+ editConfig: '缂栬緫鍙傛暟閰嶇疆'
+ },
+ dept: {
+ empty: '鏆傛棤閮ㄩ棬淇℃伅',
+ title: '閮ㄩ棬鍒楄〃',
+ parentId: '涓婄骇閮ㄩ棬',
+ deptName: '閮ㄩ棬鍚嶇О',
+ orderNum: '鎺掑簭',
+ deptCategory: '绫诲埆缂栫爜',
+ leader: '璐熻矗浜�',
+ phone: '鑱旂郴鐢佃瘽',
+ email: '閭',
+ status: '鐘舵��',
+ sort: '鎺掑簭',
+ createTime: '鍒涘缓鏃堕棿',
+ expandAll: '鍏ㄩ儴灞曞紑',
+ collapseAll: '鍏ㄩ儴鏀惰捣',
+ form: {
+ parentId: {
+ required: '璇烽�夋嫨涓婄骇閮ㄩ棬',
+ invalid: '涓婄骇閮ㄩ棬涓嶈兘涓虹┖'
+ },
+ deptName: {
+ required: '璇疯緭鍏ラ儴闂ㄥ悕绉�',
+ invalid: '閮ㄩ棬鍚嶇О涓嶈兘涓虹┖'
+ },
+ orderNum: {
+ required: '璇疯緭鍏ユ帓搴�',
+ invalid: '鎺掑簭涓嶈兘涓虹┖'
+ },
+ deptCategory: {
+ required: '璇疯緭鍏ョ被鍒紪鐮�',
+ invalid: '绫诲埆缂栫爜涓嶈兘涓虹┖'
+ },
+ leader: {
+ required: '璇疯緭鍏ヨ礋璐d汉',
+ invalid: '璐熻矗浜轰笉鑳戒负绌�'
+ },
+ phone: {
+ required: '璇疯緭鍏ヨ仈绯荤數璇�',
+ invalid: '鑱旂郴鐢佃瘽涓嶈兘涓虹┖'
+ },
+ email: {
+ required: '璇疯緭鍏ラ偖绠�',
+ invalid: '閭涓嶈兘涓虹┖'
+ },
+ status: {
+ required: '璇烽�夋嫨鐘舵��',
+ invalid: '鐘舵�佷笉鑳戒负绌�'
+ },
+ sort: {
+ required: '璇疯緭鍏ユ帓搴�',
+ invalid: '鎺掑簭涓嶈兘涓虹┖'
+ },
+ deptId: {
+ required: '璇疯緭鍏ラ儴闂╥d',
+ invalid: '閮ㄩ棬id涓嶈兘涓虹┖'
+ }
+ },
+ error: {
+ getDeptDataFail: '鑾峰彇閮ㄩ棬鐢ㄦ埛鏁版嵁澶辫触',
+ getDeptUserDataFail: '鑾峰彇閮ㄩ棬鐢ㄦ埛鏁版嵁澶辫触'
+ },
+ placeholder: {
+ defaultLeaderPlaceHolder: '璇烽�夋嫨璐熻矗浜�',
+ addDataLeaderPlaceHolder: '浠呭湪鏇存柊鏃跺彲閫夋嫨閮ㄩ棬璐熻矗浜�',
+ deptUserIsEmptyLeaderPlaceHolder: '璇ラ儴闂ㄦ病鏈夎礋璐d汉'
+ },
+ addDept: '鏂板閮ㄩ棬',
+ editDept: '缂栬緫閮ㄩ棬'
+ },
+ dict: {
+ title: '瀛楀吀鍒楄〃',
+ dictTypeTitle: '瀛楀吀绫诲瀷鍒楄〃',
+ dictName: '瀛楀吀鍚嶇О',
+ dictType: '瀛楀吀绫诲瀷',
+ status: '鐘舵��',
+ remark: '澶囨敞',
+ createTime: '鍒涘缓鏃堕棿',
+ refreshCacheSuccess: '鍒锋柊缂撳瓨鎴愬姛',
+ refreshCache: '鍒锋柊缂撳瓨',
+ confirmDeleteDictType: '纭畾鍒犻櫎瀛楀吀绫诲瀷',
+ data: {
+ title: '瀛楀吀鏁版嵁鍒楄〃',
+ label: '瀛楀吀鏍囩',
+ value: '瀛楀吀閿��',
+ dictSort: '瀛楀吀鎺掑簭',
+ isDefault: '鏄惁榛樿',
+ listClass: '鏍囩鏍峰紡',
+ cssClass: 'CSS鏍峰紡',
+ status: '鐘舵��',
+ remark: '澶囨敞',
+ createTime: '鍒涘缓鏃堕棿'
+ },
+ form: {
+ dictId: {
+ required: '璇疯緭鍏ュ瓧鍏镐富閿�',
+ invalid: '瀛楀吀涓婚敭涓嶈兘涓虹┖'
+ },
+ dictCode: {
+ required: '璇疯緭鍏ュ瓧鍏哥紪鐮�',
+ invalid: '瀛楀吀缂栫爜涓嶈兘涓虹┖'
+ },
+ dictName: {
+ required: '璇疯緭鍏ュ瓧鍏稿悕绉�',
+ invalid: '瀛楀吀鍚嶇О涓嶈兘涓虹┖'
+ },
+ dictType: {
+ required: '璇疯緭鍏ュ瓧鍏哥被鍨�',
+ invalid: '瀛楀吀绫诲瀷涓嶈兘涓虹┖'
+ },
+ status: {
+ required: '璇烽�夋嫨鐘舵��',
+ invalid: '鐘舵�佷笉鑳戒负绌�'
+ },
+ remark: {
+ required: '璇疯緭鍏ュ娉�',
+ invalid: '澶囨敞涓嶈兘涓虹┖'
+ },
+ dictLabel: {
+ required: '璇疯緭鍏ュ瓧鍏告爣绛�',
+ invalid: '瀛楀吀鏍囩涓嶈兘涓虹┖'
+ },
+ dictValue: {
+ required: '璇疯緭鍏ュ瓧鍏搁敭鍊�',
+ invalid: '瀛楀吀閿�间笉鑳戒负绌�'
+ },
+ dictSort: {
+ required: '璇疯緭鍏ュ瓧鍏告帓搴�',
+ invalid: '瀛楀吀鎺掑簭涓嶈兘涓虹┖'
+ },
+ isDefault: {
+ required: '璇烽�夋嫨鏄惁榛樿',
+ invalid: '鏄惁榛樿涓嶈兘涓虹┖'
+ },
+ listClass: {
+ required: '璇烽�夋嫨鍥炴樉鏍峰紡',
+ invalid: '鍥炴樉鏍峰紡涓嶈兘涓虹┖'
+ },
+ cssClass: {
+ required: '璇疯緭鍏ユ牱寮忓睘鎬э紙鍏朵粬鏍峰紡鎵╁睍锛�',
+ invalid: 'CSS鏍峰紡涓嶈兘涓虹┖'
+ }
+ },
+ addDict: '鏂板瀛楀吀',
+ editDict: '缂栬緫瀛楀吀',
+ addDictData: '鏂板瀛楀吀鏁版嵁',
+ editDictData: '缂栬緫瀛楀吀鏁版嵁',
+ addDictType: '鏂板瀛楀吀绫诲瀷',
+ editDictType: '缂栬緫瀛楀吀绫诲瀷',
+ exportDictType: '瀵煎嚭瀛楀吀绫诲瀷',
+ refreshDictType: '鍒锋柊鍒楄〃',
+ dictTypeIsEmpty: '鏆傛棤瀛楀吀绫诲瀷'
+ },
+ menu: {
+ title: '鑿滃崟鍒楄〃',
+ parentId: '涓婄骇鑿滃崟',
+ iconType: '鍥炬爣绫诲瀷',
+ menuName: '鑿滃崟鍚嶇О',
+ icon: '鑿滃崟鍥炬爣',
+ orderNum: '鎺掑簭',
+ perms: '鏉冮檺瀛楃',
+ component: '缁勪欢璺緞',
+ path: '璺敱鍦板潃',
+ layout: '甯冨眬鏂瑰紡',
+ externalPath: '澶栭摼鍦板潃',
+ query: '璺敱鍙傛暟',
+ iframeQuery: 'iframe 鍦板潃',
+ isFrame: '鏄惁澶栭摼',
+ isCache: '鏄惁缂撳瓨',
+ menuType: '鑿滃崟绫诲瀷',
+ visible: '鏄剧ず鐘舵��',
+ status: '鑿滃崟鐘舵��',
+ createTime: '鍒涘缓鏃堕棿',
+ cache: '缂撳瓨',
+ noCache: '涓嶇紦瀛�',
+ rootName: '鏍圭洰褰�',
+ buttonPermissionList: '鎸夐挳鏉冮檺鍒楄〃',
+ emptyMenu: '鏆傛棤鑿滃崟',
+ menuDetail: '鑿滃崟璇︽儏',
+ cascadeDeleteContent: '绾ц仈鍒犻櫎鑿滃崟灏嗗垹闄ゆ墍閫変腑鐨勮彍鍗曪紝鏄惁缁х画锛�',
+ iconifyTip: 'iconify 鍦板潃锛歨ttps://icones.js.org',
+ isFrameTip: '閫夋嫨鏄閾惧垯璺敱鍦板潃闇�瑕佷互`http(s)://`寮�澶�',
+ isCacheTip: '閫夋嫨鏄垯浼氳`keep-alive`缂撳瓨锛岄渶瑕佸尮閰嶇粍浠剁殑`name`鍜屽湴鍧�淇濇寔涓�鑷�',
+ visibleTip: '閫夋嫨闅愯棌鍒欒矾鐢卞皢涓嶄細鍑虹幇鍦ㄤ晶杈规爮锛屼絾浠嶇劧鍙互璁块棶',
+ statusTip: '閫夋嫨鍋滅敤鍒欒矾鐢卞皢涓嶄細鍑虹幇鍦ㄤ晶杈规爮锛屼篃涓嶈兘琚闂�',
+ permsTip: "鎺у埗鍣ㄤ腑瀹氫箟鐨勬潈闄愬瓧绗︼紝濡傦細`{'@'}SaCheckPermission('system:user:list')`",
+ componentTip: '璁块棶鐨勭粍浠惰矾寰勶紝濡傦細`system/user/index`锛岄粯璁ゅ湪`views`鐩綍涓�',
+ pathTip: '璁块棶鐨勮矾鐢卞湴鍧�锛屽锛歚user`锛屽澶栫綉鍦板潃闇�鍐呴摼璁块棶鍒欎互`http(s)://`寮�澶�',
+ layoutTip: '榛樿甯冨眬锛氬叿鏈夊叕鍏遍儴鍒嗙殑甯冨眬锛屽鍏ㄥ眬澶撮儴銆佷晶杈规爮銆佸簳閮ㄧ瓑\n绌虹櫧甯冨眬锛氭棤鍏叡閮ㄥ垎鐨勫竷灞�锛屽鐧诲綍椤�',
+ form: {
+ parentId: {
+ required: '璇烽�夋嫨涓婄骇鑿滃崟',
+ invalid: '涓婄骇鑿滃崟涓嶈兘涓虹┖'
+ },
+ menuType: {
+ required: '璇烽�夋嫨鑿滃崟绫诲瀷',
+ invalid: '鑿滃崟绫诲瀷涓嶈兘涓虹┖'
+ },
+ menuIds: {
+ required: '璇烽�夋嫨鑿滃崟',
+ invalid: '鑿滃崟涓嶈兘涓虹┖'
+ },
+ icon: {
+ required: '璇烽�夋嫨鑿滃崟鍥炬爣',
+ invalid: '鑿滃崟鍥炬爣涓嶈兘涓虹┖'
+ },
+ menuName: {
+ required: '璇疯緭鍏ヨ彍鍗曞悕绉�',
+ invalid: '鑿滃崟鍚嶇О涓嶈兘涓虹┖'
+ },
+ orderNum: {
+ required: '璇疯緭鍏ユ帓搴�',
+ invalid: '鎺掑簭涓嶈兘涓虹┖'
+ },
+ perms: {
+ required: '璇疯緭鍏ユ潈闄愬瓧绗�',
+ invalid: '鏉冮檺瀛楃涓嶈兘涓虹┖'
+ },
+ isFrame: {
+ required: '璇烽�夋嫨鏄惁澶栭摼',
+ invalid: '鏄惁澶栭摼涓嶈兘涓虹┖'
+ },
+ path: {
+ required: '璇疯緭鍏ヨ矾鐢卞湴鍧�',
+ invalid: '璺敱鍦板潃涓嶈兘涓虹┖'
+ },
+ component: {
+ required: '璇疯緭鍏ョ粍浠惰矾寰�',
+ invalid: '缁勪欢璺緞涓嶈兘涓虹┖'
+ },
+ query: {
+ required: '璇疯緭鍏ヨ矾鐢卞弬鏁�',
+ invalid: '璺敱鍙傛暟涓嶈兘涓虹┖'
+ },
+ isCache: {
+ required: '璇烽�夋嫨鏄惁缂撳瓨',
+ invalid: '鏄惁缂撳瓨涓嶈兘涓虹┖'
+ },
+ visible: {
+ required: '璇烽�夋嫨鏄剧ず鐘舵��',
+ invalid: '鏄剧ず鐘舵�佷笉鑳戒负绌�'
+ },
+ status: {
+ required: '璇烽�夋嫨鑿滃崟鐘舵��',
+ invalid: '鑿滃崟鐘舵�佷笉鑳戒负绌�'
+ },
+ permission: {
+ required: '璇疯緭鍏ユ潈闄愭爣璇�',
+ invalid: '鏉冮檺鏍囪瘑涓嶈兘涓虹┖'
+ }
+ },
+ placeholder: {
+ iconifyIconPlaceholder: '璇疯緭鍏ュ浘鏍�',
+ localIconPlaceholder: '璇烽�夋嫨鏈湴鍥炬爣',
+ queryKey: '璇疯緭鍏� Key',
+ queryValue: '璇疯緭鍏� Value',
+ queryIframe: '璇疯緭鍏� iframe 鍦板潃'
+ },
+ directory: '鐩綍',
+ menu: '鑿滃崟',
+ button: '鎸夐挳',
+ addMenu: '鏂板鑿滃崟',
+ addChildMenu: '鏂板瀛愯彍鍗�',
+ editMenu: '缂栬緫鑿滃崟',
+ cascadeDelete: '绾ц仈鍒犻櫎鑿滃崟'
+ },
+ notice: {
+ title: '閫氱煡鍏憡鍒楄〃',
+ noticeTitle: '鍏憡鏍囬',
+ noticeType: '鍏憡绫诲瀷',
+ noticeContent: '鍏憡鍐呭',
+ status: '鐘舵��',
+ createTime: '鍒涘缓鏃堕棿',
+ form: {
+ noticeTitle: {
+ required: '璇疯緭鍏ュ叕鍛婃爣棰�',
+ invalid: '鍏憡鏍囬涓嶈兘涓虹┖'
+ },
+ noticeType: {
+ required: '璇烽�夋嫨鍏憡绫诲瀷',
+ invalid: '鍏憡绫诲瀷涓嶈兘涓虹┖'
+ },
+ noticeContent: {
+ required: '璇疯緭鍏ュ叕鍛婂唴瀹�',
+ invalid: '鍏憡鍐呭涓嶈兘涓虹┖'
+ },
+ status: {
+ required: '璇烽�夋嫨鐘舵��',
+ invalid: '鐘舵�佷笉鑳戒负绌�'
+ }
+ },
+ addNotice: '鏂板鍏憡',
+ editNotice: '缂栬緫鍏憡'
+ },
+ oss: {
+ title: '鏂囦欢鍒楄〃',
+ fileName: '鏂囦欢鍚嶇О',
+ originalName: '鍘熷鍚嶇О',
+ fileSuffix: '鏂囦欢鍚庣紑',
+ url: '鏂囦欢鍦板潃',
+ createTime: '鍒涘缓鏃堕棿',
+ service: '鏈嶅姟鍟�',
+ form: {
+ file: {
+ required: '璇烽�夋嫨鏂囦欢',
+ invalid: '鏂囦欢涓嶈兘涓虹┖'
+ }
+ },
+ upload: '涓婁紶鏂囦欢',
+ preview: '棰勮',
+ download: '涓嬭浇',
+ copy: '澶嶅埗閾炬帴',
+ copySuccess: '澶嶅埗鎴愬姛'
+ },
+ ossConfig: {
+ title: 'OSS閰嶇疆鍒楄〃',
+ configKey: '閰嶇疆閿�',
+ accessKey: 'accessKey',
+ secretKey: 'secretKey',
+ bucketName: '妗跺悕绉�',
+ prefix: '鍓嶇紑',
+ endpoint: '鍩熷悕',
+ domain: '鑷畾涔夊煙鍚�',
+ isHttps: '鏄惁https',
+ region: '鍦板煙',
+ status: '鐘舵��',
+ remark: '澶囨敞',
+ createTime: '鍒涘缓鏃堕棿',
+ form: {
+ configKey: {
+ required: '璇疯緭鍏ラ厤缃敭',
+ invalid: '閰嶇疆閿笉鑳戒负绌�'
+ },
+ accessKey: {
+ required: '璇疯緭鍏ccessKey',
+ invalid: 'accessKey涓嶈兘涓虹┖'
+ },
+ secretKey: {
+ required: '璇疯緭鍏ecretKey',
+ invalid: 'secretKey涓嶈兘涓虹┖'
+ },
+ bucketName: {
+ required: '璇疯緭鍏ユ《鍚嶇О',
+ invalid: '妗跺悕绉颁笉鑳戒负绌�'
+ },
+ prefix: {
+ required: '璇疯緭鍏ュ墠缂�',
+ invalid: '鍓嶇紑涓嶈兘涓虹┖'
+ },
+ endpoint: {
+ required: '璇疯緭鍏ュ煙鍚�',
+ invalid: '鍩熷悕涓嶈兘涓虹┖'
+ },
+ domain: {
+ required: '璇疯緭鍏ヨ嚜瀹氫箟鍩熷悕',
+ invalid: '鑷畾涔夊煙鍚嶄笉鑳戒负绌�'
+ },
+ isHttps: {
+ required: '璇烽�夋嫨鏄惁https',
+ invalid: '鏄惁https涓嶈兘涓虹┖'
+ },
+ region: {
+ required: '璇疯緭鍏ュ湴鍩�',
+ invalid: '鍦板煙涓嶈兘涓虹┖'
+ },
+ status: {
+ required: '璇烽�夋嫨鐘舵��',
+ invalid: '鐘舵�佷笉鑳戒负绌�'
+ },
+ remark: {
+ required: '璇疯緭鍏ュ娉�',
+ invalid: '澶囨敞涓嶈兘涓虹┖'
+ }
+ },
+ addOssConfig: '鏂板OSS閰嶇疆',
+ editOssConfig: '缂栬緫OSS閰嶇疆'
+ },
+ post: {
+ title: '宀椾綅鍒楄〃',
+ postCode: '宀椾綅缂栫爜',
+ postName: '宀椾綅鍚嶇О',
+ postSort: '宀椾綅鎺掑簭',
+ status: '鐘舵��',
+ remark: '澶囨敞',
+ createTime: '鍒涘缓鏃堕棿',
+ form: {
+ postCode: {
+ required: '璇疯緭鍏ュ矖浣嶇紪鐮�',
+ invalid: '宀椾綅缂栫爜涓嶈兘涓虹┖'
+ },
+ postName: {
+ required: '璇疯緭鍏ュ矖浣嶅悕绉�',
+ invalid: '宀椾綅鍚嶇О涓嶈兘涓虹┖'
+ },
+ postSort: {
+ required: '璇疯緭鍏ュ矖浣嶆帓搴�',
+ invalid: '宀椾綅鎺掑簭涓嶈兘涓虹┖'
+ },
+ status: {
+ required: '璇烽�夋嫨鐘舵��',
+ invalid: '鐘舵�佷笉鑳戒负绌�'
+ },
+ remark: {
+ required: '璇疯緭鍏ュ娉�',
+ invalid: '澶囨敞涓嶈兘涓虹┖'
+ }
+ },
+ addPost: '鏂板宀椾綅',
+ editPost: '缂栬緫宀椾綅'
+ },
+ role: {
+ title: '瑙掕壊鍒楄〃',
+ roleName: '瑙掕壊鍚嶇О',
+ roleKey: '鏉冮檺瀛楃',
+ roleSort: '瑙掕壊鎺掑簭',
+ status: '鐘舵��',
+ remark: '澶囨敞',
+ menuPermission: '鑿滃崟鏉冮檺',
+ dataScope: '鏁版嵁鏉冮檺',
+ createTime: '鍒涘缓鏃堕棿',
+ form: {
+ roleName: {
+ required: '璇疯緭鍏ヨ鑹插悕绉�',
+ invalid: '瑙掕壊鍚嶇О涓嶈兘涓虹┖'
+ },
+ roleKey: {
+ required: '璇疯緭鍏ユ潈闄愬瓧绗�',
+ invalid: '鏉冮檺瀛楃涓嶈兘涓虹┖'
+ },
+ roleSort: {
+ required: '璇疯緭鍏ヨ鑹叉帓搴�',
+ invalid: '瑙掕壊鎺掑簭涓嶈兘涓虹┖'
+ },
+ status: {
+ required: '璇烽�夋嫨鐘舵��',
+ invalid: '鐘舵�佷笉鑳戒负绌�'
+ },
+ remark: {
+ required: '璇疯緭鍏ュ娉�',
+ invalid: '澶囨敞涓嶈兘涓虹┖'
+ },
+ menuIds: {
+ required: '璇烽�夋嫨鑿滃崟鏉冮檺',
+ invalid: '鑿滃崟鏉冮檺涓嶈兘涓虹┖'
+ },
+ deptIds: {
+ required: '璇烽�夋嫨閮ㄩ棬鏉冮檺',
+ invalid: '閮ㄩ棬鏉冮檺涓嶈兘涓虹┖'
+ }
+ },
+ addRole: '鏂板瑙掕壊',
+ editRole: '缂栬緫瑙掕壊',
+ configPermission: '鍒嗛厤鏉冮檺',
+ authorizedUsers: '鍒嗛厤鐢ㄦ埛',
+ selectMenuPermission: '閫夋嫨鑿滃崟鏉冮檺',
+ selectDataScope: '閫夋嫨鏁版嵁鏉冮檺',
+ selectDeptPermission: '閫夋嫨閮ㄩ棬鏉冮檺'
+ },
+ tenant: {
+ title: '绉熸埛鍒楄〃',
+ tenantName: '绉熸埛鍚嶇О',
+ tenantId: '绉熸埛缂栧彿',
+ contactUserName: '鑱旂郴浜�',
+ contactPhone: '鑱旂郴鐢佃瘽',
+ companyName: '鍏徃鍚嶇О',
+ licenseNumber: '钀ヤ笟鎵х収缂栧彿',
+ address: '鍦板潃',
+ intro: '浼佷笟绠�浠�',
+ domain: '鍩熷悕',
+ packageId: '绉熸埛濂楅',
+ expireTime: '杩囨湡鏃堕棿',
+ accountCount: '璐﹀彿鏁伴噺',
+ status: '鐘舵��',
+ createTime: '鍒涘缓鏃堕棿',
+ form: {
+ tenantName: {
+ required: '璇疯緭鍏ョ鎴峰悕绉�',
+ invalid: '绉熸埛鍚嶇О涓嶈兘涓虹┖'
+ },
+ contactUserName: {
+ required: '璇疯緭鍏ヨ仈绯讳汉',
+ invalid: '鑱旂郴浜轰笉鑳戒负绌�'
+ },
+ contactPhone: {
+ required: '璇疯緭鍏ヨ仈绯荤數璇�',
+ invalid: '鑱旂郴鐢佃瘽涓嶈兘涓虹┖'
+ },
+ companyName: {
+ required: '璇疯緭鍏ュ叕鍙稿悕绉�',
+ invalid: '鍏徃鍚嶇О涓嶈兘涓虹┖'
+ },
+ licenseNumber: {
+ required: '璇疯緭鍏ヨ惀涓氭墽鐓х紪鍙�',
+ invalid: '钀ヤ笟鎵х収缂栧彿涓嶈兘涓虹┖'
+ },
+ address: {
+ required: '璇疯緭鍏ュ湴鍧�',
+ invalid: '鍦板潃涓嶈兘涓虹┖'
+ },
+ intro: {
+ required: '璇疯緭鍏ヤ紒涓氱畝浠�',
+ invalid: '浼佷笟绠�浠嬩笉鑳戒负绌�'
+ },
+ domain: {
+ required: '璇疯緭鍏ュ煙鍚�',
+ invalid: '鍩熷悕涓嶈兘涓虹┖'
+ },
+ packageId: {
+ required: '璇烽�夋嫨绉熸埛濂楅',
+ invalid: '绉熸埛濂楅涓嶈兘涓虹┖'
+ },
+ expireTime: {
+ required: '璇烽�夋嫨杩囨湡鏃堕棿',
+ invalid: '杩囨湡鏃堕棿涓嶈兘涓虹┖'
+ },
+ accountCount: {
+ required: '璇疯緭鍏ヨ处鍙锋暟閲�',
+ invalid: '璐﹀彿鏁伴噺涓嶈兘涓虹┖'
+ },
+ status: {
+ required: '璇烽�夋嫨鐘舵��',
+ invalid: '鐘舵�佷笉鑳戒负绌�'
+ }
+ },
+ addTenant: '鏂板绉熸埛',
+ editTenant: '缂栬緫绉熸埛'
+ },
+ tenantPackage: {
+ title: '绉熸埛濂楅鍒楄〃',
+ packageName: '濂楅鍚嶇О',
+ menuIds: '鑿滃崟鏉冮檺',
+ remark: '澶囨敞',
+ status: '鐘舵��',
+ createTime: '鍒涘缓鏃堕棿',
+ form: {
+ packageName: {
+ required: '璇疯緭鍏ュ椁愬悕绉�',
+ invalid: '濂楅鍚嶇О涓嶈兘涓虹┖'
+ },
+ menuIds: {
+ required: '璇烽�夋嫨鑿滃崟鏉冮檺',
+ invalid: '鑿滃崟鏉冮檺涓嶈兘涓虹┖'
+ },
+ status: {
+ required: '璇烽�夋嫨鐘舵��',
+ invalid: '鐘舵�佷笉鑳戒负绌�'
+ },
+ remark: {
+ required: '璇疯緭鍏ュ娉�',
+ invalid: '澶囨敞涓嶈兘涓虹┖'
+ }
+ },
+ addTenantPackage: '鏂板绉熸埛濂楅',
+ editTenantPackage: '缂栬緫绉熸埛濂楅',
+ statusChangeSuccess: '鐘舵�佷慨鏀规垚鍔�'
+ },
+ user: {
+ title: '鐢ㄦ埛鍒楄〃',
+ userName: '鐢ㄦ埛鍚嶇О',
+ nickName: '鐢ㄦ埛鏄电О',
+ deptName: '閮ㄩ棬',
+ phonenumber: '鎵嬫満鍙风爜',
+ status: '鐘舵��',
+ createTime: '鍒涘缓鏃堕棿',
+ password: '瀵嗙爜',
+ confirmPassword: '纭瀵嗙爜',
+ sex: '鎬у埆',
+ roleIds: '瑙掕壊',
+ postIds: '宀椾綅',
+ email: '閭',
+ avatar: '澶村儚',
+ remark: '澶囨敞',
+ form: {
+ userName: {
+ required: '璇疯緭鍏ョ敤鎴峰悕绉�',
+ invalid: '鐢ㄦ埛鍚嶇О涓嶈兘涓虹┖'
+ },
+ nickName: {
+ required: '璇疯緭鍏ョ敤鎴锋樀绉�',
+ invalid: '鐢ㄦ埛鏄电О涓嶈兘涓虹┖'
+ },
+ deptId: {
+ required: '璇烽�夋嫨閮ㄩ棬',
+ invalid: '閮ㄩ棬涓嶈兘涓虹┖'
+ },
+ phonenumber: {
+ required: '璇疯緭鍏ユ墜鏈哄彿鐮�',
+ invalid: '鎵嬫満鍙风爜涓嶈兘涓虹┖'
+ },
+ status: {
+ required: '璇烽�夋嫨鐘舵��',
+ invalid: '鐘舵�佷笉鑳戒负绌�'
+ },
+ password: {
+ required: '璇疯緭鍏ュ瘑鐮�',
+ invalid: '瀵嗙爜涓嶈兘涓虹┖'
+ },
+ confirmPassword: {
+ required: '璇疯緭鍏ョ‘璁ゅ瘑鐮�',
+ invalid: '纭瀵嗙爜涓嶈兘涓虹┖'
+ },
+ sex: {
+ required: '璇烽�夋嫨鎬у埆',
+ invalid: '鎬у埆涓嶈兘涓虹┖'
+ },
+ roleIds: {
+ required: '璇烽�夋嫨瑙掕壊',
+ invalid: '瑙掕壊涓嶈兘涓虹┖'
+ },
+ postIds: {
+ required: '璇烽�夋嫨宀椾綅',
+ invalid: '宀椾綅涓嶈兘涓虹┖'
+ },
+ email: {
+ required: '璇疯緭鍏ラ偖绠�',
+ invalid: '閭涓嶈兘涓虹┖'
+ },
+ remark: {
+ required: '璇疯緭鍏ュ娉�',
+ invalid: '澶囨敞涓嶈兘涓虹┖'
+ }
+ },
+ addUser: '鏂板鐢ㄦ埛',
+ editUser: '缂栬緫鐢ㄦ埛',
+ resetPassword: '閲嶇疆瀵嗙爜',
+ importUsers: '瀵煎叆鐢ㄦ埛',
+ exportTemplate: '瀵煎嚭妯℃澘',
+ importSuccess: '瀵煎叆鎴愬姛',
+ statusChangeSuccess: '鐘舵�佷慨鏀规垚鍔�'
+ }
+ },
+ about: {
+ title: '鍏充簬',
+ introduction: `鏅鸿兘鍖栫儫鍘傜患鍚堢鐞嗗钩鍙癭,
+ projectInfo: {
+ title: '椤圭洰淇℃伅',
+ version: '鐗堟湰',
+ latestBuildTime: '鏈�鏂版瀯寤烘椂闂�',
+ documentLink: '鏂囨。鍦板潃',
+ previewLink: '棰勮鍦板潃',
+ repositoryLink: '浠撳簱鍦板潃'
+ },
+ prdDep: '鐢熶骇渚濊禆',
+ devDep: '寮�鍙戜緷璧�'
+ }
+ },
+ form: {
+ required: '涓嶈兘涓虹┖',
+ userName: {
+ required: '璇疯緭鍏ョ敤鎴峰悕',
+ invalid: '鐢ㄦ埛鍚嶆牸寮忎笉姝g‘'
+ },
+ phone: {
+ required: '璇疯緭鍏ユ墜鏈哄彿',
+ invalid: '鎵嬫満鍙锋牸寮忎笉姝g‘'
+ },
+ pwd: {
+ required: '璇疯緭鍏ュ瘑鐮�',
+ invalid: '瀵嗙爜鏍煎紡涓嶆纭紝6-18浣嶅瓧绗︼紝鍖呭惈瀛楁瘝銆佹暟瀛椼�佷笅鍒掔嚎'
+ },
+ confirmPwd: {
+ required: '璇疯緭鍏ョ‘璁ゅ瘑鐮�',
+ invalid: '涓ゆ杈撳叆瀵嗙爜涓嶄竴鑷�'
+ },
+ code: {
+ required: '璇疯緭鍏ラ獙璇佺爜',
+ invalid: '楠岃瘉鐮佹牸寮忎笉姝g‘'
+ },
+ email: {
+ required: '璇疯緭鍏ラ偖绠�',
+ invalid: '閭鏍煎紡涓嶆纭�'
+ }
+ },
+ dropdown: {
+ closeCurrent: '鍏抽棴',
+ closeOther: '鍏抽棴鍏跺畠',
+ closeLeft: '鍏抽棴宸︿晶',
+ closeRight: '鍏抽棴鍙充晶',
+ closeAll: '鍏抽棴鎵�鏈�',
+ pin: '鍥哄畾鏍囩',
+ unpin: '鍙栨秷鍥哄畾'
+ },
+ icon: {
+ themeConfig: '涓婚閰嶇疆',
+ themeSchema: '涓婚妯″紡',
+ lang: '鍒囨崲璇█',
+ fullscreen: '鍏ㄥ睆',
+ fullscreenExit: '閫�鍑哄叏灞�',
+ reload: '鍒锋柊椤甸潰',
+ collapse: '鎶樺彔鑿滃崟',
+ expand: '灞曞紑鑿滃崟',
+ pin: '鍥哄畾',
+ unpin: '鍙栨秷鍥哄畾'
+ },
+ datatable: {
+ itemCount: '鍏� {total} 鏉�'
+ }
+};
+
+export default local;
diff --git a/ruoyi-plus-soybean/src/locales/locale.ts b/ruoyi-plus-soybean/src/locales/locale.ts
new file mode 100755
index 0000000..dc38010
--- /dev/null
+++ b/ruoyi-plus-soybean/src/locales/locale.ts
@@ -0,0 +1,9 @@
+import zhCN from './langs/zh-cn';
+import enUS from './langs/en-us';
+
+const locales: Record<App.I18n.LangType, App.I18n.Schema> = {
+ 'zh-CN': zhCN,
+ 'en-US': enUS
+};
+
+export default locales;
diff --git a/ruoyi-plus-soybean/src/locales/naive.ts b/ruoyi-plus-soybean/src/locales/naive.ts
new file mode 100755
index 0000000..fa18776
--- /dev/null
+++ b/ruoyi-plus-soybean/src/locales/naive.ts
@@ -0,0 +1,12 @@
+import { dateEnUS, dateZhCN, enUS, zhCN } from 'naive-ui';
+import type { NDateLocale, NLocale } from 'naive-ui';
+
+export const naiveLocales: Record<App.I18n.LangType, NLocale> = {
+ 'zh-CN': zhCN,
+ 'en-US': enUS
+};
+
+export const naiveDateLocales: Record<App.I18n.LangType, NDateLocale> = {
+ 'zh-CN': dateZhCN,
+ 'en-US': dateEnUS
+};
diff --git a/ruoyi-plus-soybean/src/main.ts b/ruoyi-plus-soybean/src/main.ts
new file mode 100755
index 0000000..b97a0d9
--- /dev/null
+++ b/ruoyi-plus-soybean/src/main.ts
@@ -0,0 +1,31 @@
+import { createApp } from 'vue';
+import './plugins/assets';
+import { setupAppVersionNotification, setupDayjs, setupIconifyOffline, setupLoading, setupNProgress } from './plugins';
+import { setupStore } from './store';
+import { setupRouter } from './router';
+import { setupI18n } from './locales';
+import App from './App.vue';
+
+async function setupApp() {
+ setupLoading();
+
+ setupNProgress();
+
+ setupIconifyOffline();
+
+ setupDayjs();
+
+ const app = createApp(App);
+
+ setupStore(app);
+
+ await setupRouter(app);
+
+ setupI18n(app);
+
+ setupAppVersionNotification();
+
+ app.mount('#app');
+}
+
+setupApp();
diff --git a/ruoyi-plus-soybean/src/plugins/app.ts b/ruoyi-plus-soybean/src/plugins/app.ts
new file mode 100755
index 0000000..5213c0f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/plugins/app.ts
@@ -0,0 +1,108 @@
+import { h } from 'vue';
+import type { App } from 'vue';
+import { NButton } from 'naive-ui';
+import { $t } from '@/locales';
+
+export function setupAppErrorHandle(app: App) {
+ app.config.errorHandler = (err, vm, info) => {
+ // eslint-disable-next-line no-console
+ console.error(err, vm, info);
+ };
+}
+
+export function setupAppVersionNotification() {
+ // Update check interval in milliseconds
+ const UPDATE_CHECK_INTERVAL = 3 * 60 * 1000;
+
+ const canAutoUpdateApp = import.meta.env.VITE_AUTOMATICALLY_DETECT_UPDATE === 'Y' && import.meta.env.PROD;
+ if (!canAutoUpdateApp) return;
+
+ let isShow = false;
+ let updateInterval: ReturnType<typeof setInterval> | undefined;
+
+ const checkForUpdates = async () => {
+ if (isShow) return;
+
+ const buildTime = await getHtmlBuildTime();
+
+ // If failed to get build time or build time hasn't changed, no update is needed.
+ if (!buildTime || buildTime === BUILD_TIME) {
+ return;
+ }
+
+ isShow = true;
+
+ // Show update notification
+ const n = window.$notification?.create({
+ title: $t('system.updateTitle'),
+ content: $t('system.updateContent'),
+ action() {
+ return h('div', { style: { display: 'flex', justifyContent: 'end', gap: '12px', width: '325px' } }, [
+ h(
+ NButton,
+ {
+ onClick() {
+ n?.destroy();
+ isShow = false;
+ }
+ },
+ () => $t('system.updateCancel')
+ ),
+ h(
+ NButton,
+ {
+ type: 'primary',
+ onClick() {
+ location.reload();
+ }
+ },
+ () => $t('system.updateConfirm')
+ )
+ ]);
+ },
+ onClose() {
+ isShow = false;
+ }
+ });
+ };
+
+ const startUpdateInterval = () => {
+ if (updateInterval) {
+ clearInterval(updateInterval);
+ }
+ updateInterval = setInterval(checkForUpdates, UPDATE_CHECK_INTERVAL);
+ };
+
+ // If updates should be checked, set up the visibility change listener and start the update interval
+ if (!isShow && document.visibilityState === 'visible') {
+ // Check for updates when the document is visible
+ document.addEventListener('visibilitychange', () => {
+ if (document.visibilityState === 'visible') {
+ checkForUpdates();
+ startUpdateInterval();
+ }
+ });
+
+ // Start the update interval
+ startUpdateInterval();
+ }
+}
+
+async function getHtmlBuildTime(): Promise<string | null> {
+ const baseUrl = import.meta.env.VITE_BASE_URL || '/';
+
+ try {
+ const res = await fetch(`${baseUrl}index.html?time=${Date.now()}`);
+
+ if (!res.ok) {
+ return null;
+ }
+
+ const html = await res.text();
+ const match = html.match(/<meta name="buildTime" content="(.*)">/);
+ return match?.[1] || null;
+ } catch (error) {
+ window.console.error('getHtmlBuildTime error:', error);
+ return null;
+ }
+}
diff --git a/ruoyi-plus-soybean/src/plugins/assets.ts b/ruoyi-plus-soybean/src/plugins/assets.ts
new file mode 100755
index 0000000..904aa6a
--- /dev/null
+++ b/ruoyi-plus-soybean/src/plugins/assets.ts
@@ -0,0 +1,3 @@
+import 'virtual:svg-icons-register';
+import 'uno.css';
+import '../styles/css/global.css';
diff --git a/ruoyi-plus-soybean/src/plugins/dayjs.ts b/ruoyi-plus-soybean/src/plugins/dayjs.ts
new file mode 100755
index 0000000..d3f96ee
--- /dev/null
+++ b/ruoyi-plus-soybean/src/plugins/dayjs.ts
@@ -0,0 +1,9 @@
+import { extend } from 'dayjs';
+import localeData from 'dayjs/plugin/localeData';
+import { setDayjsLocale } from '../locales/dayjs';
+
+export function setupDayjs() {
+ extend(localeData);
+
+ setDayjsLocale();
+}
diff --git a/ruoyi-plus-soybean/src/plugins/iconify.ts b/ruoyi-plus-soybean/src/plugins/iconify.ts
new file mode 100755
index 0000000..8d52c26
--- /dev/null
+++ b/ruoyi-plus-soybean/src/plugins/iconify.ts
@@ -0,0 +1,10 @@
+import { addAPIProvider } from '@iconify/vue';
+
+/** Setup the iconify offline */
+export function setupIconifyOffline() {
+ const { VITE_ICONIFY_URL } = import.meta.env;
+
+ if (VITE_ICONIFY_URL) {
+ addAPIProvider('', { resources: [VITE_ICONIFY_URL] });
+ }
+}
diff --git a/ruoyi-plus-soybean/src/plugins/index.ts b/ruoyi-plus-soybean/src/plugins/index.ts
new file mode 100755
index 0000000..b2c9f9b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/plugins/index.ts
@@ -0,0 +1,5 @@
+export * from './loading';
+export * from './nprogress';
+export * from './iconify';
+export * from './dayjs';
+export * from './app';
diff --git a/ruoyi-plus-soybean/src/plugins/loading.ts b/ruoyi-plus-soybean/src/plugins/loading.ts
new file mode 100755
index 0000000..698f6fd
--- /dev/null
+++ b/ruoyi-plus-soybean/src/plugins/loading.ts
@@ -0,0 +1,34 @@
+// @unocss-include
+import { getRgb } from '@sa/color';
+import { DARK_CLASS } from '@/constants/app';
+import { localStg } from '@/utils/storage';
+import { toggleHtmlClass } from '@/utils/common';
+import { $t } from '@/locales';
+import '@/styles/scss/loading.scss';
+
+export function setupLoading() {
+ const app = document.getElementById('app');
+
+ const themeColor = localStg.get('themeColor') || '#2080f0';
+ const darkMode = localStg.get('darkMode') || false;
+ const { r, g, b } = getRgb(themeColor);
+
+ const primaryColor = `--primary-color: ${r} ${g} ${b}`;
+
+ if (darkMode) {
+ toggleHtmlClass(DARK_CLASS).add();
+ }
+
+ const loading = `
+<div class="fixed-center flex-col bg-layout" style="${primaryColor}">
+ <div class="my-52px h-120px w-120px">
+ <!-- From Uiverse.io by SchawnnahJ -->
+ <div class="loader"></div>
+ </div>
+ <h2 class="text-30px text-primary-400 font-500">${$t('system.title')}</h2>
+</div>`;
+
+ if (app) {
+ app.innerHTML = loading;
+ }
+}
diff --git a/ruoyi-plus-soybean/src/plugins/nprogress.ts b/ruoyi-plus-soybean/src/plugins/nprogress.ts
new file mode 100755
index 0000000..841ae1e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/plugins/nprogress.ts
@@ -0,0 +1,9 @@
+import NProgress from 'nprogress';
+
+/** Setup plugin NProgress */
+export function setupNProgress() {
+ NProgress.configure({ easing: 'ease', speed: 500 });
+
+ // mount on window
+ window.NProgress = NProgress;
+}
diff --git a/ruoyi-plus-soybean/src/router/elegant/imports.ts b/ruoyi-plus-soybean/src/router/elegant/imports.ts
new file mode 100755
index 0000000..46ae9f6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/router/elegant/imports.ts
@@ -0,0 +1,51 @@
+/* eslint-disable */
+/* prettier-ignore */
+// Generated by elegant-router
+// Read more: https://github.com/soybeanjs/elegant-router
+
+import type { RouteComponent } from "vue-router";
+import type { LastLevelRouteKey, RouteLayout } from "@elegant-router/types";
+
+import BaseLayout from "@/layouts/base-layout/index.vue";
+import BlankLayout from "@/layouts/blank-layout/index.vue";
+
+export const layouts: Record<RouteLayout, RouteComponent | (() => Promise<RouteComponent>)> = {
+ base: BaseLayout,
+ blank: BlankLayout,
+};
+
+export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<RouteComponent>)> = {
+ 403: () => import("@/views/_builtin/403/index.vue"),
+ 404: () => import("@/views/_builtin/404/index.vue"),
+ 500: () => import("@/views/_builtin/500/index.vue"),
+ "iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
+ login: () => import("@/views/_builtin/login/index.vue"),
+ "social-callback": () => import("@/views/_builtin/social-callback/index.vue"),
+ "user-center": () => import("@/views/_builtin/user-center/index.vue"),
+ about: () => import("@/views/about/index.vue"),
+ analy_hoister: () => import("@/views/analy/hoister/index.vue"),
+ "analy_output-analy": () => import("@/views/analy/output-analy/index.vue"),
+ analy_packer: () => import("@/views/analy/packer/index.vue"),
+ analy_roller: () => import("@/views/analy/roller/index.vue"),
+ demo_demo: () => import("@/views/demo/demo/index.vue"),
+ demo_tree: () => import("@/views/demo/tree/index.vue"),
+ home: () => import("@/views/home/index.vue"),
+ monitor_cache: () => import("@/views/monitor/cache/index.vue"),
+ monitor_logininfor: () => import("@/views/monitor/logininfor/index.vue"),
+ monitor_online: () => import("@/views/monitor/online/index.vue"),
+ monitor_operlog: () => import("@/views/monitor/operlog/index.vue"),
+ system_client: () => import("@/views/system/client/index.vue"),
+ system_config: () => import("@/views/system/config/index.vue"),
+ system_dept: () => import("@/views/system/dept/index.vue"),
+ system_dict: () => import("@/views/system/dict/index.vue"),
+ system_menu: () => import("@/views/system/menu/index.vue"),
+ system_notice: () => import("@/views/system/notice/index.vue"),
+ "system_oss-config": () => import("@/views/system/oss-config/index.vue"),
+ system_oss: () => import("@/views/system/oss/index.vue"),
+ system_post: () => import("@/views/system/post/index.vue"),
+ system_role: () => import("@/views/system/role/index.vue"),
+ "system_tenant-package": () => import("@/views/system/tenant-package/index.vue"),
+ system_tenant: () => import("@/views/system/tenant/index.vue"),
+ system_user: () => import("@/views/system/user/index.vue"),
+ tool_gen: () => import("@/views/tool/gen/index.vue"),
+};
diff --git a/ruoyi-plus-soybean/src/router/elegant/routes.ts b/ruoyi-plus-soybean/src/router/elegant/routes.ts
new file mode 100755
index 0000000..dcd996d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/router/elegant/routes.ts
@@ -0,0 +1,437 @@
+/* eslint-disable */
+/* prettier-ignore */
+// Generated by elegant-router
+// Read more: https://github.com/soybeanjs/elegant-router
+
+import type { GeneratedRoute } from '@elegant-router/types';
+
+export const generatedRoutes: GeneratedRoute[] = [
+ {
+ name: '403',
+ path: '/403',
+ component: 'layout.blank$view.403',
+ meta: {
+ title: '403',
+ i18nKey: 'route.403',
+ constant: true,
+ hideInMenu: true
+ }
+ },
+ {
+ name: '404',
+ path: '/404',
+ component: 'layout.blank$view.404',
+ meta: {
+ title: '404',
+ i18nKey: 'route.404',
+ constant: true,
+ hideInMenu: true
+ }
+ },
+ {
+ name: '500',
+ path: '/500',
+ component: 'layout.blank$view.500',
+ meta: {
+ title: '500',
+ i18nKey: 'route.500',
+ constant: true,
+ hideInMenu: true
+ }
+ },
+ {
+ name: 'about',
+ path: '/about',
+ component: 'layout.base$view.about',
+ meta: {
+ title: 'about',
+ i18nKey: 'route.about',
+ icon: 'fluent:book-information-24-regular',
+ order: 99
+ }
+ },
+ {
+ name: 'analy',
+ path: '/analy',
+ component: 'layout.base',
+ meta: {
+ title: 'analy',
+ i18nKey: 'route.analy'
+ },
+ children: [
+ {
+ name: 'analy_hoister',
+ path: '/analy/hoister',
+ component: 'view.analy_hoister',
+ meta: {
+ title: 'analy_hoister',
+ i18nKey: 'route.analy_hoister'
+ }
+ },
+ {
+ name: 'analy_output-analy',
+ path: '/analy/output-analy',
+ component: 'view.analy_output-analy',
+ meta: {
+ title: 'analy_output-analy',
+ i18nKey: 'route.analy_output-analy'
+ }
+ },
+ {
+ name: 'analy_packer',
+ path: '/analy/packer',
+ component: 'view.analy_packer',
+ meta: {
+ title: 'analy_packer',
+ i18nKey: 'route.analy_packer'
+ }
+ },
+ {
+ name: 'analy_roller',
+ path: '/analy/roller',
+ component: 'view.analy_roller',
+ meta: {
+ title: 'analy_roller',
+ i18nKey: 'route.analy_roller'
+ }
+ }
+ ]
+ },
+ {
+ name: 'demo',
+ path: '/demo',
+ component: 'layout.base',
+ meta: {
+ title: 'demo',
+ i18nKey: 'route.demo'
+ },
+ children: [
+ {
+ name: 'demo_demo',
+ path: '/demo/demo',
+ component: 'view.demo_demo',
+ meta: {
+ title: 'demo_demo',
+ i18nKey: 'route.demo_demo'
+ }
+ },
+ {
+ name: 'demo_tree',
+ path: '/demo/tree',
+ component: 'view.demo_tree',
+ meta: {
+ title: 'demo_tree',
+ i18nKey: 'route.demo_tree'
+ }
+ }
+ ]
+ },
+ {
+ name: 'home',
+ path: '/home',
+ component: 'layout.base$view.home',
+ meta: {
+ title: 'home',
+ i18nKey: 'route.home',
+ icon: 'mdi:monitor-dashboard',
+ order: 1
+ }
+ },
+ {
+ name: 'iframe-page',
+ path: '/iframe-page/:url',
+ component: 'layout.base$view.iframe-page',
+ props: true,
+ meta: {
+ title: 'iframe-page',
+ i18nKey: 'route.iframe-page',
+ constant: true,
+ hideInMenu: true,
+ keepAlive: true
+ }
+ },
+ {
+ name: 'login',
+ path: '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?',
+ component: 'layout.blank$view.login',
+ props: true,
+ meta: {
+ title: 'login',
+ i18nKey: 'route.login',
+ constant: true,
+ hideInMenu: true
+ }
+ },
+ {
+ name: 'monitor',
+ path: '/monitor',
+ component: 'layout.base',
+ meta: {
+ title: 'monitor',
+ i18nKey: 'route.monitor'
+ },
+ children: [
+ {
+ name: 'monitor_cache',
+ path: '/monitor/cache',
+ component: 'view.monitor_cache',
+ meta: {
+ title: 'monitor_cache',
+ i18nKey: 'route.monitor_cache'
+ }
+ },
+ {
+ name: 'monitor_logininfor',
+ path: '/monitor/logininfor',
+ component: 'view.monitor_logininfor',
+ meta: {
+ title: 'monitor_logininfor',
+ i18nKey: 'route.monitor_logininfor'
+ }
+ },
+ {
+ name: 'monitor_online',
+ path: '/monitor/online',
+ component: 'view.monitor_online',
+ meta: {
+ title: 'monitor_online',
+ i18nKey: 'route.monitor_online'
+ }
+ },
+ {
+ name: 'monitor_operlog',
+ path: '/monitor/operlog',
+ component: 'view.monitor_operlog',
+ meta: {
+ title: 'monitor_operlog',
+ i18nKey: 'route.monitor_operlog'
+ }
+ }
+ ]
+ },
+ {
+ name: 'social-callback',
+ path: '/social-callback',
+ component: 'layout.blank$view.social-callback',
+ meta: {
+ title: 'social-callback',
+ i18nKey: 'route.social-callback',
+ constant: true,
+ hideInMenu: true
+ }
+ },
+ {
+ name: 'system',
+ path: '/system',
+ component: 'layout.base',
+ meta: {
+ title: 'system',
+ i18nKey: 'route.system',
+ localIcon: 'menu-system',
+ order: 1
+ },
+ children: [
+ {
+ name: 'system_client',
+ path: '/system/client',
+ component: 'view.system_client',
+ meta: {
+ title: 'system_client',
+ i18nKey: 'route.system_client'
+ }
+ },
+ {
+ name: 'system_config',
+ path: '/system/config',
+ component: 'view.system_config',
+ meta: {
+ title: 'system_config',
+ i18nKey: 'route.system_config'
+ }
+ },
+ {
+ name: 'system_dept',
+ path: '/system/dept',
+ component: 'view.system_dept',
+ meta: {
+ title: 'system_dept',
+ i18nKey: 'route.system_dept'
+ }
+ },
+ {
+ name: 'system_dict',
+ path: '/system/dict',
+ component: 'view.system_dict',
+ meta: {
+ title: 'system_dict',
+ i18nKey: 'route.system_dict'
+ },
+ children: [
+ {
+ name: 'system_dict_data',
+ path: '/system/dict/data',
+ component: 'view.system_dict_data',
+ meta: {
+ title: 'system_dict_data',
+ i18nKey: 'route.system_dict_data'
+ }
+ }
+ ]
+ },
+ {
+ name: 'system_menu',
+ path: '/system/menu',
+ component: 'view.system_menu',
+ meta: {
+ title: 'system_menu',
+ i18nKey: 'route.system_menu',
+ localIcon: 'menu-tree-table',
+ order: 3
+ }
+ },
+ {
+ name: 'system_notice',
+ path: '/system/notice',
+ component: 'view.system_notice',
+ meta: {
+ title: 'system_notice',
+ i18nKey: 'route.system_notice'
+ }
+ },
+ {
+ name: 'system_oss',
+ path: '/system/oss',
+ component: 'view.system_oss',
+ meta: {
+ title: 'system_oss',
+ i18nKey: 'route.system_oss'
+ }
+ },
+ {
+ name: 'system_oss-config',
+ path: '/system/oss-config',
+ component: 'view.system_oss-config',
+ meta: {
+ title: 'system_oss-config',
+ i18nKey: 'route.system_oss-config',
+ constant: true,
+ hideInMenu: true,
+ icon: 'hugeicons:configuration-01'
+ }
+ },
+ {
+ name: 'system_post',
+ path: '/system/post',
+ component: 'view.system_post',
+ meta: {
+ title: 'system_post',
+ i18nKey: 'route.system_post'
+ }
+ },
+ {
+ name: 'system_role',
+ path: '/system/role',
+ component: 'view.system_role',
+ meta: {
+ title: 'system_role',
+ i18nKey: 'route.system_role'
+ },
+ children: [
+ {
+ name: 'system_role_auth-user',
+ path: '/system/role/auth-user',
+ component: 'view.system_role_auth-user',
+ meta: {
+ title: 'system_role_auth-user',
+ i18nKey: 'route.system_role_auth-user'
+ }
+ }
+ ]
+ },
+ {
+ name: 'system_tenant',
+ path: '/system/tenant',
+ component: 'view.system_tenant',
+ meta: {
+ title: 'system_tenant',
+ i18nKey: 'route.system_tenant'
+ }
+ },
+ {
+ name: 'system_tenant-package',
+ path: '/system/tenant-package',
+ component: 'view.system_tenant-package',
+ meta: {
+ title: 'system_tenant-package',
+ i18nKey: 'route.system_tenant-package'
+ }
+ },
+ {
+ name: 'system_user',
+ path: '/system/user',
+ component: 'view.system_user',
+ meta: {
+ title: 'system_user',
+ i18nKey: 'route.system_user'
+ },
+ children: [
+ {
+ name: 'system_user_auth-role',
+ path: '/system/user/auth-role',
+ component: 'view.system_user_auth-role',
+ meta: {
+ title: 'system_user_auth-role',
+ i18nKey: 'route.system_user_auth-role'
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ name: 'tool',
+ path: '/tool',
+ component: 'layout.base',
+ meta: {
+ title: 'tool',
+ i18nKey: 'route.tool',
+ localIcon: 'menu-tool',
+ order: 4
+ },
+ children: [
+ {
+ name: 'tool_gen',
+ path: '/tool/gen',
+ component: 'view.tool_gen',
+ meta: {
+ title: 'tool_gen',
+ i18nKey: 'route.tool_gen',
+ localIcon: 'menu-code',
+ order: 2
+ },
+ children: [
+ {
+ name: 'tool_gen_edit-table',
+ path: '/tool/gen/edit-table',
+ component: 'view.tool_gen_edit-table',
+ meta: {
+ title: 'tool_gen_edit-table',
+ i18nKey: 'route.tool_gen_edit-table'
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ name: 'user-center',
+ path: '/user-center',
+ component: 'layout.base$view.user-center',
+ meta: {
+ title: 'user-center',
+ i18nKey: 'route.user-center',
+ icon: 'material-symbols:account-circle-full',
+ hideInMenu: true
+ }
+ }
+];
diff --git a/ruoyi-plus-soybean/src/router/elegant/transform.ts b/ruoyi-plus-soybean/src/router/elegant/transform.ts
new file mode 100755
index 0000000..3d3a7aa
--- /dev/null
+++ b/ruoyi-plus-soybean/src/router/elegant/transform.ts
@@ -0,0 +1,228 @@
+/* eslint-disable */
+/* prettier-ignore */
+// Generated by elegant-router
+// Read more: https://github.com/soybeanjs/elegant-router
+
+import type { RouteRecordRaw, RouteComponent } from 'vue-router';
+import type { ElegantConstRoute } from '@elegant-router/vue';
+import type { RouteMap, RouteKey, RoutePath } from '@elegant-router/types';
+
+/**
+ * transform elegant const routes to vue routes
+ * @param routes elegant const routes
+ * @param layouts layout components
+ * @param views view components
+ */
+export function transformElegantRoutesToVueRoutes(
+ routes: ElegantConstRoute[],
+ layouts: Record<string, RouteComponent | (() => Promise<RouteComponent>)>,
+ views: Record<string, RouteComponent | (() => Promise<RouteComponent>)>
+) {
+ return routes.flatMap(route => transformElegantRouteToVueRoute(route, layouts, views));
+}
+
+/**
+ * transform elegant route to vue route
+ * @param route elegant const route
+ * @param layouts layout components
+ * @param views view components
+ */
+function transformElegantRouteToVueRoute(
+ route: ElegantConstRoute,
+ layouts: Record<string, RouteComponent | (() => Promise<RouteComponent>)>,
+ views: Record<string, RouteComponent | (() => Promise<RouteComponent>)>
+) {
+ const LAYOUT_PREFIX = 'layout.';
+ const VIEW_PREFIX = 'view.';
+ const ROUTE_DEGREE_SPLITTER = '_';
+ const FIRST_LEVEL_ROUTE_COMPONENT_SPLIT = '$';
+
+ function isLayout(component: string) {
+ return component.startsWith(LAYOUT_PREFIX);
+ }
+
+ function getLayoutName(component: string) {
+ const layout = component.replace(LAYOUT_PREFIX, '');
+
+ if(!layouts[layout]) {
+ throw new Error(`Layout component "${layout}" not found`);
+ }
+
+ return layout;
+ }
+
+ function isView(component: string) {
+ return component.startsWith(VIEW_PREFIX);
+ }
+
+ function getViewName(component: string) {
+ const view = component.replace(VIEW_PREFIX, '');
+
+ if(!views[view]) {
+ throw new Error(`View component "${view}" not found`);
+ }
+
+ return view;
+ }
+
+ function isFirstLevelRoute(item: ElegantConstRoute) {
+ return !item.name.includes(ROUTE_DEGREE_SPLITTER);
+ }
+
+ function isSingleLevelRoute(item: ElegantConstRoute) {
+ return isFirstLevelRoute(item) && !item.children?.length;
+ }
+
+ function getSingleLevelRouteComponent(component: string) {
+ const [layout, view] = component.split(FIRST_LEVEL_ROUTE_COMPONENT_SPLIT);
+
+ return {
+ layout: getLayoutName(layout),
+ view: getViewName(view)
+ };
+ }
+
+ const vueRoutes: RouteRecordRaw[] = [];
+
+ // add props: true to route
+ if (route.path.includes(':') && !route.props) {
+ route.props = true;
+ }
+
+ const { name, path, component, children, ...rest } = route;
+
+ const vueRoute = { name, path, ...rest } as RouteRecordRaw;
+
+ try {
+ if (component) {
+ if (isSingleLevelRoute(route)) {
+ const { layout, view } = getSingleLevelRouteComponent(component);
+
+ const singleLevelRoute: RouteRecordRaw = {
+ path,
+ component: layouts[layout],
+ meta: {
+ title: route.meta?.title || ''
+ },
+ children: [
+ {
+ name,
+ path: '',
+ component: views[view],
+ ...rest
+ } as RouteRecordRaw
+ ]
+ };
+
+ return [singleLevelRoute];
+ }
+
+ if (isLayout(component)) {
+ const layoutName = getLayoutName(component);
+
+ vueRoute.component = layouts[layoutName];
+ }
+
+ if (isView(component)) {
+ const viewName = getViewName(component);
+
+ vueRoute.component = views[viewName];
+ }
+
+ }
+ } catch (error: any) {
+ console.error(`Error transforming route "${route.name}": ${error.toString()}`);
+ return [];
+ }
+
+ // add redirect to child
+ if (children?.length && !vueRoute.redirect) {
+ vueRoute.redirect = {
+ name: children[0].name
+ };
+ }
+
+ if (children?.length) {
+ const childRoutes = children.flatMap(child => transformElegantRouteToVueRoute(child, layouts, views));
+
+ if(isFirstLevelRoute(route)) {
+ vueRoute.children = childRoutes;
+ } else {
+ vueRoutes.push(...childRoutes);
+ }
+ }
+
+ vueRoutes.unshift(vueRoute);
+
+ return vueRoutes;
+}
+
+/**
+ * map of route name and route path
+ */
+const routeMap: RouteMap = {
+ "root": "/",
+ "not-found": "/:pathMatch(.*)*",
+ "exception": "/exception",
+ "exception_403": "/exception/403",
+ "exception_404": "/exception/404",
+ "exception_500": "/exception/500",
+ "403": "/403",
+ "404": "/404",
+ "500": "/500",
+ "about": "/about",
+ "analy": "/analy",
+ "analy_hoister": "/analy/hoister",
+ "analy_output-analy": "/analy/output-analy",
+ "analy_packer": "/analy/packer",
+ "analy_roller": "/analy/roller",
+ "demo": "/demo",
+ "demo_demo": "/demo/demo",
+ "demo_tree": "/demo/tree",
+ "home": "/home",
+ "iframe-page": "/iframe-page/:url",
+ "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
+ "monitor": "/monitor",
+ "monitor_cache": "/monitor/cache",
+ "monitor_logininfor": "/monitor/logininfor",
+ "monitor_online": "/monitor/online",
+ "monitor_operlog": "/monitor/operlog",
+ "social-callback": "/social-callback",
+ "system": "/system",
+ "system_client": "/system/client",
+ "system_config": "/system/config",
+ "system_dept": "/system/dept",
+ "system_dict": "/system/dict",
+ "system_menu": "/system/menu",
+ "system_notice": "/system/notice",
+ "system_oss": "/system/oss",
+ "system_oss-config": "/system/oss-config",
+ "system_post": "/system/post",
+ "system_role": "/system/role",
+ "system_tenant": "/system/tenant",
+ "system_tenant-package": "/system/tenant-package",
+ "system_user": "/system/user",
+ "tool": "/tool",
+ "tool_gen": "/tool/gen",
+ "user-center": "/user-center"
+};
+
+/**
+ * get route path by route name
+ * @param name route name
+ */
+export function getRoutePath<T extends RouteKey>(name: T) {
+ return routeMap[name];
+}
+
+/**
+ * get route name by route path
+ * @param path route path
+ */
+export function getRouteName(path: RoutePath) {
+ const routeEntries = Object.entries(routeMap) as [RouteKey, RoutePath][];
+
+ const routeName: RouteKey | null = routeEntries.find(([, routePath]) => routePath === path)?.[0] || null;
+
+ return routeName;
+}
diff --git a/ruoyi-plus-soybean/src/router/guard/index.ts b/ruoyi-plus-soybean/src/router/guard/index.ts
new file mode 100755
index 0000000..9f91c9b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/router/guard/index.ts
@@ -0,0 +1,15 @@
+import type { Router } from 'vue-router';
+import { createRouteGuard } from './route';
+import { createProgressGuard } from './progress';
+import { createDocumentTitleGuard } from './title';
+
+/**
+ * Router guard
+ *
+ * @param router - Router instance
+ */
+export function createRouterGuard(router: Router) {
+ createProgressGuard(router);
+ createRouteGuard(router);
+ createDocumentTitleGuard(router);
+}
diff --git a/ruoyi-plus-soybean/src/router/guard/progress.ts b/ruoyi-plus-soybean/src/router/guard/progress.ts
new file mode 100755
index 0000000..a1d4aee
--- /dev/null
+++ b/ruoyi-plus-soybean/src/router/guard/progress.ts
@@ -0,0 +1,11 @@
+import type { Router } from 'vue-router';
+
+export function createProgressGuard(router: Router) {
+ router.beforeEach((_to, _from, next) => {
+ window.NProgress?.start?.();
+ next();
+ });
+ router.afterEach(_to => {
+ window.NProgress?.done?.();
+ });
+}
diff --git a/ruoyi-plus-soybean/src/router/guard/route.ts b/ruoyi-plus-soybean/src/router/guard/route.ts
new file mode 100755
index 0000000..1a36989
--- /dev/null
+++ b/ruoyi-plus-soybean/src/router/guard/route.ts
@@ -0,0 +1,192 @@
+import type {
+ LocationQueryRaw,
+ NavigationGuardNext,
+ RouteLocationNormalized,
+ RouteLocationRaw,
+ Router
+} from 'vue-router';
+import type { RouteKey, RoutePath } from '@elegant-router/types';
+import { useAuthStore } from '@/store/modules/auth';
+import { useRouteStore } from '@/store/modules/route';
+import { localStg } from '@/utils/storage';
+import { getRouteName } from '@/router/elegant/transform';
+
+/**
+ * create route guard
+ *
+ * @param router router instance
+ */
+export function createRouteGuard(router: Router) {
+ router.beforeEach(async (to, from, next) => {
+ const location = await initRoute(to);
+
+ if (location) {
+ next(location);
+ return;
+ }
+
+ const authStore = useAuthStore();
+
+ const rootRoute: RouteKey = 'root';
+ const loginRoute: RouteKey = 'login';
+ const noAuthorizationRoute: RouteKey = '403';
+
+ const isLogin = Boolean(localStg.get('token'));
+ const needLogin = !to.meta.constant;
+ const routeRoles = to.meta.roles || [];
+
+ const hasRole = authStore.userInfo.roles.some(role => routeRoles.includes(role));
+ const hasAuth = authStore.isStaticSuper || !routeRoles.length || hasRole;
+
+ // if it is login route when logged in, then switch to the root page
+ if (to.name === loginRoute && isLogin) {
+ next({ name: rootRoute });
+ return;
+ }
+
+ // if the route does not need login, then it is allowed to access directly
+ if (!needLogin) {
+ handleRouteSwitch(to, from, next);
+ return;
+ }
+
+ // the route need login but the user is not logged in, then switch to the login page
+ if (!isLogin) {
+ next({ name: loginRoute, query: { redirect: to.fullPath } });
+ return;
+ }
+
+ // if the user is logged in but does not have authorization, then switch to the 403 page
+ if (!hasAuth) {
+ next({ name: noAuthorizationRoute });
+ return;
+ }
+
+ // switch route normally
+ handleRouteSwitch(to, from, next);
+ });
+}
+
+/**
+ * initialize route
+ *
+ * @param to to route
+ */
+async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw | null> {
+ const routeStore = useRouteStore();
+
+ const notFoundRoute: RouteKey = 'not-found';
+ const isNotFoundRoute = to.name === notFoundRoute;
+
+ // if the constant route is not initialized, then initialize the constant route
+ if (!routeStore.isInitConstantRoute) {
+ await routeStore.initConstantRoute();
+
+ // the route is captured by the "not-found" route because the constant route is not initialized
+ // after the constant route is initialized, redirect to the original route
+ const path = to.fullPath;
+ const location: RouteLocationRaw = {
+ path,
+ replace: true,
+ query: to.query,
+ hash: to.hash
+ };
+
+ return location;
+ }
+
+ const isLogin = Boolean(localStg.get('token'));
+
+ if (!isLogin) {
+ // if the user is not logged in and the route is a constant route but not the "not-found" route, then it is allowed to access.
+ if (to.meta.constant && !isNotFoundRoute) {
+ routeStore.onRouteSwitchWhenNotLoggedIn();
+
+ return null;
+ }
+
+ // if the user is not logged in, then switch to the login page
+ const loginRoute: RouteKey = 'login';
+ const query = getRouteQueryOfLoginRoute(to, routeStore.routeHome);
+
+ const location: RouteLocationRaw = {
+ name: loginRoute,
+ query
+ };
+
+ return location;
+ }
+
+ if (!routeStore.isInitAuthRoute) {
+ // initialize the auth route
+ await routeStore.initAuthRoute();
+
+ // the route is captured by the "not-found" route because the auth route is not initialized
+ // after the auth route is initialized, redirect to the original route
+ if (isNotFoundRoute) {
+ const rootRoute: RouteKey = 'root';
+ const path = to.redirectedFrom?.name === rootRoute ? '/' : to.fullPath;
+
+ const location: RouteLocationRaw = {
+ path,
+ replace: true,
+ query: to.query,
+ hash: to.hash
+ };
+
+ return location;
+ }
+ }
+
+ routeStore.onRouteSwitchWhenLoggedIn();
+
+ // the auth route is initialized
+ // it is not the "not-found" route, then it is allowed to access
+ if (!isNotFoundRoute) {
+ return null;
+ }
+
+ // it is captured by the "not-found" route, then check whether the route exists
+ const exist = await routeStore.getIsAuthRouteExist(to.path as RoutePath);
+ const noPermissionRoute: RouteKey = '403';
+
+ if (exist) {
+ const location: RouteLocationRaw = {
+ name: noPermissionRoute
+ };
+
+ return location;
+ }
+
+ return null;
+}
+
+function handleRouteSwitch(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
+ // route with href
+ if (to.meta.href) {
+ window.open(to.meta.href, '_blank');
+
+ next({ path: from.fullPath, replace: true, query: from.query, hash: to.hash });
+
+ return;
+ }
+
+ next();
+}
+
+function getRouteQueryOfLoginRoute(to: RouteLocationNormalized, routeHome: RouteKey) {
+ const loginRoute: RouteKey = 'login';
+ const redirect = to.fullPath;
+ const [redirectPath, redirectQuery] = redirect.split('?');
+ const redirectName = getRouteName(redirectPath as RoutePath);
+
+ const isRedirectHome = routeHome === redirectName;
+
+ const query: LocationQueryRaw = to.name !== loginRoute && !isRedirectHome ? { redirect } : {};
+
+ if (isRedirectHome && redirectQuery) {
+ query.redirect = `/?${redirectQuery}`;
+ }
+
+ return query;
+}
diff --git a/ruoyi-plus-soybean/src/router/guard/title.ts b/ruoyi-plus-soybean/src/router/guard/title.ts
new file mode 100755
index 0000000..ff310f8
--- /dev/null
+++ b/ruoyi-plus-soybean/src/router/guard/title.ts
@@ -0,0 +1,13 @@
+import type { Router } from 'vue-router';
+import { useTitle } from '@vueuse/core';
+import { $t } from '@/locales';
+
+export function createDocumentTitleGuard(router: Router) {
+ router.afterEach(to => {
+ const { i18nKey, title } = to.meta;
+
+ const documentTitle = i18nKey ? $t(i18nKey) : title;
+
+ useTitle(documentTitle);
+ });
+}
diff --git a/ruoyi-plus-soybean/src/router/index.ts b/ruoyi-plus-soybean/src/router/index.ts
new file mode 100755
index 0000000..ec1b925
--- /dev/null
+++ b/ruoyi-plus-soybean/src/router/index.ts
@@ -0,0 +1,30 @@
+import type { App } from 'vue';
+import {
+ type RouterHistory,
+ createMemoryHistory,
+ createRouter,
+ createWebHashHistory,
+ createWebHistory
+} from 'vue-router';
+import { createBuiltinVueRoutes } from './routes/builtin';
+import { createRouterGuard } from './guard';
+
+const { VITE_ROUTER_HISTORY_MODE = 'history', VITE_BASE_URL } = import.meta.env;
+
+const historyCreatorMap: Record<Env.RouterHistoryMode, (base?: string) => RouterHistory> = {
+ hash: createWebHashHistory,
+ history: createWebHistory,
+ memory: createMemoryHistory
+};
+
+export const router = createRouter({
+ history: historyCreatorMap[VITE_ROUTER_HISTORY_MODE](VITE_BASE_URL),
+ routes: createBuiltinVueRoutes()
+});
+
+/** Setup Vue Router */
+export async function setupRouter(app: App) {
+ app.use(router);
+ createRouterGuard(router);
+ await router.isReady();
+}
diff --git a/ruoyi-plus-soybean/src/router/routes/builtin.ts b/ruoyi-plus-soybean/src/router/routes/builtin.ts
new file mode 100755
index 0000000..0a13e78
--- /dev/null
+++ b/ruoyi-plus-soybean/src/router/routes/builtin.ts
@@ -0,0 +1,31 @@
+import type { CustomRoute } from '@elegant-router/types';
+import { layouts, views } from '../elegant/imports';
+import { getRoutePath, transformElegantRoutesToVueRoutes } from '../elegant/transform';
+
+export const ROOT_ROUTE: CustomRoute = {
+ name: 'root',
+ path: '/',
+ redirect: getRoutePath(import.meta.env.VITE_ROUTE_HOME) || '/home',
+ meta: {
+ title: 'root',
+ constant: true
+ }
+};
+
+const NOT_FOUND_ROUTE: CustomRoute = {
+ name: 'not-found',
+ path: '/:pathMatch(.*)*',
+ component: 'layout.blank$view.404',
+ meta: {
+ title: 'not-found',
+ constant: true
+ }
+};
+
+/** builtin routes, it must be constant and setup in vue-router */
+const builtinRoutes: CustomRoute[] = [ROOT_ROUTE, NOT_FOUND_ROUTE];
+
+/** create builtin vue routes */
+export function createBuiltinVueRoutes() {
+ return transformElegantRoutesToVueRoutes(builtinRoutes, layouts, views);
+}
diff --git a/ruoyi-plus-soybean/src/router/routes/index.ts b/ruoyi-plus-soybean/src/router/routes/index.ts
new file mode 100755
index 0000000..4cbcd1b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/router/routes/index.ts
@@ -0,0 +1,156 @@
+import type { CustomRoute, ElegantConstRoute, ElegantRoute } from '@elegant-router/types';
+import { generatedRoutes } from '../elegant/routes';
+import { layouts, views } from '../elegant/imports';
+import { transformElegantRoutesToVueRoutes } from '../elegant/transform';
+
+/**
+ * custom routes
+ *
+ * @link https://github.com/soybeanjs/elegant-router?tab=readme-ov-file#custom-route
+ */
+const customRoutes: CustomRoute[] = [];
+
+/** create routes when the auth route mode is static */
+export function createStaticRoutes() {
+ const constantRoutes: ElegantRoute[] = [];
+
+ const authRoutes: ElegantRoute[] = [];
+
+ [...customRoutes, ...generatedRoutes].forEach(item => {
+ if (item.meta?.constant) {
+ constantRoutes.push(item);
+ } else {
+ authRoutes.push(item);
+ }
+ });
+
+ return {
+ constantRoutes,
+ authRoutes
+ };
+}
+
+const dynamicConstantRoutes: ElegantRoute[] = [
+ {
+ name: 'home',
+ path: '/home',
+ component: 'layout.base$view.home',
+ meta: {
+ title: 'home',
+ i18nKey: 'route.home',
+ icon: 'mdi:monitor-dashboard',
+ order: -1
+ }
+ },
+ {
+ name: '403',
+ path: '/403',
+ component: 'layout.blank$view.403',
+ meta: {
+ title: '403',
+ i18nKey: 'route.403',
+ constant: true,
+ hideInMenu: true
+ }
+ },
+ {
+ name: '404',
+ path: '/404',
+ component: 'layout.blank$view.404',
+ meta: {
+ title: '404',
+ i18nKey: 'route.404',
+ constant: true,
+ hideInMenu: true
+ }
+ },
+ {
+ name: '500',
+ path: '/500',
+ component: 'layout.blank$view.500',
+ meta: {
+ title: '500',
+ i18nKey: 'route.500',
+ constant: true,
+ hideInMenu: true
+ }
+ },
+ {
+ name: 'login',
+ path: '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?',
+ component: 'layout.blank$view.login',
+ props: true,
+ meta: {
+ title: 'login',
+ i18nKey: 'route.login',
+ constant: true,
+ hideInMenu: true
+ }
+ },
+ {
+ name: 'iframe-page',
+ path: '/iframe-page/:url',
+ component: 'layout.base$view.iframe-page',
+ props: true,
+ meta: {
+ title: 'iframe-page',
+ i18nKey: 'route.iframe-page',
+ constant: true,
+ hideInMenu: true,
+ keepAlive: true,
+ icon: 'material-symbols:iframe-outline'
+ }
+ },
+ {
+ name: 'social-callback',
+ path: '/social-callback',
+ component: 'layout.blank$view.social-callback',
+ meta: {
+ title: 'social-callback',
+ i18nKey: 'route.social-callback',
+ constant: true,
+ hideInMenu: true,
+ icon: 'simple-icons:authy'
+ }
+ },
+ {
+ name: 'user-center',
+ path: '/user-center',
+ component: 'layout.base$view.user-center',
+ meta: {
+ title: 'user-center',
+ i18nKey: 'route.user-center',
+ icon: 'material-symbols:account-circle-full',
+ hideInMenu: true
+ }
+ }
+];
+
+/** create routes when the auth route mode is static */
+export function createDynamicRoutes() {
+ const constantRoutes: ElegantConstRoute[] = [];
+
+ const authRoutes: ElegantConstRoute[] = [];
+
+ [...customRoutes, ...dynamicConstantRoutes].forEach(item => {
+ if (item.meta?.constant) {
+ constantRoutes.push(item);
+ } else {
+ authRoutes.push(item);
+ }
+ });
+
+ return {
+ constantRoutes,
+ authRoutes
+ };
+}
+
+/**
+ * Get auth vue routes
+ *
+ * @param routes Elegant routes
+ */
+export function getAuthVueRoutes(routes: ElegantConstRoute[]) {
+ return transformElegantRoutesToVueRoutes(routes, layouts, views);
+}
diff --git a/ruoyi-plus-soybean/src/service/api/analy/hoister-data.ts b/ruoyi-plus-soybean/src/service/api/analy/hoister-data.ts
new file mode 100755
index 0000000..4acf04e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/analy/hoister-data.ts
@@ -0,0 +1,35 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇鎻愬崌鏈哄垎鏋愬垪琛� */
+export function fetchGetHoisterDataList (params?: Api.Qa.HoisterDataSearchParams) {
+ return request<Api.Qa.HoisterDataList>({
+ url: '/analy/hoister/list',
+ method: 'get',
+ params
+ });
+}
+/** 鏂板鎻愬崌鏈哄垎鏋� */
+export function fetchCreateHoisterData (data: Api.Qa.HoisterDataOperateParams) {
+ return request<boolean>({
+ url: '/analy/hoister',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼鎻愬崌鏈哄垎鏋� */
+export function fetchUpdateHoisterData (data: Api.Qa.HoisterDataOperateParams) {
+ return request<boolean>({
+ url: '/analy/hoister',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎鎻愬崌鏈哄垎鏋� */
+export function fetchBatchDeleteHoisterData (times: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/analy/hoister/${times.join(',')}`,
+ method: 'delete'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/analy/packer-data.ts b/ruoyi-plus-soybean/src/service/api/analy/packer-data.ts
new file mode 100755
index 0000000..24eb462
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/analy/packer-data.ts
@@ -0,0 +1,35 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇鍖呰鏈哄垎鏋愬垪琛� */
+export function fetchGetPackerDataList (params?: Api.Qa.PackerDataSearchParams) {
+ return request<Api.Qa.PackerDataList>({
+ url: '/analy/packer/list',
+ method: 'get',
+ params
+ });
+}
+/** 鏂板鍖呰鏈哄垎鏋� */
+export function fetchCreatePackerData (data: Api.Qa.PackerDataOperateParams) {
+ return request<boolean>({
+ url: '/analy/packer',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼鍖呰鏈哄垎鏋� */
+export function fetchUpdatePackerData (data: Api.Qa.PackerDataOperateParams) {
+ return request<boolean>({
+ url: '/analy/packer',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎鍖呰鏈哄垎鏋� */
+export function fetchBatchDeletePackerData (times: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/analy/packer/${times.join(',')}`,
+ method: 'delete'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/analy/roller-data.ts b/ruoyi-plus-soybean/src/service/api/analy/roller-data.ts
new file mode 100755
index 0000000..f4166e5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/analy/roller-data.ts
@@ -0,0 +1,46 @@
+import { request } from '@/service/request';
+
+
+/** 鑾峰彇鍗锋帴鏈哄垎鏋愬垪琛� */
+export function fetchGetRollerSampleDataList (params?: Api.Qa.RollerDataSearchParams) {
+ return request<Api.Qa.RollerDataList>({
+ url: '/analy/roller/sampleList',
+ method: 'get',
+ params
+ });
+}
+
+
+/** 鑾峰彇鍗锋帴鏈哄垎鏋愬垪琛� */
+export function fetchGetRollerDataList (params?: Api.Qa.RollerDataSearchParams) {
+ return request<Api.Qa.RollerDataList>({
+ url: '/analy/roller/list',
+ method: 'get',
+ params
+ });
+}
+/** 鏂板鍗锋帴鏈哄垎鏋� */
+export function fetchCreateRollerData (data: Api.Qa.RollerDataOperateParams) {
+ return request<boolean>({
+ url: '/analy/roller',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼鍗锋帴鏈哄垎鏋� */
+export function fetchUpdateRollerData (data: Api.Qa.RollerDataOperateParams) {
+ return request<boolean>({
+ url: '/analy/roller',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎鍗锋帴鏈哄垎鏋� */
+export function fetchBatchDeleteRollerData (times: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/analy/roller/${times.join(',')}`,
+ method: 'delete'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/auth.ts b/ruoyi-plus-soybean/src/service/api/auth.ts
new file mode 100755
index 0000000..2ef50b6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/auth.ts
@@ -0,0 +1,78 @@
+import { request } from '../request';
+
+/** Get tenant list */
+export function fetchTenantList() {
+ return request<Api.Auth.LoginTenant>({
+ url: '/auth/tenant/list',
+ method: 'get'
+ });
+}
+
+/** Get image code */
+export function fetchCaptchaCode() {
+ return request<Api.Auth.CaptchaCode>({
+ url: '/auth/code',
+ method: 'get'
+ });
+}
+
+/**
+ * Login
+ *
+ * @param username User name
+ * @param password Password
+ */
+export function fetchLogin(data: Api.Auth.PwdLoginForm) {
+ return request<Api.Auth.LoginToken>({
+ url: '/auth/login',
+ method: 'post',
+ headers: {
+ isToken: false,
+ isEncrypt: true,
+ repeatSubmit: false
+ },
+ data
+ });
+}
+
+/** social login callback */
+export function fetchSocialLoginCallback(data: Api.Auth.SocialLoginForm) {
+ return request({
+ url: '/auth/social/callback',
+ method: 'post',
+ data
+ });
+}
+
+/** Register */
+export function fetchRegister(data: Api.Auth.RegisterForm) {
+ return request<Api.Auth.LoginToken>({
+ url: '/auth/register',
+ method: 'post',
+ headers: {
+ isToken: false,
+ isEncrypt: true,
+ repeatSubmit: false
+ },
+ data
+ });
+}
+
+/** Get user info */
+export function fetchGetUserInfo() {
+ return request<Api.Auth.UserInfo>({ url: '/system/user/getInfo' });
+}
+
+/** Logout */
+export function fetchLogout() {
+ if (import.meta.env.VITE_APP_SSE === 'Y') {
+ request({
+ url: '/resource/sse/close',
+ method: 'get'
+ });
+ }
+ return request({
+ url: '/auth/logout',
+ method: 'post'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/demo/demo.ts b/ruoyi-plus-soybean/src/service/api/demo/demo.ts
new file mode 100755
index 0000000..4624720
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/demo/demo.ts
@@ -0,0 +1,36 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇娴嬭瘯鍗曡〃鍒楄〃 */
+export function fetchGetDemoList(params?: Api.Demo.DemoSearchParams) {
+ return request<Api.Demo.DemoList>({
+ url: '/demo/demo/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鏂板娴嬭瘯鍗曡〃 */
+export function fetchCreateDemo(data: Api.Demo.DemoOperateParams) {
+ return request<boolean>({
+ url: '/demo/demo',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼娴嬭瘯鍗曡〃 */
+export function fetchUpdateDemo(data: Api.Demo.DemoOperateParams) {
+ return request<boolean>({
+ url: '/demo/demo',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎娴嬭瘯鍗曡〃 */
+export function fetchBatchDeleteDemo(ids: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/demo/demo/${ids.join(',')}`,
+ method: 'delete'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/demo/index.ts b/ruoyi-plus-soybean/src/service/api/demo/index.ts
new file mode 100755
index 0000000..eb84d86
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/demo/index.ts
@@ -0,0 +1,2 @@
+export * from './demo';
+export * from './tree';
diff --git a/ruoyi-plus-soybean/src/service/api/demo/tree.ts b/ruoyi-plus-soybean/src/service/api/demo/tree.ts
new file mode 100755
index 0000000..bb8666d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/demo/tree.ts
@@ -0,0 +1,36 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇娴嬭瘯鏍戝垪琛� */
+export function fetchGetTreeList(params?: Api.Demo.TreeSearchParams) {
+ return request<Api.Demo.TreeList>({
+ url: '/demo/tree/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鏂板娴嬭瘯鏍� */
+export function fetchCreateTree(data: Api.Demo.TreeOperateParams) {
+ return request<boolean>({
+ url: '/demo/tree',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼娴嬭瘯鏍� */
+export function fetchUpdateTree(data: Api.Demo.TreeOperateParams) {
+ return request<boolean>({
+ url: '/demo/tree',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎娴嬭瘯鏍� */
+export function fetchBatchDeleteTree(ids: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/demo/tree/${ids.join(',')}`,
+ method: 'delete'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/index.ts b/ruoyi-plus-soybean/src/service/api/index.ts
new file mode 100755
index 0000000..89f4e58
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/index.ts
@@ -0,0 +1,2 @@
+export * from './auth';
+export * from './route';
diff --git a/ruoyi-plus-soybean/src/service/api/monitor/cache.ts b/ruoyi-plus-soybean/src/service/api/monitor/cache.ts
new file mode 100755
index 0000000..18583b4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/monitor/cache.ts
@@ -0,0 +1,8 @@
+import { request } from '@/service/request';
+
+export function fetchGetMonitorCacheInfo() {
+ return request<Api.Monitor.CacheInfo>({
+ url: '/monitor/cache',
+ method: 'get'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/monitor/index.ts b/ruoyi-plus-soybean/src/service/api/monitor/index.ts
new file mode 100755
index 0000000..d39dfc9
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/monitor/index.ts
@@ -0,0 +1,4 @@
+export * from './online';
+export * from './oper-log';
+export * from './cache';
+export * from './login-infor';
diff --git a/ruoyi-plus-soybean/src/service/api/monitor/login-infor.ts b/ruoyi-plus-soybean/src/service/api/monitor/login-infor.ts
new file mode 100755
index 0000000..532584e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/monitor/login-infor.ts
@@ -0,0 +1,34 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇绯荤粺璁块棶璁板綍鍒楄〃 */
+export function fetchGetLoginInforList(params?: Api.Monitor.LoginInforSearchParams) {
+ return request<Api.Monitor.LoginInforList>({
+ url: '/monitor/logininfor/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鎵归噺鍒犻櫎绯荤粺璁块棶璁板綍 */
+export function fetchBatchDeleteLoginInfor(infoIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/monitor/logininfor/${infoIds.join(',')}`,
+ method: 'delete'
+ });
+}
+
+/** 瑙i攣绯荤粺璁块棶璁板綍 */
+export function fetchUnlockLoginInfor(username: string) {
+ return request<boolean>({
+ url: `/monitor/logininfor/unlock/${username}`,
+ method: 'get'
+ });
+}
+
+/** 娓呯┖绯荤粺璁块棶璁板綍 */
+export function fetchCleanLoginInfor() {
+ return request<boolean>({
+ url: '/monitor/logininfor/clean',
+ method: 'delete'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/monitor/online.ts b/ruoyi-plus-soybean/src/service/api/monitor/online.ts
new file mode 100755
index 0000000..3654757
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/monitor/online.ts
@@ -0,0 +1,42 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇鍦ㄧ嚎鐢ㄦ埛鍒楄〃 */
+export function fetchGetOnlineUserList(params?: Api.Monitor.OnlineUserSearchParams) {
+ return request<Api.Monitor.OnlineUserList>({
+ url: '/monitor/online/list',
+ method: 'get',
+ params
+ });
+}
+
+/**
+ * 寮哄埗涓嬬嚎
+ *
+ * @param tokenId - 浠ょ墝ID
+ */
+export function fetchForceLogout(tokenId: string) {
+ return request<boolean>({
+ url: `/monitor/online/${tokenId}`,
+ method: 'delete'
+ });
+}
+/**
+ * 寮洪��褰撳墠鍦ㄧ嚎璁惧
+ *
+ * @param tokenId - 浠ょ墝ID
+ */
+export function fetchKickOutCurrentDevice(tokenId: string) {
+ return request<boolean>({
+ url: `/monitor/online/myself/${tokenId}`,
+ method: 'delete'
+ });
+}
+
+/** 鑾峰彇鍦ㄧ嚎璁惧鍒楄〃 */
+export function fetchGetOnlineDeviceList(params?: Api.Monitor.OnlineUserSearchParams) {
+ return request<Api.Monitor.OnlineUserList>({
+ url: '/monitor/online',
+ method: 'get',
+ params
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/monitor/oper-log.ts b/ruoyi-plus-soybean/src/service/api/monitor/oper-log.ts
new file mode 100755
index 0000000..f005f11
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/monitor/oper-log.ts
@@ -0,0 +1,26 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇鎿嶄綔鏃ュ織璁板綍鍒楄〃 */
+export function fetchGetOperLogList(params?: Api.Monitor.OperLogSearchParams) {
+ return request<Api.Monitor.OperLogList>({
+ url: '/monitor/operlog/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鎵归噺鍒犻櫎鎿嶄綔鏃ュ織璁板綍 */
+export function fetchBatchDeleteOperLog(operIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/monitor/operlog/${operIds.join(',')}`,
+ method: 'delete'
+ });
+}
+
+/** 娓呯悊鎿嶄綔鏃ュ織璁板綍 */
+export function fetchCleanOperLog() {
+ return request<boolean>({
+ url: '/monitor/operlog/clean',
+ method: 'delete'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/route.ts b/ruoyi-plus-soybean/src/service/api/route.ts
new file mode 100755
index 0000000..30c7fb6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/route.ts
@@ -0,0 +1,7 @@
+import type { ElegantConstRoute } from '@elegant-router/types';
+import { request } from '../request';
+
+/** get routes */
+export function fetchGetRoutes() {
+ return request<ElegantConstRoute[]>({ url: '/system/menu/getRouters' });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/client.ts b/ruoyi-plus-soybean/src/service/api/system/client.ts
new file mode 100755
index 0000000..2b3705d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/client.ts
@@ -0,0 +1,45 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇瀹㈡埛绔垪琛� */
+export function fetchGetClientList(params?: Api.System.ClientSearchParams) {
+ return request<Api.System.ClientList>({
+ url: '/system/client/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鏂板瀹㈡埛绔� */
+export function fetchCreateClient(data: Api.System.ClientOperateParams) {
+ return request<boolean>({
+ url: '/system/client',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼瀹㈡埛绔� */
+export function fetchUpdateClient(data: Api.System.ClientOperateParams) {
+ return request<boolean>({
+ url: '/system/client',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎瀹㈡埛绔� */
+export function fetchBatchDeleteClient(ids: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/system/client/${ids.join(',')}`,
+ method: 'delete'
+ });
+}
+
+/** 淇敼瀹㈡埛绔姸鎬� */
+export function fetchUpdateClientStatus(data: Api.System.ClientOperateParams) {
+ return request<boolean>({
+ url: '/system/client/changeStatus',
+ method: 'put',
+ data
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/config.ts b/ruoyi-plus-soybean/src/service/api/system/config.ts
new file mode 100755
index 0000000..4eb3bf5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/config.ts
@@ -0,0 +1,69 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇鍙傛暟閰嶇疆鍒楄〃 */
+export function fetchGetConfigList(params?: Api.System.ConfigSearchParams) {
+ return request<Api.System.ConfigList>({
+ url: '/system/config/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鏍规嵁鍙傛暟閿悕鏌ヨ鍙傛暟鍊� */
+export function fetchGetConfigDetail(configKey: string) {
+ return request<Api.System.Config>({
+ url: `/system/config/configKey/${configKey}`,
+ method: 'get'
+ });
+}
+
+/** 鏂板鍙傛暟閰嶇疆 */
+export function fetchCreateConfig(data: Api.System.ConfigOperateParams) {
+ return request<boolean>({
+ url: '/system/config',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼鍙傛暟閰嶇疆 */
+export function fetchUpdateConfig(data: Api.System.ConfigOperateParams) {
+ return request<boolean>({
+ url: '/system/config',
+ method: 'put',
+ data
+ });
+}
+
+/** 鏍规嵁Key淇敼鍊� */
+export function fetchUpdateConfigByKey(data: Api.System.ConfigOperateParams) {
+ return request<boolean>({
+ url: '/system/config/updateByKey',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎鍙傛暟閰嶇疆 */
+export function fetchBatchDeleteConfig(configIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/system/config/${configIds.join(',')}`,
+ method: 'delete'
+ });
+}
+
+/** 鏍规嵁Key鑾峰彇鍊� */
+export function fetchGetConfigByKey(configKey: string) {
+ return request<string>({
+ url: `/system/config/configKey/${configKey}`,
+ method: 'get'
+ });
+}
+
+/** 鍒锋柊缂撳瓨 */
+export function fetchRefreshCache() {
+ return request<boolean>({
+ url: `/system/config/refreshCache`,
+ method: 'delete'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/dept.ts b/ruoyi-plus-soybean/src/service/api/system/dept.ts
new file mode 100755
index 0000000..a678e56
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/dept.ts
@@ -0,0 +1,51 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇閮ㄩ棬鍒楄〃 */
+export function fetchGetDeptList(params?: Api.System.DeptSearchParams) {
+ return request<Api.System.Dept[]>({
+ url: '/system/dept/list',
+ method: 'get',
+ params
+ });
+}
+/** 鑾峰彇鎺掗櫎閮ㄩ棬鍒楄〃 */
+export function fetchGetExcludeDeptList(deptId?: CommonType.IdType) {
+ return request<Api.System.Dept[]>({
+ url: `/system/dept/list/exclude/${deptId}`,
+ method: 'get'
+ });
+}
+
+/** 鏂板閮ㄩ棬 */
+export function fetchCreateDept(data: Api.System.DeptOperateParams) {
+ return request<boolean>({
+ url: '/system/dept',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼閮ㄩ棬 */
+export function fetchUpdateDept(data: Api.System.DeptOperateParams) {
+ return request<boolean>({
+ url: '/system/dept',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎閮ㄩ棬 */
+export function fetchBatchDeleteDept(deptIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/system/dept/${deptIds.join(',')}`,
+ method: 'delete'
+ });
+}
+
+/** 鑾峰彇閮ㄩ棬閫夋嫨妗嗗垪琛� */
+export function fetchGetDeptSelect() {
+ return request<Api.System.Dept[]>({
+ url: '/system/dept/optionselect',
+ method: 'get'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/dict-data.ts b/ruoyi-plus-soybean/src/service/api/system/dict-data.ts
new file mode 100755
index 0000000..1a5d589
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/dict-data.ts
@@ -0,0 +1,36 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇瀛楀吀鏁版嵁鍒楄〃 */
+export function fetchGetDictDataList(params?: Api.System.DictDataSearchParams) {
+ return request<Api.System.DictDataList>({
+ url: '/system/dict/data/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鏂板瀛楀吀鏁版嵁 */
+export function fetchCreateDictData(data: Api.System.DictDataOperateParams) {
+ return request<boolean>({
+ url: '/system/dict/data',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼瀛楀吀鏁版嵁 */
+export function fetchUpdateDictData(data: Api.System.DictDataOperateParams) {
+ return request<boolean>({
+ url: '/system/dict/data',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎瀛楀吀鏁版嵁 */
+export function fetchBatchDeleteDictData(dictCodes: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/system/dict/data/${dictCodes.join(',')}`,
+ method: 'delete'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/dict.ts b/ruoyi-plus-soybean/src/service/api/system/dict.ts
new file mode 100755
index 0000000..5ff6b50
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/dict.ts
@@ -0,0 +1,59 @@
+import { request } from '@/service/request';
+
+/** 鏍规嵁瀛楀吀绫诲瀷鏌ヨ瀛楀吀鏁版嵁淇℃伅 */
+export function fetchGetDictDataByType(dictType: string) {
+ return request<Api.System.DictData[]>({
+ url: `/system/dict/data/type/${dictType}`,
+ method: 'get'
+ });
+}
+
+/** 鑾峰彇瀛楀吀閫夋嫨妗嗗垪琛� */
+export function fetchGetDictTypeOption() {
+ return request<Api.System.DictType[]>({
+ url: '/system/dict/type/optionselect',
+ method: 'get'
+ });
+}
+
+/** 鑾峰彇瀛楀吀绫诲瀷鍒楄〃 */
+export function fetchGetDictTypeList(params?: Api.System.DictTypeSearchParams) {
+ return request<Api.System.DictTypeList>({
+ url: '/system/dict/type/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鏂板瀛楀吀绫诲瀷 */
+export function fetchCreateDictType(data: Api.System.DictTypeOperateParams) {
+ return request<boolean>({
+ url: '/system/dict/type',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼瀛楀吀绫诲瀷 */
+export function fetchUpdateDictType(data: Api.System.DictTypeOperateParams) {
+ return request<boolean>({
+ url: '/system/dict/type',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎瀛楀吀绫诲瀷 */
+export function fetchBatchDeleteDictType(dictIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/system/dict/type/${dictIds.join(',')}`,
+ method: 'delete'
+ });
+}
+/** 鍒锋柊缂撳瓨 */
+export function fetchRefreshCache() {
+ return request<boolean>({
+ url: `/system/dict/type/refreshCache`,
+ method: 'delete'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/index.ts b/ruoyi-plus-soybean/src/service/api/system/index.ts
new file mode 100755
index 0000000..ac12912
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/index.ts
@@ -0,0 +1,8 @@
+export * from './menu';
+export * from './dict';
+export * from './dict-data';
+export * from './user';
+export * from './dept';
+export * from './role';
+export * from './post';
+export * from './social';
diff --git a/ruoyi-plus-soybean/src/service/api/system/menu.ts b/ruoyi-plus-soybean/src/service/api/system/menu.ts
new file mode 100755
index 0000000..b1f9cd0
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/menu.ts
@@ -0,0 +1,69 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇鑿滃崟鍒楄〃 */
+export function fetchGetMenuList(params?: Api.System.MenuSearchParams, signal?: AbortSignal) {
+ return request<Api.System.MenuList>({
+ url: '/system/menu/list',
+ method: 'get',
+ params,
+ signal
+ });
+}
+
+/** 鏂板鑿滃崟 */
+export function fetchCreateMenu(data: Api.System.MenuOperateParams) {
+ return request<boolean>({
+ url: '/system/menu',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼鑿滃崟 */
+export function fetchUpdateMenu(data: Api.System.MenuOperateParams) {
+ return request<boolean>({
+ url: '/system/menu',
+ method: 'put',
+ data
+ });
+}
+
+/** 鍒犻櫎鑿滃崟 */
+export function fetchDeleteMenu(menuId: CommonType.IdType) {
+ return request<boolean>({
+ url: `/system/menu/${menuId}`,
+ method: 'delete'
+ });
+}
+
+/** 鑾峰彇鑿滃崟鏍� */
+export function fetchGetMenuTreeSelect() {
+ return request<Api.System.MenuList>({
+ url: 'system/menu/treeselect',
+ method: 'get'
+ });
+}
+
+/** 鑾峰彇瑙掕壊鑿滃崟鏉冮檺 */
+export function fetchGetRoleMenuTreeSelect(roleId: CommonType.IdType) {
+ return request<Api.System.RoleMenuTreeSelect>({
+ url: `/system/menu/roleMenuTreeselect/${roleId}`,
+ method: 'get'
+ });
+}
+
+/** 鑾峰彇绉熸埛濂楅鍏宠仈鑿滃崟 */
+export function fetchGetTenantPackageMenuTreeSelect(packageId: CommonType.IdType) {
+ return request<Api.System.TenantPackageMenuTreeSelect>({
+ url: `/system/menu/tenantPackageMenuTreeselect/${packageId}`,
+ method: 'get'
+ });
+}
+
+/** 绾ц仈鍒犻櫎鑿滃崟 */
+export function fetchCascadeDeleteMenu(menuIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/system/menu/cascade/${menuIds.join(',')}`,
+ method: 'delete'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/notice.ts b/ruoyi-plus-soybean/src/service/api/system/notice.ts
new file mode 100755
index 0000000..f3931d4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/notice.ts
@@ -0,0 +1,36 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇閫氱煡鍏憡鍒楄〃 */
+export function fetchGetNoticeList(params?: Api.System.NoticeSearchParams) {
+ return request<Api.System.NoticeList>({
+ url: '/system/notice/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鏂板閫氱煡鍏憡 */
+export function fetchCreateNotice(data: Api.System.NoticeOperateParams) {
+ return request<boolean>({
+ url: '/system/notice',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼閫氱煡鍏憡 */
+export function fetchUpdateNotice(data: Api.System.NoticeOperateParams) {
+ return request<boolean>({
+ url: '/system/notice',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎閫氱煡鍏憡 */
+export function fetchBatchDeleteNotice(noticeIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/system/notice/${noticeIds.join(',')}`,
+ method: 'delete'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/oss-config.ts b/ruoyi-plus-soybean/src/service/api/system/oss-config.ts
new file mode 100755
index 0000000..a59740f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/oss-config.ts
@@ -0,0 +1,45 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇OSS閰嶇疆鍒楄〃 */
+export function fetchGetOssConfigList(params?: Api.System.OssConfigSearchParams) {
+ return request<Api.System.OssConfigList>({
+ url: '/resource/oss/config/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鏂板OSS閰嶇疆 */
+export function fetchCreateOssConfig(data: Api.System.OssConfigOperateParams) {
+ return request<boolean>({
+ url: '/resource/oss/config',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼OSS閰嶇疆 */
+export function fetchUpdateOssConfig(data: Api.System.OssConfigOperateParams) {
+ return request<boolean>({
+ url: '/resource/oss/config',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎OSS閰嶇疆 */
+export function fetchBatchDeleteOssConfig(ossConfigIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/resource/oss/config/${ossConfigIds.join(',')}`,
+ method: 'delete'
+ });
+}
+
+/** 鐘舵�佷慨鏀� */
+export function fetchUpdateOssConfigStatus(data: Api.System.OssConfigOperateParams) {
+ return request<boolean>({
+ url: '/resource/oss/config/changeStatus',
+ method: 'put',
+ data
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/oss.ts b/ruoyi-plus-soybean/src/service/api/system/oss.ts
new file mode 100755
index 0000000..cecf098
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/oss.ts
@@ -0,0 +1,40 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇鏂囦欢绠$悊鍒楄〃 */
+export function fetchGetOssList(params?: Api.System.OssSearchParams) {
+ return request<Api.System.OssList>({
+ url: '/resource/oss/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鎵归噺鍒犻櫎鏂囦欢绠$悊 */
+export function fetchBatchDeleteOss(ossIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/resource/oss/${ossIds.join(',')}`,
+ method: 'delete'
+ });
+}
+
+/** 鏌ヨOSS瀵硅薄鍩轰簬id涓� */
+export function fetchGetOssListByIds(ossIds: CommonType.IdType[]) {
+ return request<Api.System.Oss[]>({
+ url: `/resource/oss/listByIds/${ossIds.join(',')}`,
+ method: 'get'
+ });
+}
+
+/** 涓婁紶鏂囦欢 */
+export function fetchUploadFile(file: File) {
+ const formData = new FormData();
+ formData.append('file', file);
+ return request<Api.System.Oss>({
+ url: '/resource/oss/upload',
+ method: 'post',
+ data: formData,
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/post.ts b/ruoyi-plus-soybean/src/service/api/system/post.ts
new file mode 100755
index 0000000..88c3764
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/post.ts
@@ -0,0 +1,52 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇宀椾綅淇℃伅鍒楄〃 */
+export function fetchGetPostList(params?: Api.System.PostSearchParams) {
+ return request<Api.System.PostList>({
+ url: '/system/post/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鏂板宀椾綅淇℃伅 */
+export function fetchCreatePost(data: Api.System.PostOperateParams) {
+ return request<boolean>({
+ url: '/system/post',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼宀椾綅淇℃伅 */
+export function fetchUpdatePost(data: Api.System.PostOperateParams) {
+ return request<boolean>({
+ url: '/system/post',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎宀椾綅淇℃伅 */
+export function fetchBatchDeletePost(postIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/system/post/${postIds.join(',')}`,
+ method: 'delete'
+ });
+}
+
+/** 鑾峰彇宀椾綅閫夋嫨妗嗗垪琛� */
+export function fetchGetPostSelect(deptId?: CommonType.IdType, postIds?: CommonType.IdType[]) {
+ return request<Api.System.Post[]>({
+ url: '/system/post/optionselect',
+ method: 'get',
+ params: { postIds, deptId }
+ });
+}
+/** 鑾峰彇閮ㄩ棬閫夋嫨妗嗗垪琛� */
+export function fetchGetPostDeptSelect() {
+ return request<Api.Common.CommonTreeRecord>({
+ url: '/system/post/deptTree',
+ method: 'get'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/role.ts b/ruoyi-plus-soybean/src/service/api/system/role.ts
new file mode 100755
index 0000000..9aea88d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/role.ts
@@ -0,0 +1,98 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇瑙掕壊淇℃伅鍒楄〃 */
+export function fetchGetRoleList(params?: Api.System.RoleSearchParams) {
+ return request<Api.System.RoleList>({
+ url: '/system/role/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鏂板瑙掕壊淇℃伅 */
+export function fetchCreateRole(data: Api.System.RoleOperateParams) {
+ return request<boolean>({
+ url: '/system/role',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼瑙掕壊淇℃伅 */
+export function fetchUpdateRole(data: Api.System.RoleOperateParams) {
+ return request<boolean>({
+ url: '/system/role',
+ method: 'put',
+ data
+ });
+}
+
+/** 淇敼瑙掕壊鐘舵�� */
+export function fetchUpdateRoleStatus(data: Api.System.RoleOperateParams) {
+ return request<boolean>({
+ url: '/system/role/changeStatus',
+ method: 'put',
+ data
+ });
+}
+
+/** 淇敼瑙掕壊鏁版嵁鏉冮檺 */
+export function fetchUpdateRoleDataScope(data: Api.System.RoleOperateParams) {
+ return request<boolean>({
+ url: '/system/role/dataScope',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎瑙掕壊淇℃伅 */
+export function fetchBatchDeleteRole(roleIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/system/role/${roleIds.join(',')}`,
+ method: 'delete'
+ });
+}
+
+/** 鑾峰彇瑙掕壊閫夋嫨妗嗗垪琛� */
+export function fetchGetRoleSelect(roleIds?: CommonType.IdType[]) {
+ return request<Api.System.Role[]>({
+ url: '/system/role/optionselect',
+ method: 'get',
+ params: { roleIds }
+ });
+}
+
+/** 鑾峰彇瀵瑰簲瑙掕壊閮ㄩ棬鏍戝垪琛� */
+export function fetchGetRoleDeptTreeSelect(roleId: CommonType.IdType) {
+ return request<Api.System.RoleDeptTreeSelect>({
+ url: `/system/role/deptTree/${roleId}`,
+ method: 'get'
+ });
+}
+
+/** 鑾峰彇瀵瑰簲瑙掕壊鐢ㄦ埛鍒楄〃 */
+export function fetchGetRoleUserList(params: Api.System.UserSearchParams) {
+ return request<Api.System.UserList>({
+ url: `/system/role/authUser/allocatedList`,
+ method: 'get',
+ params
+ });
+}
+
+/** 鎵归噺閫夋嫨鐢ㄦ埛鎺堟潈 */
+export function fetchUpdateRoleAuthUser(roleId: CommonType.IdType, userIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: '/system/role/authUser/selectAll',
+ method: 'put',
+ params: { roleId, userIds: userIds.join(',') }
+ });
+}
+
+/** 鎵归噺鍙栨秷鐢ㄦ埛鎺堟潈 */
+export function fetchUpdateRoleAuthUserCancel(roleId: CommonType.IdType, userIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: '/system/role/authUser/cancelAll',
+ method: 'put',
+ params: { roleId, userIds: userIds.join(',') }
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/social.ts b/ruoyi-plus-soybean/src/service/api/system/social.ts
new file mode 100755
index 0000000..5280085
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/social.ts
@@ -0,0 +1,29 @@
+import { request } from '../../request';
+
+/** 鑾峰彇璺宠浆URL */
+export function fetchSocialAuthBinding(source: Api.System.SocialSource, tenantId: string = '000000') {
+ return request<string>({
+ url: `/auth/binding/${source}`,
+ method: 'get',
+ params: {
+ tenantId,
+ domain: window.location.host
+ }
+ });
+}
+
+/** 瑙g粦璐︽埛 */
+export function fetchSocialAuthUnbinding(socialId: CommonType.IdType) {
+ return request<string>({
+ url: `/auth/unlock/${socialId}`,
+ method: 'delete'
+ });
+}
+
+/** 鏌ヨ绀句細鍖栧叧绯诲垪琛� */
+export function fetchSocialList() {
+ return request<Api.System.Social[]>({
+ url: '/system/social/list',
+ method: 'get'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/tenant-package.ts b/ruoyi-plus-soybean/src/service/api/system/tenant-package.ts
new file mode 100755
index 0000000..d99a057
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/tenant-package.ts
@@ -0,0 +1,52 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇绉熸埛濂楅鍒楄〃 */
+export function fetchGetTenantPackageList(params?: Api.System.TenantPackageSearchParams) {
+ return request<Api.System.TenantPackageList>({
+ url: '/system/tenant/package/list',
+ method: 'get',
+ params
+ });
+}
+/** 鑾峰彇绉熸埛涓嬫媺鍒楄〃 */
+export function fetchGetTenantPackageSelectList() {
+ return request<Api.System.TenantPackageSelectList[]>({
+ url: '/system/tenant/package/selectList',
+ method: 'get'
+ });
+}
+
+/** 鏂板绉熸埛濂楅 */
+export function fetchCreateTenantPackage(data: Api.System.TenantPackageOperateParams) {
+ return request<boolean>({
+ url: '/system/tenant/package',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼绉熸埛濂楅 */
+export function fetchUpdateTenantPackage(data: Api.System.TenantPackageOperateParams) {
+ return request<boolean>({
+ url: '/system/tenant/package',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎绉熸埛濂楅 */
+export function fetchBatchDeleteTenantPackage(packageIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/system/tenant/package/${packageIds.join(',')}`,
+ method: 'delete'
+ });
+}
+
+/** 淇敼绉熸埛濂楅鐘舵�� */
+export function fetchUpdateTenantPackageStatus(data: Api.System.TenantPackageOperateParams) {
+ return request<boolean>({
+ url: `/system/tenant/package/changeStatus`,
+ method: 'put',
+ data
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/tenant.ts b/ruoyi-plus-soybean/src/service/api/system/tenant.ts
new file mode 100755
index 0000000..33baa5f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/tenant.ts
@@ -0,0 +1,81 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇绉熸埛鍒楄〃 */
+export function fetchGetTenantList(params?: Api.System.TenantSearchParams) {
+ return request<Api.System.TenantList>({
+ url: '/system/tenant/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鏂板绉熸埛 */
+export function fetchCreateTenant(data: Api.System.TenantOperateParams) {
+ return request<boolean>({
+ url: '/system/tenant',
+ method: 'post',
+ headers: {
+ isEncrypt: true,
+ repeatSubmit: false
+ },
+ data
+ });
+}
+
+/** 淇敼绉熸埛 */
+export function fetchUpdateTenant(data: Api.System.TenantOperateParams) {
+ return request<boolean>({
+ url: '/system/tenant',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎绉熸埛 */
+export function fetchBatchDeleteTenant(ids: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/system/tenant/${ids.join(',')}`,
+ method: 'delete'
+ });
+}
+
+/** 鍚屾绉熸埛瀛楀吀 */
+export function fetchSyncTenantDict() {
+ return request<boolean>({
+ url: '/system/tenant/syncTenantDict',
+ method: 'get'
+ });
+}
+
+/** 鍚屾绉熸埛濂楅 */
+export function fetchSyncTenantPackage(params: Api.System.TenantPackageSyncParams) {
+ return request<boolean>({
+ url: '/system/tenant/syncTenantPackage',
+ method: 'get',
+ params
+ });
+}
+
+/** 鍚屾绉熸埛鍙傛暟閰嶇疆 */
+export function fetchSyncTenantConfig() {
+ return request<boolean>({
+ url: '/system/tenant/syncTenantConfig',
+ method: 'get'
+ });
+}
+
+/** 鍔ㄦ�佸垏鎹㈢鎴� */
+export function fetchChangeTenant(tenantId: CommonType.IdType) {
+ return request<boolean>({
+ url: `/system/tenant/dynamic/${tenantId}`,
+ method: 'get'
+ });
+}
+
+/** 娓呯┖绉熸埛 */
+export function fetchClearTenant() {
+ return request<boolean>({
+ url: '/system/tenant/dynamic/clear',
+ method: 'get'
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/system/user.ts b/ruoyi-plus-soybean/src/service/api/system/user.ts
new file mode 100755
index 0000000..df1c11d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/system/user.ts
@@ -0,0 +1,136 @@
+import { request } from '@/service/request';
+
+/** 鑾峰彇鐢ㄦ埛淇℃伅鍒楄〃 */
+export function fetchGetUserList(params?: Api.System.UserSearchParams) {
+ return request<Api.System.UserList>({
+ url: '/system/user/list',
+ method: 'get',
+ params
+ });
+}
+/** 鑾峰彇閮ㄩ棬鐢ㄦ埛淇℃伅鍒楄〃 */
+export function fetchGetDeptUserList(deptId: CommonType.IdType) {
+ return request<Api.System.User[]>({
+ url: `/system/user/list/dept/${deptId}`,
+ method: 'get'
+ });
+}
+
+/** 鏂板鐢ㄦ埛淇℃伅 */
+export function fetchCreateUser(data: Api.System.UserOperateParams) {
+ return request<boolean>({
+ url: '/system/user',
+ method: 'post',
+ data
+ });
+}
+
+/** 淇敼鐢ㄦ埛淇℃伅 */
+export function fetchUpdateUser(data: Api.System.UserOperateParams) {
+ return request<boolean>({
+ url: '/system/user',
+ method: 'put',
+ data
+ });
+}
+
+/** 鑾峰彇鐢ㄦ埛閫夋嫨妗嗗垪琛� */
+export function fetchGetUserSelect() {
+ return request<Api.System.User[]>({
+ url: '/system/user/optionselect',
+ method: 'get'
+ });
+}
+
+/** 淇敼鐢ㄦ埛鐘舵�� */
+export function fetchUpdateUserStatus(data: Api.System.UserOperateParams) {
+ return request<boolean>({
+ url: '/system/user/changeStatus',
+ method: 'put',
+ data
+ });
+}
+
+/** 鎵归噺鍒犻櫎鐢ㄦ埛淇℃伅 */
+export function fetchBatchDeleteUser(userIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: `/system/user/${userIds.join(',')}`,
+ method: 'delete'
+ });
+}
+
+/** 鏍规嵁鐢ㄦ埛缂栧彿鑾峰彇璇︾粏淇℃伅 */
+export function fetchGetUserInfo(userId?: CommonType.IdType) {
+ return request<Api.System.UserInfo>({
+ url: `/system/user/${userId}`,
+ method: 'get'
+ });
+}
+
+/** 鑾峰彇閮ㄩ棬鏍戝垪琛� */
+export function fetchGetDeptTree() {
+ return request<Api.Common.CommonTreeRecord>({
+ url: '/system/user/deptTree',
+ method: 'get'
+ });
+}
+
+/** 閲嶇疆鐢ㄦ埛瀵嗙爜 */
+export function fetchResetUserPassword(userId: CommonType.IdType, password: string) {
+ return request<boolean>({
+ url: '/system/user/resetPwd',
+ method: 'put',
+ headers: {
+ isEncrypt: true,
+ repeatSubmit: false
+ },
+ data: { userId, password }
+ });
+}
+
+/** 鏍规嵁鐢ㄦ埛缂栧彿鑾峰彇鎺堟潈瑙掕壊 */
+export function fetchGetAuthRole(userId: CommonType.IdType) {
+ return request<Api.System.AuthRole>({
+ url: `/system/user/authRole/${userId}`,
+ method: 'get'
+ });
+}
+
+/** 鐢ㄦ埛鎺堟潈瑙掕壊 */
+export function fetchAuthUserRole(userId: CommonType.IdType, roleIds: CommonType.IdType[]) {
+ return request<boolean>({
+ url: '/system/user/authRole',
+ method: 'put',
+ data: { userId, roleIds }
+ });
+}
+
+/** 淇敼鐢ㄦ埛鍩烘湰淇℃伅 */
+export function fetchUpdateUserProfile(data: Api.System.UserProfileOperateParams) {
+ return request<boolean>({
+ url: '/system/user/profile',
+ method: 'put',
+ data
+ });
+}
+
+/** 淇敼鐢ㄦ埛瀵嗙爜 */
+export function fetchUpdateUserPassword(data: Api.System.UserPasswordOperateParams) {
+ return request<boolean>({
+ url: '/system/user/profile/updatePwd',
+ method: 'put',
+ headers: {
+ isEncrypt: true
+ },
+ data
+ });
+}
+
+/** 淇敼鐢ㄦ埛澶村儚 */
+export function fetchUpdateUserAvatar(formData: FormData) {
+ return request<boolean>({
+ url: '/system/user/profile/avatar',
+ method: 'post',
+ data: formData
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/tool/gen.ts b/ruoyi-plus-soybean/src/service/api/tool/gen.ts
new file mode 100755
index 0000000..588d655
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/tool/gen.ts
@@ -0,0 +1,101 @@
+import { request } from '@/service/request';
+
+/** 鏌ヨ浠g爜鐢熸垚鍒楄〃 */
+export function fetchGetGenTableList(params?: Api.Tool.GenTableSearchParams) {
+ return request<Api.Tool.GenTableList>({
+ url: '/tool/gen/list',
+ method: 'get',
+ params
+ });
+}
+
+/**
+ * 瀵煎叆琛ㄧ粨鏋�
+ *
+ * @param tables 琛ㄥ悕绉�
+ * @param dataName 鏁版嵁婧愬悕绉�
+ */
+export function fetchImportGenTable(tables: string[], dataName: string) {
+ return request<boolean>({
+ url: '/tool/gen/importTable',
+ method: 'post',
+ params: { tables: tables.join(','), dataName }
+ });
+}
+
+/** 淇敼浠g爜鐢熸垚 */
+export function fetchUpdateGenTable(data: Api.Tool.GenTable) {
+ return request<boolean>({
+ url: '/tool/gen',
+ method: 'put',
+ data
+ });
+}
+
+/** 鑾峰彇浠g爜鐢熸垚淇℃伅 */
+export function fetchGetGenTableInfo(tableId: CommonType.IdType) {
+ return request<Api.Tool.GenTableInfo>({
+ url: `/tool/gen/${tableId}`,
+ method: 'get'
+ });
+}
+
+/** 鎵归噺鍒犻櫎浠g爜鐢熸垚 */
+export function fetchBatchDeleteGenTable(tableIds: CommonType.IdType[]) {
+ const ids = tableIds.join(',');
+ return request<boolean>({
+ url: `/tool/gen/${ids}`,
+ method: 'delete'
+ });
+}
+
+/** 鏌ヨ鏁版嵁婧愬悕绉板垪琛� */
+export function fetchGetGenDataNames() {
+ return request<string[]>({
+ url: '/tool/gen/getDataNames',
+ method: 'get'
+ });
+}
+
+/** 鏌ヨ鏁版嵁搴撳垪琛� */
+export function fetchGetGenDbList(params?: Api.Tool.GenTableDbSearchParams) {
+ return request<Api.Tool.GenTableDbList>({
+ url: '/tool/gen/db/list',
+ method: 'get',
+ params
+ });
+}
+
+/** 鍚屾鏁版嵁搴� */
+export function fetchSynchGenDbList(tableId: CommonType.IdType) {
+ return request<Api.Tool.GenTableDbList>({
+ url: `/tool/gen/synchDb/${tableId}`,
+ method: 'get'
+ });
+}
+
+/** 棰勮浠g爜 */
+export function fetchGetGenPreview(tableId: CommonType.IdType) {
+ return request<Api.Tool.GenTablePreview>({
+ url: `/tool/gen/preview/${tableId}`,
+ method: 'get'
+ });
+}
+
+/** 鐢熸垚浠g爜锛堣嚜瀹氫箟璺緞锛� */
+export function fetchGenCode(tableId: CommonType.IdType) {
+ return request<Api.Tool.GenTableDbList>({
+ url: `/tool/gen/genCode/${tableId}`,
+ method: 'get'
+ });
+}
+
+/** 鎵归噺鐢熸垚浠g爜 */
+export function fetchBatchGenCode(tableIds: CommonType.IdType[]) {
+ const tableIdStr = tableIds.join(',');
+ return request<Api.Tool.GenTableDbList>({
+ url: '/tool/gen/genCode/',
+ method: 'get',
+ params: { tableIdStr }
+ });
+}
diff --git a/ruoyi-plus-soybean/src/service/api/tool/index.ts b/ruoyi-plus-soybean/src/service/api/tool/index.ts
new file mode 100755
index 0000000..8dfed38
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/api/tool/index.ts
@@ -0,0 +1 @@
+export * from './gen';
diff --git a/ruoyi-plus-soybean/src/service/request/index.ts b/ruoyi-plus-soybean/src/service/request/index.ts
new file mode 100755
index 0000000..e7c35a8
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/request/index.ts
@@ -0,0 +1,225 @@
+import type { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
+import { BACKEND_ERROR_CODE, createFlatRequest } from '@sa/axios';
+import { useAuthStore } from '@/store/modules/auth';
+import { localStg, sessionStg } from '@/utils/storage';
+import { getServiceBaseURL } from '@/utils/service';
+import { decryptBase64, decryptWithAes, encryptBase64, encryptWithAes, generateAesKey } from '@/utils/crypto';
+import { decrypt, encrypt } from '@/utils/jsencrypt';
+import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared';
+import type { RequestInstanceState } from './type';
+
+const encryptHeader = import.meta.env.VITE_HEADER_FLAG || 'encrypt-key';
+const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
+const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
+
+export const request = createFlatRequest(
+ {
+ baseURL,
+ 'axios-retry': {
+ retries: 0
+ }
+ },
+ {
+ defaultState: {
+ errMsgStack: [],
+ refreshTokenPromise: null
+ } as RequestInstanceState,
+ transform(response: AxiosResponse<App.Service.Response<any>>) {
+ // 浜岃繘鍒舵暟鎹垯鐩存帴杩斿洖
+ if (response.request.responseType === 'blob' || response.request.responseType === 'arraybuffer') {
+ return response.data;
+ }
+
+ if (response.data.rows) {
+ return response.data;
+ }
+
+ return response.data.data;
+ },
+ async onRequest(config) {
+ const isToken = config.headers?.isToken === false;
+ // set token
+ const token = localStg.get('token');
+ if (token && !isToken) {
+ const Authorization = getAuthorization();
+ Object.assign(config.headers, { Authorization });
+ }
+
+ // 瀹㈡埛绔� ID
+ config.headers.Clientid = import.meta.env.VITE_APP_CLIENT_ID;
+ // 瀵瑰簲鍥介檯鍖栬祫婧愭枃浠跺悗缂�
+ config.headers['Content-Language'] = (localStg.get('lang') || 'zh-CN').replace('-', '_');
+
+ handleRepeatSubmit(config);
+
+ handleEncrypt(config);
+
+ // FormData鏁版嵁鍘昏姹傚ごContent-Type
+ if (config.data instanceof FormData) {
+ delete config.headers['Content-Type'];
+ }
+
+ return config;
+ },
+ isBackendSuccess(response) {
+ // when the backend response code is "0000"(default), it means the request is success
+ // to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
+ if (import.meta.env.VITE_APP_ENCRYPT === 'Y' && response.headers[encryptHeader]) {
+ const keyStr = response.headers[encryptHeader];
+ const data = String(response.data);
+ const base64Str = decrypt(keyStr);
+ const aesKey = decryptBase64(base64Str.toString());
+ const decryptData = decryptWithAes(data, aesKey);
+ response.data = JSON.parse(decryptData);
+ }
+ return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
+ },
+ async onBackendFail(response, instance) {
+ const authStore = useAuthStore();
+ const responseCode = String(response.data.code);
+
+ function handleLogout() {
+ authStore.resetStore();
+ }
+
+ function logoutAndCleanup() {
+ handleLogout();
+ window.removeEventListener('beforeunload', handleLogout);
+
+ request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.msg);
+ }
+
+ const isLogin = Boolean(localStg.get('token'));
+
+ // when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
+ const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
+ if (logoutCodes.includes(responseCode) && !isLogin) {
+ logoutAndCleanup();
+ return null;
+ }
+
+ // when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
+ const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
+ if (modalLogoutCodes.includes(responseCode) && isLogin) {
+ const isExist = request.state.errMsgStack?.includes(response.data.msg);
+ if (isExist) {
+ return null;
+ }
+ if (window.location.pathname?.startsWith('/login')) {
+ logoutAndCleanup();
+ return null;
+ }
+
+ request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];
+
+ window.$dialog?.warning({
+ title: '绯荤粺鎻愮ず',
+ content: '鐧诲綍鐘舵�佸凡杩囨湡锛岃閲嶆柊鐧诲綍',
+ positiveText: '閲嶆柊鐧诲綍',
+ maskClosable: false,
+ closeOnEsc: false,
+ onAfterEnter() {
+ // prevent the user from refreshing the page
+ window.addEventListener('beforeunload', handleLogout);
+ },
+ onPositiveClick() {
+ logoutAndCleanup();
+ },
+ onClose() {
+ logoutAndCleanup();
+ }
+ });
+ request.cancelAllRequest();
+ return null;
+ }
+
+ // when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token
+ // the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes`
+ const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
+ if (expiredTokenCodes.includes(responseCode)) {
+ const success = await handleExpiredRequest(request.state);
+ if (success) {
+ const Authorization = getAuthorization();
+ Object.assign(response.config.headers, { Authorization });
+
+ return instance.request(response.config) as Promise<AxiosResponse>;
+ }
+ }
+
+ return null;
+ },
+ onError(error) {
+ // when the request is fail, you can show error message
+
+ let message = error.message;
+ let backendErrorCode = '';
+
+ // get backend error message and code
+ if (error.code === BACKEND_ERROR_CODE) {
+ message = error.response?.data?.msg || message;
+ backendErrorCode = String(error.response?.data?.code || '');
+ }
+
+ // the error message is displayed in the modal
+ const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
+ if (modalLogoutCodes.includes(backendErrorCode)) {
+ return;
+ }
+
+ // when the token is expired, refresh token and retry request, so no need to show error message
+ const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
+ if (expiredTokenCodes.includes(backendErrorCode)) {
+ return;
+ }
+
+ showErrorMsg(request.state, message);
+ }
+ }
+);
+
+function handleRepeatSubmit(config: InternalAxiosRequestConfig) {
+ // 鏄惁闇�瑕侀槻姝㈡暟鎹噸澶嶆彁浜�
+ const isRepeatSubmit = config.headers?.repeatSubmit === false;
+
+ if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
+ const requestObj = {
+ url: config.url!,
+ data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
+ time: new Date().getTime()
+ };
+ const sessionObj = sessionStg.get('sessionObj');
+ if (!sessionObj) {
+ sessionStg.set('sessionObj', requestObj);
+ } else {
+ const s_url = sessionObj.url; // 璇锋眰鍦板潃
+ const s_data = sessionObj.data; // 璇锋眰鏁版嵁
+ const s_time = sessionObj.time; // 璇锋眰鏃堕棿
+ const interval = 500; // 闂撮殧鏃堕棿(ms)锛屽皬浜庢鏃堕棿瑙嗕负閲嶅鎻愪氦
+ if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
+ const message = '鏁版嵁姝e湪澶勭悊锛岃鍕块噸澶嶆彁浜�';
+ // eslint-disable-next-line no-console
+ console.warn(`[${s_url}]: ${message}`);
+ throw new Error(message);
+ }
+ sessionStg.set('sessionObj', requestObj);
+ }
+ }
+}
+
+function handleEncrypt(config: InternalAxiosRequestConfig) {
+ // 鏄惁闇�瑕佸姞瀵�
+ const isEncrypt = config.headers?.isEncrypt === 'true';
+
+ if (import.meta.env.VITE_APP_ENCRYPT === 'Y') {
+ // 褰撳紑鍚弬鏁板姞瀵�
+ if (isEncrypt && (config.method === 'post' || config.method === 'put')) {
+ // 鐢熸垚涓�涓� AES 瀵嗛挜
+ const aesKey = generateAesKey();
+ config.headers[encryptHeader] = encrypt(encryptBase64(aesKey));
+ config.data =
+ typeof config.data === 'object'
+ ? encryptWithAes(JSON.stringify(config.data), aesKey)
+ : encryptWithAes(config.data, aesKey);
+ }
+ }
+}
diff --git a/ruoyi-plus-soybean/src/service/request/shared.ts b/ruoyi-plus-soybean/src/service/request/shared.ts
new file mode 100755
index 0000000..d55cddd
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/request/shared.ts
@@ -0,0 +1,63 @@
+import { useAuthStore } from '@/store/modules/auth';
+import { localStg } from '@/utils/storage';
+import type { RequestInstanceState } from './type';
+
+export function getAuthorization() {
+ const token = localStg.get('token');
+ const Authorization = token ? `Bearer ${token}` : null;
+
+ return Authorization;
+}
+
+/** refresh token */
+async function handleRefreshToken() {
+ const { resetStore } = useAuthStore();
+
+ // const rToken = localStg.get('refreshToken') || '';
+ // const { error, data } = await fetchRefreshToken(rToken);
+ // if (!error) {
+ // localStg.set('token', data.token);
+ // localStg.set('refreshToken', data.refreshToken);
+ // return true;
+ // }
+
+ resetStore();
+
+ return false;
+}
+
+export async function handleExpiredRequest(state: RequestInstanceState) {
+ if (!state.refreshTokenPromise) {
+ state.refreshTokenPromise = handleRefreshToken();
+ }
+
+ const success = await state.refreshTokenPromise;
+
+ setTimeout(() => {
+ state.refreshTokenPromise = null;
+ }, 1000);
+
+ return success;
+}
+
+export function showErrorMsg(state: RequestInstanceState, message: string) {
+ if (!state.errMsgStack?.length) {
+ state.errMsgStack = [];
+ }
+
+ const isExist = state.errMsgStack.includes(message);
+
+ if (!isExist) {
+ state.errMsgStack.push(message);
+
+ window.$message?.error(message, {
+ onLeave: () => {
+ state.errMsgStack = state.errMsgStack.filter(msg => msg !== message);
+
+ setTimeout(() => {
+ state.errMsgStack = [];
+ }, 5000);
+ }
+ });
+ }
+}
diff --git a/ruoyi-plus-soybean/src/service/request/type.ts b/ruoyi-plus-soybean/src/service/request/type.ts
new file mode 100755
index 0000000..f72b58a
--- /dev/null
+++ b/ruoyi-plus-soybean/src/service/request/type.ts
@@ -0,0 +1,7 @@
+export interface RequestInstanceState {
+ /** the promise of refreshing token */
+ refreshTokenPromise: Promise<boolean> | null;
+ /** the request error message stack */
+ errMsgStack: string[];
+ [key: string]: unknown;
+}
diff --git a/ruoyi-plus-soybean/src/store/index.ts b/ruoyi-plus-soybean/src/store/index.ts
new file mode 100755
index 0000000..df57302
--- /dev/null
+++ b/ruoyi-plus-soybean/src/store/index.ts
@@ -0,0 +1,12 @@
+import type { App } from 'vue';
+import { createPinia } from 'pinia';
+import { resetSetupStore } from './plugins';
+
+/** Setup Vue store plugin pinia */
+export function setupStore(app: App) {
+ const store = createPinia();
+
+ store.use(resetSetupStore);
+
+ app.use(store);
+}
diff --git a/ruoyi-plus-soybean/src/store/modules/app/index.ts b/ruoyi-plus-soybean/src/store/modules/app/index.ts
new file mode 100755
index 0000000..62af82f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/store/modules/app/index.ts
@@ -0,0 +1,166 @@
+import { effectScope, nextTick, onScopeDispose, ref, watch } from 'vue';
+import { breakpointsTailwind, useBreakpoints, useEventListener, useTitle } from '@vueuse/core';
+import { defineStore } from 'pinia';
+import { useBoolean } from '@sa/hooks';
+import { router } from '@/router';
+import { localStg } from '@/utils/storage';
+import { SetupStoreId } from '@/enum';
+import { $t, setLocale } from '@/locales';
+import { setDayjsLocale } from '@/locales/dayjs';
+import { useRouteStore } from '../route';
+import { useTabStore } from '../tab';
+import { useThemeStore } from '../theme';
+
+export const useAppStore = defineStore(SetupStoreId.App, () => {
+ const themeStore = useThemeStore();
+ const routeStore = useRouteStore();
+ const tabStore = useTabStore();
+ const scope = effectScope();
+ const breakpoints = useBreakpoints(breakpointsTailwind);
+ const { bool: themeDrawerVisible, setTrue: openThemeDrawer, setFalse: closeThemeDrawer } = useBoolean();
+ const { bool: reloadFlag, setBool: setReloadFlag } = useBoolean(true);
+ const { bool: fullContent, toggle: toggleFullContent } = useBoolean();
+ const { bool: contentXScrollable, setBool: setContentXScrollable } = useBoolean();
+ const { bool: siderCollapse, setBool: setSiderCollapse, toggle: toggleSiderCollapse } = useBoolean();
+ const {
+ bool: mixSiderFixed,
+ setBool: setMixSiderFixed,
+ toggle: toggleMixSiderFixed
+ } = useBoolean(localStg.get('mixSiderFixed') === 'Y');
+
+ /** Is mobile layout */
+ const isMobile = breakpoints.smaller('sm');
+
+ /**
+ * Reload page
+ *
+ * @param duration Duration time
+ */
+ async function reloadPage(duration = 300) {
+ setReloadFlag(false);
+
+ const d = themeStore.page.animate ? duration : 40;
+
+ await new Promise(resolve => {
+ setTimeout(resolve, d);
+ });
+
+ setReloadFlag(true);
+ routeStore.resetRouteCache();
+ }
+
+ const locale = ref<App.I18n.LangType>(localStg.get('lang') || 'zh-CN');
+
+ const localeOptions: App.I18n.LangOption[] = [
+ {
+ label: '涓枃',
+ key: 'zh-CN'
+ },
+ {
+ label: 'English',
+ key: 'en-US'
+ }
+ ];
+
+ function changeLocale(lang: App.I18n.LangType) {
+ locale.value = lang;
+ setLocale(lang);
+ localStg.set('lang', lang);
+ }
+
+ /** Update document title by locale */
+ function updateDocumentTitleByLocale() {
+ const { i18nKey, title } = router.currentRoute.value.meta;
+
+ const documentTitle = i18nKey ? $t(i18nKey) : title;
+
+ useTitle(documentTitle);
+ }
+
+ function init() {
+ setDayjsLocale(locale.value);
+ }
+
+ // watch store
+ scope.run(() => {
+ // watch isMobile, if is mobile, collapse sider
+ watch(
+ isMobile,
+ newValue => {
+ if (newValue) {
+ // backup theme setting before is mobile
+ localStg.set('backupThemeSettingBeforeIsMobile', {
+ layout: themeStore.layout.mode,
+ siderCollapse: siderCollapse.value
+ });
+
+ themeStore.setThemeLayout('vertical');
+ setSiderCollapse(true);
+ } else {
+ // when is not mobile, recover the backup theme setting
+ const backup = localStg.get('backupThemeSettingBeforeIsMobile');
+
+ if (backup) {
+ nextTick(() => {
+ themeStore.setThemeLayout(backup.layout);
+ setSiderCollapse(backup.siderCollapse);
+
+ localStg.remove('backupThemeSettingBeforeIsMobile');
+ });
+ }
+ }
+ },
+ { immediate: true }
+ );
+
+ // watch locale
+ watch(locale, () => {
+ // update document title by locale
+ updateDocumentTitleByLocale();
+
+ // update global menus by locale
+ routeStore.updateGlobalMenusByLocale();
+
+ // update tabs by locale
+ tabStore.updateTabsByLocale();
+
+ // set dayjs locale
+ setDayjsLocale(locale.value);
+ });
+ });
+
+ // cache mixSiderFixed
+ useEventListener(window, 'beforeunload', () => {
+ localStg.set('mixSiderFixed', mixSiderFixed.value ? 'Y' : 'N');
+ });
+
+ /** On scope dispose */
+ onScopeDispose(() => {
+ scope.stop();
+ });
+
+ // init
+ init();
+
+ return {
+ isMobile,
+ reloadFlag,
+ reloadPage,
+ fullContent,
+ locale,
+ localeOptions,
+ changeLocale,
+ themeDrawerVisible,
+ openThemeDrawer,
+ closeThemeDrawer,
+ toggleFullContent,
+ contentXScrollable,
+ setContentXScrollable,
+ siderCollapse,
+ setSiderCollapse,
+ toggleSiderCollapse,
+ mixSiderFixed,
+ setMixSiderFixed,
+ toggleMixSiderFixed
+ };
+});
diff --git a/ruoyi-plus-soybean/src/store/modules/auth/index.ts b/ruoyi-plus-soybean/src/store/modules/auth/index.ts
new file mode 100755
index 0000000..56874df
--- /dev/null
+++ b/ruoyi-plus-soybean/src/store/modules/auth/index.ts
@@ -0,0 +1,199 @@
+import { computed, reactive, ref } from 'vue';
+import { useRoute } from 'vue-router';
+import { defineStore } from 'pinia';
+import { useLoading } from '@sa/hooks';
+import { fetchGetUserInfo, fetchLogin, fetchLogout } from '@/service/api';
+import { useRouterPush } from '@/hooks/common/router';
+import { localStg } from '@/utils/storage';
+import { SetupStoreId } from '@/enum';
+import { useRouteStore } from '../route';
+import { useTabStore } from '../tab';
+import useNoticeStore from '../notice';
+import { clearAuthStorage, getToken } from './shared';
+
+export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
+ const route = useRoute();
+ const authStore = useAuthStore();
+ const routeStore = useRouteStore();
+ const tabStore = useTabStore();
+ const noticeStore = useNoticeStore();
+ const { toLogin, redirectFromLogin } = useRouterPush(false);
+ const { loading: loginLoading, startLoading, endLoading } = useLoading();
+
+ const token = ref(getToken());
+
+ const userInfo: Api.Auth.UserInfo = reactive({
+ user: undefined,
+ roles: [],
+ permissions: []
+ });
+
+ /** is super role in static route */
+ const isStaticSuper = computed(() => {
+ const { VITE_AUTH_ROUTE_MODE, VITE_STATIC_SUPER_ROLE } = import.meta.env;
+
+ return VITE_AUTH_ROUTE_MODE === 'static' && userInfo.roles.includes(VITE_STATIC_SUPER_ROLE);
+ });
+
+ /** Is login */
+ const isLogin = computed(() => Boolean(token.value));
+
+ /** Reset auth store */
+ async function resetStore() {
+ recordUserId();
+
+ clearAuthStorage();
+
+ authStore.$reset();
+
+ if (!route.meta.constant) {
+ await toLogin();
+ }
+
+ noticeStore.clearNotice();
+ tabStore.cacheTabs();
+ routeStore.resetStore();
+ }
+
+ async function logout() {
+ await fetchLogout();
+ resetStore();
+ }
+
+ /** Record the user ID of the previous login session Used to compare with the current user ID on next login */
+ function recordUserId() {
+ if (!userInfo.user?.userId) {
+ return;
+ }
+
+ // Store current user ID locally for next login comparison
+ localStg.set('lastLoginUserId', userInfo.user?.userId);
+ }
+
+ /**
+ * Check if current login user is different from previous login user If different, clear all tabs
+ *
+ * @returns {boolean} Whether to clear all tabs
+ */
+ function checkTabClear(): boolean {
+ if (!userInfo.user?.userId) {
+ return false;
+ }
+
+ const lastLoginUserId = localStg.get('lastLoginUserId');
+
+ // Clear all tabs if current user is different from previous user
+ if (!lastLoginUserId || lastLoginUserId !== userInfo.user?.userId) {
+ localStg.remove('globalTabs');
+ tabStore.clearTabs();
+
+ localStg.remove('lastLoginUserId');
+ return true;
+ }
+
+ localStg.remove('lastLoginUserId');
+ return false;
+ }
+
+ /**
+ * Login
+ *
+ * @param [redirect=true] Whether to redirect after login. Default is `true`
+ */
+ async function login(loginForm: Api.Auth.PwdLoginForm | Api.Auth.SocialLoginForm, redirect = true) {
+ startLoading();
+
+ const { VITE_APP_CLIENT_ID } = import.meta.env;
+
+ const loginData: Api.Auth.PwdLoginForm = {
+ ...loginForm,
+ tenantId: loginForm.tenantId ?? '000000',
+ clientId: VITE_APP_CLIENT_ID!,
+ grantType: loginForm.grantType ?? 'password'
+ };
+
+ const { data: loginToken, error } = await fetchLogin(loginData);
+
+ if (!error) {
+ const pass = await loginByToken(loginToken);
+
+ if (pass) {
+ // Check if the tab needs to be cleared
+ const isClear = checkTabClear();
+ let needRedirect = redirect;
+
+ if (isClear) {
+ // If the tab needs to be cleared,it means we don't need to redirect.
+ needRedirect = false;
+ }
+ await redirectFromLogin(needRedirect);
+
+ // window.$notification?.success({
+ // title: $t('page.login.common.loginSuccess'),
+ // content: $t('page.login.common.welcomeBack', { userName: userInfo.userName }),
+ // duration: 4500
+ // });
+ }
+ } else {
+ resetStore();
+ }
+
+ endLoading();
+
+ return error ? Promise.reject(error) : Promise.resolve();
+ }
+
+ async function loginByToken(loginToken: Api.Auth.LoginToken) {
+ // 1. stored in the localStorage, the later requests need it in headers
+ localStg.set('token', loginToken.access_token!);
+ localStg.set('refreshToken', loginToken.refresh_token!);
+
+ // 2. get user info
+ const pass = await getUserInfo();
+
+ if (pass) {
+ token.value = loginToken.access_token!;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ async function getUserInfo() {
+ const { data: info, error } = await fetchGetUserInfo();
+
+ if (!error) {
+ // update store
+ Object.assign(userInfo, info);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ async function initUserInfo() {
+ const hasToken = getToken();
+
+ if (hasToken) {
+ const pass = await getUserInfo();
+
+ if (!pass) {
+ resetStore();
+ }
+ }
+ }
+
+ return {
+ token,
+ userInfo,
+ isStaticSuper,
+ isLogin,
+ loginLoading,
+ resetStore,
+ login,
+ logout,
+ initUserInfo
+ };
+});
diff --git a/ruoyi-plus-soybean/src/store/modules/auth/shared.ts b/ruoyi-plus-soybean/src/store/modules/auth/shared.ts
new file mode 100755
index 0000000..5cf8852
--- /dev/null
+++ b/ruoyi-plus-soybean/src/store/modules/auth/shared.ts
@@ -0,0 +1,12 @@
+import { localStg } from '@/utils/storage';
+
+/** Get token */
+export function getToken() {
+ return localStg.get('token') || '';
+}
+
+/** Clear auth storage */
+export function clearAuthStorage() {
+ localStg.remove('token');
+ localStg.remove('refreshToken');
+}
diff --git a/ruoyi-plus-soybean/src/store/modules/dict/index.ts b/ruoyi-plus-soybean/src/store/modules/dict/index.ts
new file mode 100755
index 0000000..d21fcd9
--- /dev/null
+++ b/ruoyi-plus-soybean/src/store/modules/dict/index.ts
@@ -0,0 +1,42 @@
+import { ref } from 'vue';
+import { defineStore } from 'pinia';
+import { $t } from '@/locales';
+import { SetupStoreId } from '@/enum';
+
+export const useDictStore = defineStore(SetupStoreId.Dict, () => {
+ const dictData = ref<{ [key: string]: Api.System.DictData[] }>({});
+
+ const getDict = (key: string) => {
+ return dictData.value[key]?.map(item => ({
+ ...item,
+ dictLabel: item.dictLabel?.startsWith(`dict.${item.dictType}.`)
+ ? $t(item.dictLabel as App.I18n.I18nKey)
+ : item.dictLabel
+ }));
+ };
+
+ const setDict = (key: string, dict: Api.System.DictData[]) => {
+ dictData.value[key] = dict;
+ };
+
+ const removeDict = (key: string) => {
+ if (key in dictData.value) {
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
+ delete dictData.value[key];
+ }
+ };
+
+ const cleanDict = () => {
+ dictData.value = {};
+ };
+
+ return {
+ dictData,
+ getDict,
+ setDict,
+ removeDict,
+ cleanDict
+ };
+});
+
+export default useDictStore;
diff --git a/ruoyi-plus-soybean/src/store/modules/notice/index.ts b/ruoyi-plus-soybean/src/store/modules/notice/index.ts
new file mode 100755
index 0000000..8af2906
--- /dev/null
+++ b/ruoyi-plus-soybean/src/store/modules/notice/index.ts
@@ -0,0 +1,50 @@
+import { reactive } from 'vue';
+import { defineStore } from 'pinia';
+import { SetupStoreId } from '@/enum';
+
+interface NoticeItem {
+ title?: string;
+ read: boolean;
+ message: any;
+ time: string;
+}
+
+export const useNoticeStore = defineStore(SetupStoreId.Notice, () => {
+ const state = reactive({
+ notices: [] as NoticeItem[]
+ });
+
+ const addNotice = (notice: NoticeItem) => {
+ state.notices.push(notice);
+ };
+
+ const removeNotice = (notice: NoticeItem) => {
+ state.notices.splice(state.notices.indexOf(notice), 1);
+ };
+
+ const readNotice = (notice: NoticeItem) => {
+ state.notices[state.notices.indexOf(notice)].read = true;
+ };
+
+ // 瀹炵幇鍏ㄩ儴宸茶
+ const readAll = () => {
+ state.notices.forEach((item: any) => {
+ item.read = true;
+ });
+ };
+
+ const clearNotice = () => {
+ state.notices = [];
+ };
+
+ return {
+ state,
+ addNotice,
+ removeNotice,
+ readNotice,
+ readAll,
+ clearNotice
+ };
+});
+
+export default useNoticeStore;
diff --git a/ruoyi-plus-soybean/src/store/modules/route/index.ts b/ruoyi-plus-soybean/src/store/modules/route/index.ts
new file mode 100755
index 0000000..ae79ce1
--- /dev/null
+++ b/ruoyi-plus-soybean/src/store/modules/route/index.ts
@@ -0,0 +1,424 @@
+import { computed, nextTick, ref, shallowRef } from 'vue';
+import type { RouteRecordRaw } from 'vue-router';
+import { defineStore } from 'pinia';
+import { useBoolean } from '@sa/hooks';
+import type { CustomRoute, ElegantConstRoute, LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types';
+import { router } from '@/router';
+import { fetchGetRoutes } from '@/service/api';
+import { isNotNull } from '@/utils/common';
+import { SetupStoreId } from '@/enum';
+import { createDynamicRoutes, createStaticRoutes, getAuthVueRoutes } from '@/router/routes';
+import { ROOT_ROUTE } from '@/router/routes/builtin';
+import { getRouteName, getRoutePath } from '@/router/elegant/transform';
+import { useAuthStore } from '../auth';
+import { useTabStore } from '../tab';
+import {
+ filterAuthRoutesByRoles,
+ getBreadcrumbsByRoute,
+ getCacheRouteNames,
+ getGlobalMenusByAuthRoutes,
+ getSelectedMenuKeyPathByKey,
+ isRouteExistByRouteName,
+ sortRoutesByOrder,
+ transformMenuToSearchMenus,
+ updateLocaleOfGlobalMenus
+} from './shared';
+
+const defaultIcon = import.meta.env.VITE_MENU_ICON;
+
+export const useRouteStore = defineStore(SetupStoreId.Route, () => {
+ const authStore = useAuthStore();
+ const tabStore = useTabStore();
+ const { bool: isInitConstantRoute, setBool: setIsInitConstantRoute } = useBoolean();
+ const { bool: isInitAuthRoute, setBool: setIsInitAuthRoute } = useBoolean();
+
+ /**
+ * Auth route mode
+ *
+ * It recommends to use static mode in the development environment, and use dynamic mode in the production
+ * environment, if use static mode in development environment, the auth routes will be auto generated by plugin
+ * "@elegant-router/vue"
+ */
+ const authRouteMode = ref(import.meta.env.VITE_AUTH_ROUTE_MODE);
+
+ /** Home route key */
+ const routeHome = ref(import.meta.env.VITE_ROUTE_HOME || 'home');
+
+ /** constant routes */
+ const constantRoutes = shallowRef<ElegantConstRoute[]>([]);
+
+ function addConstantRoutes(routes: ElegantConstRoute[]) {
+ const constantRoutesMap = new Map<string, ElegantConstRoute>([]);
+
+ routes.forEach(route => {
+ constantRoutesMap.set(route.name, route);
+ });
+
+ constantRoutes.value = Array.from(constantRoutesMap.values());
+ }
+
+ /** auth routes */
+ const authRoutes = shallowRef<ElegantConstRoute[]>([]);
+
+ function addAuthRoutes(routes: ElegantConstRoute[]) {
+ const authRoutesMap = new Map<string, ElegantConstRoute>([]);
+
+ routes.forEach(route => {
+ if (authRouteMode.value === 'dynamic') {
+ if (route.path === '/' && route.children?.length) {
+ const child = route.children[0];
+ // @ts-expect-error no hidden field
+ child.hidden = route.hidden;
+ parseRouter(child);
+ child.name = Math.random().toString(36).slice(2, 12);
+ Object.assign(route, child);
+ delete route.children;
+ authRoutesMap.set(route.name, route);
+ return;
+ }
+ parseRouter(route);
+ }
+ authRoutesMap.set(route.name, route);
+ });
+
+ const dynamicRoutes = createDynamicRoutes();
+
+ dynamicRoutes.authRoutes.forEach(route => {
+ const parent = authRoutesMap.get(route.name);
+ if (parent && route.children) {
+ parent.children?.push(...route.children);
+ return;
+ }
+ authRoutesMap.set(route.name, route);
+ });
+
+ authRoutes.value = Array.from(authRoutesMap.values());
+ }
+
+ // eslint-disable-next-line complexity
+ function parseRouter(route: ElegantConstRoute, parent?: ElegantConstRoute) {
+ route.meta = route.meta ? route.meta : { title: route.name };
+ if (route.meta.title.startsWith('route.') || route.meta.title.startsWith('menu.')) {
+ route.meta.i18nKey = route.meta.title as App.I18n.I18nKey;
+ }
+ const isLayout = route.component === 'Layout';
+ const isFramePage = route.component === 'FrameView';
+ const isParentLayout = route.component === 'ParentView';
+ const isBlankLayout = route.component?.startsWith('layout.blank$view.');
+ const isExternalLink = isNotNull(route.meta.link);
+
+ route.path = route.path.startsWith('/') ? route.path : `/${route.path}`;
+ route.path = parent ? parent.path + route.path : route.path;
+
+ route.name = route
+ .component!.replace(/\/index$/, '')
+ .replace(/\//g, '_')
+ .replace(/([A-Z])/g, '-$1')
+ .toLowerCase();
+ if (isLayout || isFramePage || isParentLayout) {
+ const name = route.path.substring(1).replaceAll('/', '_');
+ route.name = parent ? `${parent.name}_${name}` : name;
+ }
+
+ if (route.meta.icon?.startsWith('local-icon-')) {
+ route.meta.localIcon = route.meta.icon.replace('local-icon-', 'menu-');
+ delete route.meta.icon;
+ } else if (!isNotNull(route.meta.icon)) {
+ route.meta.icon = defaultIcon;
+ }
+ // @ts-expect-error no hidden field
+ route.meta.hideInMenu = route.hidden;
+ // @ts-expect-error route.meta.activeMenu is activeMenu type
+ route.meta.activeMenu = route.meta?.activeMenu?.substring(1);
+ if (route.meta.hideInMenu && parent && !route.meta.activeMenu) {
+ // @ts-expect-error parent.name is activeMenu type
+ route.meta.activeMenu = parent.name;
+ }
+ // 鏄惁闇�瑕乲eepAlive
+ route.meta.keepAlive = !route.meta.noCache;
+
+ if (isFramePage) {
+ if (isExternalLink) {
+ route.meta.href = String(route.meta.link);
+ const random = Math.random().toString(36).slice(2, 12);
+ route.name = random;
+ route.path = `/${random}`;
+ route.component = 'layout.base$view.iframe-page';
+ } else {
+ try {
+ route.props = {
+ // @ts-expect-error no query field
+ url: JSON.parse(route.query)?.url
+ };
+ } catch {}
+ }
+ route.component = parent && !isExternalLink ? 'view.iframe-page' : 'layout.base$view.iframe-page';
+ } else if (!isLayout && !isParentLayout && !isBlankLayout) {
+ route.component = parent ? `view.${route.name}` : `layout.base$view.${route.name}`;
+ } else if (!isBlankLayout) {
+ route.component = isParentLayout ? undefined : 'layout.base';
+ }
+
+ delete route.meta.link;
+ delete route.meta.noCache;
+ // @ts-expect-error no query field
+ delete route.query;
+ // @ts-expect-error no hidden field
+ delete route.hidden;
+
+ route.children?.forEach(child => parseRouter(child, route));
+ }
+
+ const removeRouteFns: (() => void)[] = [];
+
+ /** Global menus */
+ const menus = ref<App.Global.Menu[]>([]);
+ const searchMenus = computed(() => transformMenuToSearchMenus(menus.value));
+
+ /** Get global menus */
+ function getGlobalMenus(routes: ElegantConstRoute[]) {
+ menus.value = getGlobalMenusByAuthRoutes(routes);
+ }
+
+ /** Update global menus by locale */
+ function updateGlobalMenusByLocale() {
+ menus.value = updateLocaleOfGlobalMenus(menus.value);
+ }
+
+ /** Cache routes */
+ const cacheRoutes = ref<RouteKey[]>([]);
+
+ /**
+ * Exclude cache routes
+ *
+ * for reset route cache
+ */
+ const excludeCacheRoutes = ref<RouteKey[]>([]);
+
+ /**
+ * Get cache routes
+ *
+ * @param routes Vue routes
+ */
+ function getCacheRoutes(routes: RouteRecordRaw[]) {
+ cacheRoutes.value = getCacheRouteNames(routes);
+ }
+
+ /**
+ * Reset route cache
+ *
+ * @default router.currentRoute.value.name current route name
+ * @param routeKey
+ */
+ async function resetRouteCache(routeKey?: RouteKey) {
+ const routeName = routeKey || (router.currentRoute.value.name as RouteKey);
+
+ excludeCacheRoutes.value.push(routeName);
+
+ await nextTick();
+
+ excludeCacheRoutes.value = [];
+ }
+
+ /** Global breadcrumbs */
+ const breadcrumbs = computed(() => getBreadcrumbsByRoute(router.currentRoute.value, menus.value));
+
+ /** Reset store */
+ async function resetStore() {
+ const routeStore = useRouteStore();
+
+ routeStore.$reset();
+
+ resetVueRoutes();
+
+ // after reset store, need to re-init constant route
+ await initConstantRoute();
+ }
+
+ /** Reset vue routes */
+ function resetVueRoutes() {
+ removeRouteFns.forEach(fn => fn());
+ removeRouteFns.length = 0;
+ }
+
+ /** init constant route */
+ async function initConstantRoute() {
+ if (isInitConstantRoute.value) return;
+
+ const staticRoute = createStaticRoutes();
+
+ if (authRouteMode.value === 'static') {
+ addConstantRoutes(staticRoute.constantRoutes);
+ } else {
+ addConstantRoutes(staticRoute.constantRoutes);
+ }
+
+ handleConstantAndAuthRoutes();
+
+ setIsInitConstantRoute(true);
+
+ tabStore.initHomeTab();
+ }
+
+ /** Init auth route */
+ async function initAuthRoute() {
+ // check if user info is initialized
+ if (!authStore.userInfo.user?.userId) {
+ await authStore.initUserInfo();
+ }
+
+ if (authRouteMode.value === 'static') {
+ initStaticAuthRoute();
+ } else {
+ await initDynamicAuthRoute();
+ }
+
+ tabStore.initHomeTab();
+ }
+
+ /** Init static auth route */
+ function initStaticAuthRoute() {
+ const { authRoutes: staticAuthRoutes } = createStaticRoutes();
+
+ if (authStore.isStaticSuper) {
+ addAuthRoutes(staticAuthRoutes);
+ } else {
+ const filteredAuthRoutes = filterAuthRoutesByRoles(staticAuthRoutes, authStore.userInfo.roles);
+
+ addAuthRoutes(filteredAuthRoutes);
+ }
+
+ handleConstantAndAuthRoutes();
+
+ setIsInitAuthRoute(true);
+ }
+
+ /** Init dynamic auth route */
+ async function initDynamicAuthRoute() {
+ const { data, error } = await fetchGetRoutes();
+
+ if (!error) {
+ addAuthRoutes(data);
+
+ handleConstantAndAuthRoutes();
+
+ handleUpdateRootRouteRedirect(routeHome.value);
+
+ setIsInitAuthRoute(true);
+ } else {
+ // if fetch user routes failed, reset store
+ authStore.resetStore();
+ }
+ }
+
+ /** handle constant and auth routes */
+ function handleConstantAndAuthRoutes() {
+ const allRoutes = [...constantRoutes.value, ...authRoutes.value];
+
+ const sortRoutes = sortRoutesByOrder(allRoutes);
+
+ const vueRoutes = getAuthVueRoutes(sortRoutes);
+
+ resetVueRoutes();
+
+ addRoutesToVueRouter(vueRoutes);
+
+ getGlobalMenus(sortRoutes);
+
+ getCacheRoutes(vueRoutes);
+ }
+
+ /**
+ * Add routes to vue router
+ *
+ * @param routes Vue routes
+ */
+ function addRoutesToVueRouter(routes: RouteRecordRaw[]) {
+ routes.forEach(route => {
+ const removeFn = router.addRoute(route);
+ addRemoveRouteFn(removeFn);
+ });
+ }
+
+ /**
+ * Add remove route fn
+ *
+ * @param fn
+ */
+ function addRemoveRouteFn(fn: () => void) {
+ removeRouteFns.push(fn);
+ }
+
+ /**
+ * Update root route redirect when auth route mode is dynamic
+ *
+ * @param redirectKey Redirect route key
+ */
+ function handleUpdateRootRouteRedirect(redirectKey: LastLevelRouteKey) {
+ const redirect = getRoutePath(redirectKey);
+
+ if (redirect) {
+ const rootRoute: CustomRoute = { ...ROOT_ROUTE, redirect };
+
+ router.removeRoute(rootRoute.name);
+
+ const [rootVueRoute] = getAuthVueRoutes([rootRoute]);
+
+ router.addRoute(rootVueRoute);
+ }
+ }
+
+ /**
+ * Get is auth route exist
+ *
+ * @param routePath Route path
+ */
+ async function getIsAuthRouteExist(routePath: RouteMap[RouteKey]) {
+ const routeName = getRouteName(routePath);
+
+ if (!routeName) {
+ return false;
+ }
+
+ const { authRoutes: staticAuthRoutes } = createStaticRoutes();
+ return isRouteExistByRouteName(routeName, staticAuthRoutes);
+ }
+
+ /**
+ * Get selected menu key path
+ *
+ * @param selectedKey Selected menu key
+ */
+ function getSelectedMenuKeyPath(selectedKey: string) {
+ return getSelectedMenuKeyPathByKey(selectedKey, menus.value);
+ }
+
+ async function onRouteSwitchWhenLoggedIn() {
+ // some global init logic when logged in and switch route
+ }
+
+ async function onRouteSwitchWhenNotLoggedIn() {
+ // some global init logic if it does not need to be logged in
+ }
+
+ return {
+ resetStore,
+ routeHome,
+ menus,
+ searchMenus,
+ updateGlobalMenusByLocale,
+ cacheRoutes,
+ excludeCacheRoutes,
+ resetRouteCache,
+ breadcrumbs,
+ initConstantRoute,
+ isInitConstantRoute,
+ initAuthRoute,
+ isInitAuthRoute,
+ setIsInitAuthRoute,
+ getIsAuthRouteExist,
+ getSelectedMenuKeyPath,
+ onRouteSwitchWhenLoggedIn,
+ onRouteSwitchWhenNotLoggedIn
+ };
+});
diff --git a/ruoyi-plus-soybean/src/store/modules/route/shared.ts b/ruoyi-plus-soybean/src/store/modules/route/shared.ts
new file mode 100755
index 0000000..48eeae3
--- /dev/null
+++ b/ruoyi-plus-soybean/src/store/modules/route/shared.ts
@@ -0,0 +1,335 @@
+import type { RouteLocationNormalizedLoaded, RouteRecordRaw, _RouteRecordBase } from 'vue-router';
+import type { ElegantConstRoute, LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types';
+import { useSvgIcon } from '@/hooks/common/icon';
+import { $t } from '@/locales';
+
+/**
+ * Filter auth routes by roles
+ *
+ * @param routes Auth routes
+ * @param roles Roles
+ */
+export function filterAuthRoutesByRoles(routes: ElegantConstRoute[], roles: string[]) {
+ return routes.flatMap(route => filterAuthRouteByRoles(route, roles));
+}
+
+/**
+ * Filter auth route by roles
+ *
+ * @param route Auth route
+ * @param roles Roles
+ */
+function filterAuthRouteByRoles(route: ElegantConstRoute, roles: string[]): ElegantConstRoute[] {
+ const routeRoles = (route.meta && route.meta.roles) || [];
+
+ // if the route's "roles" is empty, then it is allowed to access
+ const isEmptyRoles = !routeRoles.length;
+
+ // if the user's role is included in the route's "roles", then it is allowed to access
+ const hasPermission = routeRoles.some(role => roles.includes(role));
+
+ const filterRoute = { ...route };
+
+ if (filterRoute.children?.length) {
+ filterRoute.children = filterRoute.children.flatMap(item => filterAuthRouteByRoles(item, roles));
+ }
+
+ // Exclude the route if it has no children after filtering
+ if (filterRoute.children?.length === 0) {
+ return [];
+ }
+
+ return hasPermission || isEmptyRoles ? [filterRoute] : [];
+}
+
+/**
+ * sort route by order
+ *
+ * @param route route
+ */
+function sortRouteByOrder(route: ElegantConstRoute) {
+ if (route.children?.length) {
+ route.children.sort((next, prev) => (Number(next.meta?.order) || 0) - (Number(prev.meta?.order) || 0));
+ route.children.forEach(sortRouteByOrder);
+ }
+
+ return route;
+}
+
+/**
+ * sort routes by order
+ *
+ * @param routes routes
+ */
+export function sortRoutesByOrder(routes: ElegantConstRoute[]) {
+ routes.sort((next, prev) => (Number(next.meta?.order) || 0) - (Number(prev.meta?.order) || 0));
+ routes.forEach(sortRouteByOrder);
+
+ return routes;
+}
+
+/**
+ * Get global menus by auth routes
+ *
+ * @param routes Auth routes
+ */
+export function getGlobalMenusByAuthRoutes(routes: ElegantConstRoute[]) {
+ const menus: App.Global.Menu[] = [];
+
+ routes.forEach(route => {
+ if (!route.meta?.hideInMenu) {
+ const menu = getGlobalMenuByBaseRoute(route);
+
+ if (route.children?.some(child => !child.meta?.hideInMenu)) {
+ menu.children = getGlobalMenusByAuthRoutes(route.children);
+ }
+
+ menus.push(menu);
+ }
+ });
+
+ return menus;
+}
+
+/**
+ * Update locale of global menus
+ *
+ * @param menus
+ */
+export function updateLocaleOfGlobalMenus(menus: App.Global.Menu[]) {
+ const result: App.Global.Menu[] = [];
+
+ menus.forEach(menu => {
+ const { i18nKey, label, children } = menu;
+
+ const newLabel = i18nKey ? $t(i18nKey) : label;
+
+ const newMenu: App.Global.Menu = {
+ ...menu,
+ label: newLabel
+ };
+
+ if (children?.length) {
+ newMenu.children = updateLocaleOfGlobalMenus(children);
+ }
+
+ result.push(newMenu);
+ });
+
+ return result;
+}
+
+/**
+ * Get global menu by route
+ *
+ * @param route
+ */
+function getGlobalMenuByBaseRoute(route: RouteLocationNormalizedLoaded | ElegantConstRoute) {
+ const { SvgIconVNode } = useSvgIcon();
+
+ const { name, path } = route;
+ const { title, i18nKey, icon = import.meta.env.VITE_MENU_ICON, localIcon, iconFontSize } = route.meta ?? {};
+
+ const label = i18nKey ? $t(i18nKey) : title!;
+
+ const menu: App.Global.Menu = {
+ key: name as string,
+ label,
+ i18nKey,
+ routeKey: name as RouteKey,
+ routePath: path as RouteMap[RouteKey],
+ icon: SvgIconVNode({ icon, localIcon, fontSize: iconFontSize || 20 })
+ };
+
+ return menu;
+}
+
+/**
+ * Get cache route names
+ *
+ * @param routes Vue routes (two levels)
+ */
+export function getCacheRouteNames(routes: RouteRecordRaw[]) {
+ const cacheNames: LastLevelRouteKey[] = [];
+
+ routes.forEach(route => {
+ // only get last two level route, which has component
+ route.children?.forEach(child => {
+ if (child.component && child.meta?.keepAlive) {
+ cacheNames.push(child.name as LastLevelRouteKey);
+ }
+ });
+ });
+
+ return cacheNames;
+}
+
+/**
+ * Is route exist by route name
+ *
+ * @param routeName
+ * @param routes
+ */
+export function isRouteExistByRouteName(routeName: RouteKey, routes: ElegantConstRoute[]) {
+ return routes.some(route => recursiveGetIsRouteExistByRouteName(route, routeName));
+}
+
+/**
+ * Recursive get is route exist by route name
+ *
+ * @param route
+ * @param routeName
+ */
+function recursiveGetIsRouteExistByRouteName(route: ElegantConstRoute, routeName: RouteKey) {
+ let isExist = route.name === routeName;
+
+ if (isExist) {
+ return true;
+ }
+
+ if (route.children && route.children.length) {
+ isExist = route.children.some(item => recursiveGetIsRouteExistByRouteName(item, routeName));
+ }
+
+ return isExist;
+}
+
+/**
+ * Get selected menu key path
+ *
+ * @param selectedKey
+ * @param menus
+ */
+export function getSelectedMenuKeyPathByKey(selectedKey: string, menus: App.Global.Menu[]) {
+ const keyPath: string[] = [];
+
+ menus.some(menu => {
+ const path = findMenuPath(selectedKey, menu);
+
+ const find = Boolean(path?.length);
+
+ if (find) {
+ keyPath.push(...path!);
+ }
+
+ return find;
+ });
+
+ return keyPath;
+}
+
+/**
+ * Find menu path
+ *
+ * @param targetKey Target menu key
+ * @param menu Menu
+ */
+function findMenuPath(targetKey: string, menu: App.Global.Menu): string[] | null {
+ const path: string[] = [];
+
+ function dfs(item: App.Global.Menu): boolean {
+ path.push(item.key);
+
+ if (item.key === targetKey) {
+ return true;
+ }
+
+ if (item.children) {
+ for (const child of item.children) {
+ if (dfs(child)) {
+ return true;
+ }
+ }
+ }
+
+ path.pop();
+
+ return false;
+ }
+
+ if (dfs(menu)) {
+ return path;
+ }
+
+ return null;
+}
+
+/**
+ * Transform menu to breadcrumb
+ *
+ * @param menu
+ */
+function transformMenuToBreadcrumb(menu: App.Global.Menu) {
+ const { children, ...rest } = menu;
+
+ const breadcrumb: App.Global.Breadcrumb = {
+ ...rest
+ };
+
+ if (children?.length) {
+ breadcrumb.options = children.map(transformMenuToBreadcrumb);
+ }
+
+ return breadcrumb;
+}
+
+/**
+ * Get breadcrumbs by route
+ *
+ * @param route
+ * @param menus
+ */
+export function getBreadcrumbsByRoute(
+ route: RouteLocationNormalizedLoaded,
+ menus: App.Global.Menu[]
+): App.Global.Breadcrumb[] {
+ const key = route.name as string;
+ const activeKey = route.meta?.activeMenu;
+
+ for (const menu of menus) {
+ if (menu.key === key) {
+ return [transformMenuToBreadcrumb(menu)];
+ }
+
+ if (menu.key === activeKey) {
+ const ROUTE_DEGREE_SPLITTER = '_';
+
+ const parentKey = key.split(ROUTE_DEGREE_SPLITTER).slice(0, -1).join(ROUTE_DEGREE_SPLITTER);
+
+ const breadcrumbMenu = getGlobalMenuByBaseRoute(route);
+ if (parentKey !== activeKey) {
+ return [transformMenuToBreadcrumb(breadcrumbMenu)];
+ }
+
+ return [transformMenuToBreadcrumb(menu), transformMenuToBreadcrumb(breadcrumbMenu)];
+ }
+
+ if (menu.children?.length) {
+ const result = getBreadcrumbsByRoute(route, menu.children);
+ if (result.length > 0) {
+ return [transformMenuToBreadcrumb(menu), ...result];
+ }
+ }
+ }
+
+ return [];
+}
+
+/**
+ * Transform menu to searchMenus
+ *
+ * @param menus - menus
+ * @param treeMap
+ */
+export function transformMenuToSearchMenus(menus: App.Global.Menu[], treeMap: App.Global.Menu[] = []) {
+ if (menus && menus.length === 0) return [];
+ return menus.reduce((acc, cur) => {
+ if (!cur.children) {
+ acc.push(cur);
+ }
+ if (cur.children && cur.children.length > 0) {
+ transformMenuToSearchMenus(cur.children, treeMap);
+ }
+ return acc;
+ }, treeMap);
+}
diff --git a/ruoyi-plus-soybean/src/store/modules/tab/index.ts b/ruoyi-plus-soybean/src/store/modules/tab/index.ts
new file mode 100755
index 0000000..0025bcc
--- /dev/null
+++ b/ruoyi-plus-soybean/src/store/modules/tab/index.ts
@@ -0,0 +1,395 @@
+import { computed, ref } from 'vue';
+import { useEventListener } from '@vueuse/core';
+import { defineStore } from 'pinia';
+import type { RouteKey } from '@elegant-router/types';
+import { router } from '@/router';
+import { useRouteStore } from '@/store/modules/route';
+import { useRouterPush } from '@/hooks/common/router';
+import { localStg } from '@/utils/storage';
+import { SetupStoreId } from '@/enum';
+import { useThemeStore } from '../theme';
+import {
+ extractTabsByAllRoutes,
+ filterTabsByIds,
+ findTabByRouteName,
+ getAllTabs,
+ getDefaultHomeTab,
+ getFixedTabIds,
+ getTabByRoute,
+ getTabIdByRoute,
+ isTabInTabs,
+ reorderFixedTabs,
+ updateTabByI18nKey,
+ updateTabsByI18nKey
+} from './shared';
+
+export const useTabStore = defineStore(SetupStoreId.Tab, () => {
+ const routeStore = useRouteStore();
+ const themeStore = useThemeStore();
+ const { routerPush } = useRouterPush(false);
+
+ /** Tabs */
+ const tabs = ref<App.Global.Tab[]>([]);
+
+ /** Get active tab */
+ const homeTab = ref<App.Global.Tab>();
+
+ /** Init home tab */
+ function initHomeTab() {
+ homeTab.value = getDefaultHomeTab(router, routeStore.routeHome);
+ }
+
+ /** Get all tabs */
+ const allTabs = computed(() => getAllTabs(tabs.value, homeTab.value));
+
+ /** Active tab id */
+ const activeTabId = ref<string>('');
+
+ /**
+ * Set active tab id
+ *
+ * @param id Tab id
+ */
+ function setActiveTabId(id: string) {
+ activeTabId.value = id;
+ }
+
+ /**
+ * Init tab store
+ *
+ * @param currentRoute Current route
+ */
+ function initTabStore(currentRoute: App.Global.TabRoute) {
+ const storageTabs = localStg.get('globalTabs');
+
+ if (themeStore.tab.cache && storageTabs) {
+ const extractedTabs = extractTabsByAllRoutes(router, storageTabs);
+ tabs.value = updateTabsByI18nKey(extractedTabs);
+ }
+
+ addTab(currentRoute);
+ }
+
+ /**
+ * Add tab
+ *
+ * @param route Tab route
+ * @param active Whether to activate the added tab
+ */
+ function addTab(route: App.Global.TabRoute, active = true) {
+ const tab = getTabByRoute(route);
+
+ const isHomeTab = tab.id === homeTab.value?.id;
+
+ if (!isHomeTab && !isTabInTabs(tab.id, tabs.value)) {
+ tabs.value.push(tab);
+ }
+
+ if (active) {
+ setActiveTabId(tab.id);
+ }
+ }
+
+ /**
+ * Remove tab
+ *
+ * @param tabId Tab id
+ */
+ async function removeTab(tabId: string) {
+ const removeTabIndex = tabs.value.findIndex(tab => tab.id === tabId);
+ if (removeTabIndex === -1) return;
+
+ const removedTabRouteKey = tabs.value[removeTabIndex].routeKey;
+ const isRemoveActiveTab = activeTabId.value === tabId;
+
+ // if remove the last tab, then switch to the second last tab
+ const nextTab = tabs.value[removeTabIndex + 1] || tabs.value[removeTabIndex - 1] || homeTab.value;
+
+ // remove tab
+ tabs.value.splice(removeTabIndex, 1);
+
+ // if current tab is removed, then switch to next tab
+ if (isRemoveActiveTab && nextTab) {
+ await switchRouteByTab(nextTab);
+ }
+
+ // reset route cache
+ routeStore.resetRouteCache(removedTabRouteKey);
+ }
+
+ /** remove active tab */
+ async function removeActiveTab() {
+ await removeTab(activeTabId.value);
+ }
+
+ /**
+ * remove tab by route name
+ *
+ * @param routeName route name
+ */
+ async function removeTabByRouteName(routeName: RouteKey) {
+ const tab = findTabByRouteName(routeName, tabs.value);
+ if (!tab) return;
+
+ await removeTab(tab.id);
+ }
+
+ /**
+ * Clear tabs
+ *
+ * @param excludes Exclude tab ids
+ */
+ async function clearTabs(excludes: string[] = [], clearCache: boolean = false) {
+ const remainTabIds = [...getFixedTabIds(tabs.value), ...excludes];
+
+ // Identify tabs to be removed and collect their routeKeys if strategy is 'close'
+ const tabsToRemove = tabs.value.filter(tab => !remainTabIds.includes(tab.id));
+ const routeKeysToReset: RouteKey[] = [];
+
+ for (const tab of tabsToRemove) {
+ routeKeysToReset.push(tab.routeKey);
+ }
+
+ const removedTabsIds = tabsToRemove.map(tab => tab.id);
+
+ // If no tabs are actually being removed based on excludes and fixed tabs, exit
+ if (removedTabsIds.length === 0) {
+ return;
+ }
+
+ const isRemoveActiveTab = removedTabsIds.includes(activeTabId.value);
+ // filterTabsByIds returns tabs NOT in removedTabsIds, so these are the tabs that will remain
+ const updatedTabs = filterTabsByIds(removedTabsIds, tabs.value);
+
+ if (clearCache) {
+ // 娓呴櫎缂撳瓨
+ removedTabsIds.forEach(tabId => {
+ const tab = tabs.value.find(t => t.id === tabId);
+ if (tab) {
+ routeStore.resetRouteCache(tab.routeKey);
+ }
+ });
+ }
+
+ function update() {
+ tabs.value = updatedTabs;
+ }
+
+ if (!isRemoveActiveTab) {
+ update();
+ } else {
+ const activeTabCandidate = updatedTabs[updatedTabs.length - 1] || homeTab.value;
+
+ if (activeTabCandidate) {
+ // Ensure there's a tab to switch to
+ await switchRouteByTab(activeTabCandidate);
+ }
+ // Update the tabs array regardless of switch success or if a candidate was found
+ update();
+ }
+
+ // After tabs are updated and route potentially switched, reset cache for removed tabs
+ for (const routeKey of routeKeysToReset) {
+ routeStore.resetRouteCache(routeKey);
+ }
+ }
+
+ const { routerPushByKey } = useRouterPush();
+ /**
+ * Replace tab
+ *
+ * @param key Route key
+ * @param options Router push options
+ */
+ async function replaceTab(key: RouteKey, options?: App.Global.RouterPushOptions) {
+ const oldTabId = activeTabId.value;
+
+ // push new route
+ await routerPushByKey(key, options);
+
+ // remove old tab (exclude fixed tab)
+ if (!isTabRetain(oldTabId)) {
+ await removeTab(oldTabId);
+ }
+ }
+
+ /**
+ * Switch route by tab
+ *
+ * @param tab
+ */
+ async function switchRouteByTab(tab: App.Global.Tab) {
+ const fail = await routerPush(tab.fullPath);
+ if (!fail) {
+ setActiveTabId(tab.id);
+ }
+ }
+
+ /**
+ * Clear left tabs
+ *
+ * @param tabId
+ */
+ async function clearLeftTabs(tabId: string) {
+ const tabIds = tabs.value.map(tab => tab.id);
+ const index = tabIds.indexOf(tabId);
+ if (index === -1) return;
+
+ const excludes = tabIds.slice(index);
+ await clearTabs(excludes);
+ }
+
+ /**
+ * Clear right tabs
+ *
+ * @param tabId
+ */
+ async function clearRightTabs(tabId: string) {
+ const isHomeTab = tabId === homeTab.value?.id;
+ if (isHomeTab) {
+ clearTabs();
+ return;
+ }
+
+ const tabIds = tabs.value.map(tab => tab.id);
+ const index = tabIds.indexOf(tabId);
+ if (index === -1) return;
+
+ const excludes = tabIds.slice(0, index + 1);
+ await clearTabs(excludes);
+ }
+
+ /**
+ * Fix tab
+ *
+ * @param tabId
+ */
+ function fixTab(tabId: string) {
+ const tabIndex = tabs.value.findIndex(t => t.id === tabId);
+ if (tabIndex === -1) return;
+
+ const tab = tabs.value[tabIndex];
+ const fixedCount = getFixedTabIds(tabs.value).length;
+ tab.fixedIndex = fixedCount;
+
+ if (tabIndex !== fixedCount) {
+ tabs.value.splice(tabIndex, 1);
+ tabs.value.splice(fixedCount, 0, tab);
+ }
+
+ reorderFixedTabs(tabs.value);
+ }
+
+ /**
+ * Unfix tab
+ *
+ * @param tabId
+ */
+ function unfixTab(tabId: string) {
+ const tabIndex = tabs.value.findIndex(t => t.id === tabId);
+ if (tabIndex === -1) return;
+
+ const tab = tabs.value[tabIndex];
+ tab.fixedIndex = undefined;
+
+ const fixedCount = getFixedTabIds(tabs.value).length;
+ if (tabIndex !== fixedCount) {
+ tabs.value.splice(tabIndex, 1);
+ tabs.value.splice(fixedCount, 0, tab);
+ }
+
+ reorderFixedTabs(tabs.value);
+ }
+
+ /**
+ * Set new label of tab
+ *
+ * @default activeTabId
+ * @param label New tab label
+ * @param tabId Tab id
+ */
+ function setTabLabel(label: string, tabId?: string) {
+ const id = tabId || activeTabId.value;
+
+ const tab = tabs.value.find(item => item.id === id);
+ if (!tab) return;
+
+ tab.oldLabel = tab.label;
+ tab.newLabel = label;
+ }
+
+ /**
+ * Reset tab label
+ *
+ * @default activeTabId
+ * @param tabId Tab id
+ */
+ function resetTabLabel(tabId?: string) {
+ const id = tabId || activeTabId.value;
+
+ const tab = tabs.value.find(item => item.id === id);
+ if (!tab) return;
+
+ tab.newLabel = undefined;
+ }
+
+ /**
+ * Is tab retain
+ *
+ * @param tabId
+ */
+ function isTabRetain(tabId: string) {
+ if (tabId === homeTab.value?.id) return true;
+
+ const fixedTabIds = getFixedTabIds(tabs.value);
+
+ return fixedTabIds.includes(tabId);
+ }
+
+ /** Update tabs by locale */
+ function updateTabsByLocale() {
+ tabs.value = updateTabsByI18nKey(tabs.value);
+
+ if (homeTab.value) {
+ homeTab.value = updateTabByI18nKey(homeTab.value);
+ }
+ }
+
+ /** Cache tabs */
+ function cacheTabs() {
+ if (!themeStore.tab.cache) return;
+
+ localStg.set('globalTabs', tabs.value);
+ }
+
+ // cache tabs when page is closed or refreshed
+ useEventListener(window, 'beforeunload', () => {
+ cacheTabs();
+ });
+
+ return {
+ /** All tabs */
+ tabs: allTabs,
+ activeTabId,
+ homeTab,
+ initHomeTab,
+ initTabStore,
+ addTab,
+ removeTab,
+ removeActiveTab,
+ removeTabByRouteName,
+ replaceTab,
+ clearTabs,
+ clearLeftTabs,
+ clearRightTabs,
+ fixTab,
+ unfixTab,
+ switchRouteByTab,
+ setTabLabel,
+ resetTabLabel,
+ isTabRetain,
+ updateTabsByLocale,
+ getTabIdByRoute,
+ cacheTabs
+ };
+});
diff --git a/ruoyi-plus-soybean/src/store/modules/tab/shared.ts b/ruoyi-plus-soybean/src/store/modules/tab/shared.ts
new file mode 100755
index 0000000..48880a3
--- /dev/null
+++ b/ruoyi-plus-soybean/src/store/modules/tab/shared.ts
@@ -0,0 +1,263 @@
+import type { Router } from 'vue-router';
+import type { LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types';
+import { $t } from '@/locales';
+import { getRoutePath } from '@/router/elegant/transform';
+
+/**
+ * Get all tabs
+ *
+ * @param tabs Tabs
+ * @param homeTab Home tab
+ */
+export function getAllTabs(tabs: App.Global.Tab[], homeTab?: App.Global.Tab) {
+ if (!homeTab) {
+ return [];
+ }
+
+ const filterHomeTabs = tabs.filter(tab => tab.id !== homeTab.id);
+
+ const fixedTabs = filterHomeTabs.filter(isFixedTab).sort((a, b) => a.fixedIndex! - b.fixedIndex!);
+
+ const remainTabs = filterHomeTabs.filter(tab => !isFixedTab(tab));
+
+ const allTabs = [homeTab, ...fixedTabs, ...remainTabs];
+
+ return updateTabsLabel(allTabs);
+}
+
+/**
+ * Is fixed tab
+ *
+ * @param tab
+ */
+function isFixedTab(tab: App.Global.Tab) {
+ return tab.fixedIndex !== undefined && tab.fixedIndex !== null;
+}
+
+/**
+ * Get tab id by route
+ *
+ * @param route
+ */
+export function getTabIdByRoute(route: App.Global.TabRoute) {
+ const { path, query = {}, meta } = route;
+
+ let id = path;
+
+ if (meta.multiTab) {
+ const queryKeys = Object.keys(query).sort();
+ const qs = queryKeys.map(key => `${key}=${query[key]}`).join('&');
+
+ id = `${path}?${qs}`;
+ }
+
+ return id;
+}
+
+/**
+ * Get tab by route
+ *
+ * @param route
+ */
+export function getTabByRoute(route: App.Global.TabRoute) {
+ const { name, path, fullPath = path, meta } = route;
+
+ const { title, i18nKey, fixedIndexInTab } = meta;
+
+ // Get icon and localIcon from getRouteIcons function
+ const { icon, localIcon } = getRouteIcons(route);
+
+ const label = i18nKey ? $t(i18nKey) : title;
+
+ const tab: App.Global.Tab = {
+ id: getTabIdByRoute(route),
+ label,
+ routeKey: name as LastLevelRouteKey,
+ routePath: path as RouteMap[LastLevelRouteKey],
+ fullPath,
+ fixedIndex: fixedIndexInTab,
+ icon,
+ localIcon,
+ i18nKey
+ };
+
+ return tab;
+}
+
+/**
+ * The vue router will automatically merge the meta of all matched items, and the icons here may be affected by other
+ * matching items, so they need to be processed separately
+ *
+ * @param route
+ */
+export function getRouteIcons(route: App.Global.TabRoute) {
+ // Set default value for icon at the beginning
+ let icon: string = route?.meta?.icon || import.meta.env.VITE_MENU_ICON;
+ let localIcon: string | undefined = route?.meta?.localIcon;
+
+ // Route.matched only appears when there are multiple matches,so check if route.matched exists
+ if (route.matched) {
+ // Find the meta of the current route from matched
+ const currentRoute = route.matched.find(r => r.name === route.name);
+ // If icon exists in currentRoute.meta, it will overwrite the default value
+ icon = currentRoute?.meta?.icon || icon;
+ localIcon = currentRoute?.meta?.localIcon;
+ }
+
+ return { icon, localIcon };
+}
+
+/**
+ * Get default home tab
+ *
+ * @param router
+ * @param homeRouteName routeHome in useRouteStore
+ */
+export function getDefaultHomeTab(router: Router, homeRouteName: LastLevelRouteKey) {
+ const homeRoutePath = getRoutePath(homeRouteName);
+ const i18nLabel = $t(`route.${homeRouteName}`);
+
+ let homeTab: App.Global.Tab = {
+ id: getRoutePath(homeRouteName),
+ label: i18nLabel || homeRouteName,
+ routeKey: homeRouteName,
+ routePath: homeRoutePath,
+ fullPath: homeRoutePath
+ };
+
+ const routes = router.getRoutes();
+ const homeRoute = routes.find(route => route.name === homeRouteName);
+ if (homeRoute) {
+ homeTab = getTabByRoute(homeRoute);
+ }
+
+ return homeTab;
+}
+
+/**
+ * Is tab in tabs
+ *
+ * @param tab
+ * @param tabs
+ */
+export function isTabInTabs(tabId: string, tabs: App.Global.Tab[]) {
+ return tabs.some(tab => tab.id === tabId);
+}
+
+/**
+ * Filter tabs by id
+ *
+ * @param tabId
+ * @param tabs
+ */
+export function filterTabsById(tabId: string, tabs: App.Global.Tab[]) {
+ return tabs.filter(tab => tab.id !== tabId);
+}
+
+/**
+ * Filter tabs by ids
+ *
+ * @param tabIds
+ * @param tabs
+ */
+export function filterTabsByIds(tabIds: string[], tabs: App.Global.Tab[]) {
+ return tabs.filter(tab => !tabIds.includes(tab.id));
+}
+
+/**
+ * extract tabs by all routes
+ *
+ * @param router
+ * @param tabs
+ */
+export function extractTabsByAllRoutes(router: Router, tabs: App.Global.Tab[]) {
+ const routes = router.getRoutes();
+
+ const routeNames = routes.map(route => route.name);
+
+ return tabs.filter(tab => routeNames.includes(tab.routeKey));
+}
+
+/**
+ * Get fixed tabs
+ *
+ * @param tabs
+ */
+export function getFixedTabs(tabs: App.Global.Tab[]) {
+ return tabs.filter(isFixedTab);
+}
+
+/**
+ * Get fixed tab ids
+ *
+ * @param tabs
+ */
+export function getFixedTabIds(tabs: App.Global.Tab[]) {
+ const fixedTabs = getFixedTabs(tabs);
+
+ return fixedTabs.map(tab => tab.id);
+}
+
+/**
+ * Reorder fixed tabs fixedIndex
+ *
+ * @param tabs
+ */
+export function reorderFixedTabs(tabs: App.Global.Tab[]) {
+ const fixedTabs = getFixedTabs(tabs);
+ fixedTabs.forEach((t, i) => {
+ t.fixedIndex = i;
+ });
+}
+
+/**
+ * Update tabs label
+ *
+ * @param tabs
+ */
+function updateTabsLabel(tabs: App.Global.Tab[]) {
+ const updated = tabs.map(tab => ({
+ ...tab,
+ label: tab.newLabel || tab.oldLabel || tab.label
+ }));
+
+ return updated;
+}
+
+/**
+ * Update tab by i18n key
+ *
+ * @param tab
+ */
+export function updateTabByI18nKey(tab: App.Global.Tab) {
+ const { i18nKey, label } = tab;
+
+ return {
+ ...tab,
+ label: i18nKey ? $t(i18nKey) : label
+ };
+}
+
+/**
+ * Update tabs by i18n key
+ *
+ * @param tabs
+ */
+export function updateTabsByI18nKey(tabs: App.Global.Tab[]) {
+ return tabs.map(tab => updateTabByI18nKey(tab));
+}
+
+/**
+ * find tab by route name
+ *
+ * @param name
+ * @param tabs
+ */
+export function findTabByRouteName(name: RouteKey, tabs: App.Global.Tab[]) {
+ const routePath = getRoutePath(name);
+
+ const tabId = routePath;
+ const multiTabId = `${routePath}?`;
+
+ return tabs.find(tab => tab.id === tabId || tab.id.startsWith(multiTabId));
+}
diff --git a/ruoyi-plus-soybean/src/store/modules/theme/index.ts b/ruoyi-plus-soybean/src/store/modules/theme/index.ts
new file mode 100755
index 0000000..08199fd
--- /dev/null
+++ b/ruoyi-plus-soybean/src/store/modules/theme/index.ts
@@ -0,0 +1,303 @@
+import { computed, effectScope, onScopeDispose, ref, toRefs, watch } from 'vue';
+import type { Ref } from 'vue';
+import { useDateFormat, useEventListener, useNow, usePreferredColorScheme } from '@vueuse/core';
+import { defineStore } from 'pinia';
+import { getPaletteColorByNumber } from '@sa/color';
+import { localStg } from '@/utils/storage';
+import { SetupStoreId } from '@/enum';
+import { useAuthStore } from '../auth';
+import {
+ addThemeVarsToGlobal,
+ createThemeToken,
+ getNaiveTheme,
+ initThemeSettings,
+ toggleAuxiliaryColorModes,
+ toggleCssDarkMode
+} from './shared';
+
+/** Theme store */
+export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
+ const scope = effectScope();
+ const osTheme = usePreferredColorScheme();
+ const authStore = useAuthStore();
+
+ /** Theme settings */
+ const settings: Ref<App.Theme.ThemeSetting> = ref(initThemeSettings());
+
+ /** Optional NaiveUI theme overrides from preset */
+ const naiveThemeOverrides: Ref<App.Theme.NaiveUIThemeOverride | undefined> = ref(undefined);
+
+ /** Watermark time instance with controls */
+ const { now: watermarkTime, pause: pauseWatermarkTime, resume: resumeWatermarkTime } = useNow({ controls: true });
+
+ /** Dark mode */
+ const darkMode = computed(() => {
+ if (settings.value.themeScheme === 'auto') {
+ return osTheme.value === 'dark';
+ }
+ return settings.value.themeScheme === 'dark';
+ });
+
+ /** grayscale mode */
+ const grayscaleMode = computed(() => settings.value.grayscale);
+
+ /** colourWeakness mode */
+ const colourWeaknessMode = computed(() => settings.value.colourWeakness);
+
+ /** Theme colors */
+ const themeColors = computed(() => {
+ const { themeColor, otherColor, isInfoFollowPrimary } = settings.value;
+ const colors: App.Theme.ThemeColor = {
+ primary: themeColor,
+ ...otherColor,
+ info: isInfoFollowPrimary ? themeColor : otherColor.info
+ };
+ return colors;
+ });
+
+ /** Naive theme */
+ const naiveTheme = computed(() => getNaiveTheme(themeColors.value, settings.value, naiveThemeOverrides.value));
+
+ /**
+ * Settings json
+ *
+ * It is for copy settings
+ */
+ const settingsJson = computed(() => JSON.stringify(settings.value));
+
+ /** Watermark time date formatter */
+ const formattedWatermarkTime = computed(() => {
+ const { watermark } = settings.value;
+ const date = useDateFormat(watermarkTime, watermark.timeFormat);
+ return date.value;
+ });
+
+ /** Watermark content */
+ const watermarkContent = computed(() => {
+ const { watermark } = settings.value;
+ let content = watermark.text;
+
+ if (watermark.enableUserName && authStore.userInfo.user?.userName) {
+ content = `${authStore.userInfo.user.userName}@${content}`;
+ }
+
+ if (watermark.enableTime) {
+ content = `${content} ${formattedWatermarkTime.value}`;
+ }
+
+ return content;
+ });
+
+ /** Reset store */
+ function resetStore() {
+ const themeStore = useThemeStore();
+
+ themeStore.$reset();
+ }
+
+ /**
+ * Set theme scheme
+ *
+ * @param themeScheme
+ */
+ function setThemeScheme(themeScheme: UnionKey.ThemeScheme) {
+ settings.value.themeScheme = themeScheme;
+ }
+
+ /**
+ * Set grayscale value
+ *
+ * @param isGrayscale
+ */
+ function setGrayscale(isGrayscale: boolean) {
+ settings.value.grayscale = isGrayscale;
+ }
+
+ /**
+ * Set colourWeakness value
+ *
+ * @param isColourWeakness
+ */
+ function setColourWeakness(isColourWeakness: boolean) {
+ settings.value.colourWeakness = isColourWeakness;
+ }
+
+ /** Toggle theme scheme */
+ function toggleThemeScheme() {
+ const themeSchemes: UnionKey.ThemeScheme[] = ['light', 'dark', 'auto'];
+
+ const index = themeSchemes.findIndex(item => item === settings.value.themeScheme);
+
+ const nextIndex = index === themeSchemes.length - 1 ? 0 : index + 1;
+
+ const nextThemeScheme = themeSchemes[nextIndex];
+
+ setThemeScheme(nextThemeScheme);
+ }
+
+ /**
+ * Update theme colors
+ *
+ * @param key Theme color key
+ * @param color Theme color
+ */
+ function updateThemeColors(key: App.Theme.ThemeColorKey, color: string) {
+ let colorValue = color;
+
+ if (settings.value.recommendColor) {
+ // get a color palette by provided color and color name, and use the suitable color
+
+ colorValue = getPaletteColorByNumber(color, 500, true);
+ }
+
+ if (key === 'primary') {
+ settings.value.themeColor = colorValue;
+ } else {
+ settings.value.otherColor[key] = colorValue;
+ }
+ }
+
+ /**
+ * Set theme layout
+ *
+ * @param mode Theme layout mode
+ */
+ function setThemeLayout(mode: UnionKey.ThemeLayoutMode) {
+ settings.value.layout.mode = mode;
+ }
+
+ /** Setup theme vars to global */
+ function setupThemeVarsToGlobal() {
+ const { themeTokens, darkThemeTokens } = createThemeToken(
+ themeColors.value,
+ settings.value.tokens,
+ settings.value.recommendColor
+ );
+ addThemeVarsToGlobal(themeTokens, darkThemeTokens);
+ }
+
+ /**
+ * Set watermark enable user name
+ *
+ * @param enable Whether to enable user name watermark
+ */
+ function setWatermarkEnableUserName(enable: boolean) {
+ settings.value.watermark.enableUserName = enable;
+
+ if (enable) {
+ // settings.value.watermark.enableTime = false;
+ }
+ }
+
+ /**
+ * Set watermark enable time
+ *
+ * @param enable Whether to enable time watermark
+ */
+ function setWatermarkEnableTime(enable: boolean) {
+ settings.value.watermark.enableTime = enable;
+
+ if (enable) {
+ // settings.value.watermark.enableUserName = false;
+ }
+ }
+
+ /**
+ * Set NaiveUI theme overrides
+ *
+ * @param overrides NaiveUI theme overrides or undefined to clear
+ */
+ function setNaiveThemeOverrides(overrides?: App.Theme.NaiveUIThemeOverride) {
+ naiveThemeOverrides.value = overrides;
+ }
+
+ /** Only run timer when watermark is visible and time display is enabled */
+ function updateWatermarkTimer() {
+ const { watermark } = settings.value;
+ const shouldRunTimer = watermark.visible && watermark.enableTime;
+
+ if (shouldRunTimer) {
+ resumeWatermarkTime();
+ } else {
+ pauseWatermarkTime();
+ }
+ }
+
+ /** Cache theme settings */
+ function cacheThemeSettings() {
+ const isProd = import.meta.env.PROD;
+
+ if (!isProd) return;
+
+ localStg.set('themeSettings', settings.value);
+ }
+
+ // cache theme settings when page is closed or refreshed
+ useEventListener(window, 'beforeunload', () => {
+ cacheThemeSettings();
+ });
+
+ // watch store
+ scope.run(() => {
+ // watch dark mode
+ watch(
+ darkMode,
+ val => {
+ toggleCssDarkMode(val);
+ localStg.set('darkMode', val);
+ },
+ { immediate: true }
+ );
+
+ watch(
+ [grayscaleMode, colourWeaknessMode],
+ val => {
+ toggleAuxiliaryColorModes(val[0], val[1]);
+ },
+ { immediate: true }
+ );
+
+ // themeColors change, update css vars and storage theme color
+ watch(
+ themeColors,
+ val => {
+ setupThemeVarsToGlobal();
+ localStg.set('themeColor', val.primary);
+ },
+ { immediate: true }
+ );
+
+ // watch watermark settings to control timer
+ watch(
+ () => [settings.value.watermark.visible, settings.value.watermark.enableTime],
+ () => {
+ updateWatermarkTimer();
+ },
+ { immediate: true }
+ );
+ });
+
+ /** On scope dispose */
+ onScopeDispose(() => {
+ scope.stop();
+ });
+
+ return {
+ ...toRefs(settings.value),
+ darkMode,
+ themeColors,
+ naiveTheme,
+ settingsJson,
+ watermarkContent,
+ setGrayscale,
+ setColourWeakness,
+ resetStore,
+ setThemeScheme,
+ toggleThemeScheme,
+ updateThemeColors,
+ setThemeLayout,
+ setWatermarkEnableUserName,
+ setWatermarkEnableTime,
+ setNaiveThemeOverrides
+ };
+});
diff --git a/ruoyi-plus-soybean/src/store/modules/theme/shared.ts b/ruoyi-plus-soybean/src/store/modules/theme/shared.ts
new file mode 100755
index 0000000..769382c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/store/modules/theme/shared.ts
@@ -0,0 +1,269 @@
+import type { GlobalThemeOverrides } from 'naive-ui';
+import { defu } from 'defu';
+import { addColorAlpha, getColorPalette, getPaletteColorByNumber, getRgb } from '@sa/color';
+import { DARK_CLASS } from '@/constants/app';
+import { toggleHtmlClass } from '@/utils/common';
+import { localStg } from '@/utils/storage';
+import { overrideThemeSettings, themeSettings } from '@/theme/settings';
+import { themeVars } from '@/theme/vars';
+
+/** Init theme settings */
+export function initThemeSettings() {
+ const isProd = import.meta.env.PROD;
+
+ // if it is development mode, the theme settings will not be cached, by update `themeSettings` in `src/theme/settings.ts` to update theme settings
+ if (!isProd) return themeSettings;
+
+ // if it is production mode, the theme settings will be cached in localStorage
+ // if want to update theme settings when publish new version, please update `overrideThemeSettings` in `src/theme/settings.ts`
+
+ const localSettings = localStg.get('themeSettings');
+
+ let settings = defu(localSettings, themeSettings);
+
+ const isOverride = localStg.get('overrideThemeFlag') === BUILD_TIME;
+
+ if (!isOverride) {
+ settings = defu(overrideThemeSettings, settings);
+
+ localStg.set('overrideThemeFlag', BUILD_TIME);
+ }
+
+ return settings;
+}
+
+/**
+ * create theme token css vars value by theme settings
+ *
+ * @param colors Theme colors
+ * @param tokens Theme setting tokens
+ * @param [recommended=false] Use recommended color. Default is `false`
+ */
+export function createThemeToken(
+ colors: App.Theme.ThemeColor,
+ tokens?: App.Theme.ThemeSetting['tokens'],
+ recommended = false
+) {
+ const paletteColors = createThemePaletteColors(colors, recommended);
+
+ const { light, dark } = tokens || themeSettings.tokens;
+
+ const themeTokens: App.Theme.ThemeTokenCSSVars = {
+ colors: {
+ ...paletteColors,
+ nprogress: paletteColors.primary,
+ ...light.colors
+ },
+ boxShadow: {
+ ...light.boxShadow
+ }
+ };
+
+ const darkThemeTokens: App.Theme.ThemeTokenCSSVars = {
+ colors: {
+ ...themeTokens.colors,
+ ...dark?.colors
+ },
+ boxShadow: {
+ ...themeTokens.boxShadow,
+ ...dark?.boxShadow
+ }
+ };
+
+ return {
+ themeTokens,
+ darkThemeTokens
+ };
+}
+
+/**
+ * Create theme palette colors
+ *
+ * @param colors Theme colors
+ * @param [recommended=false] Use recommended color. Default is `false`
+ */
+function createThemePaletteColors(colors: App.Theme.ThemeColor, recommended = false) {
+ const colorKeys = Object.keys(colors) as App.Theme.ThemeColorKey[];
+ const colorPaletteVar = {} as App.Theme.ThemePaletteColor;
+
+ colorKeys.forEach(key => {
+ const colorMap = getColorPalette(colors[key], recommended);
+
+ colorPaletteVar[key] = colorMap.get(500)!;
+
+ colorMap.forEach((hex, number) => {
+ colorPaletteVar[`${key}-${number}`] = hex;
+ });
+ });
+
+ return colorPaletteVar;
+}
+
+/**
+ * Get css var by tokens
+ *
+ * @param tokens Theme base tokens
+ */
+function getCssVarByTokens(tokens: App.Theme.BaseToken) {
+ const styles: string[] = [];
+
+ function removeVarPrefix(value: string) {
+ return value.replace('var(', '').replace(')', '');
+ }
+
+ function removeRgbPrefix(value: string) {
+ return value.replace('rgb(', '').replace(')', '');
+ }
+
+ for (const [key, tokenValues] of Object.entries(themeVars)) {
+ for (const [tokenKey, tokenValue] of Object.entries(tokenValues)) {
+ let cssVarsKey = removeVarPrefix(tokenValue);
+ let cssValue = tokens[key][tokenKey];
+
+ if (key === 'colors') {
+ cssVarsKey = removeRgbPrefix(cssVarsKey);
+ const { r, g, b } = getRgb(cssValue);
+ cssValue = `${r} ${g} ${b}`;
+ }
+
+ styles.push(`${cssVarsKey}: ${cssValue}`);
+ }
+ }
+
+ const styleStr = styles.join(';');
+
+ return styleStr;
+}
+
+/**
+ * Add theme vars to global
+ *
+ * @param tokens
+ */
+export function addThemeVarsToGlobal(tokens: App.Theme.BaseToken, darkTokens: App.Theme.BaseToken) {
+ const cssVarStr = getCssVarByTokens(tokens);
+ const darkCssVarStr = getCssVarByTokens(darkTokens);
+
+ const css = `
+ :root {
+ ${cssVarStr}
+ }
+ `;
+
+ const darkCss = `
+ html.${DARK_CLASS} {
+ ${darkCssVarStr}
+ }
+ `;
+
+ const styleId = 'theme-vars';
+
+ const style = document.querySelector(`#${styleId}`) || document.createElement('style');
+
+ style.id = styleId;
+
+ style.textContent = css + darkCss;
+
+ document.head.appendChild(style);
+}
+
+/**
+ * Toggle css dark mode
+ *
+ * @param darkMode Is dark mode
+ */
+export function toggleCssDarkMode(darkMode = false) {
+ const { add, remove } = toggleHtmlClass(DARK_CLASS);
+
+ if (darkMode) {
+ add();
+ } else {
+ remove();
+ }
+}
+
+/**
+ * Toggle auxiliary color modes
+ *
+ * @param grayscaleMode
+ * @param colourWeakness
+ */
+export function toggleAuxiliaryColorModes(grayscaleMode = false, colourWeakness = false) {
+ const htmlElement = document.documentElement;
+ htmlElement.style.filter = [grayscaleMode ? 'grayscale(100%)' : '', colourWeakness ? 'invert(80%)' : '']
+ .filter(Boolean)
+ .join(' ');
+}
+
+type NaiveColorScene = '' | 'Suppl' | 'Hover' | 'Pressed' | 'Active';
+type NaiveColorKey = `${App.Theme.ThemeColorKey}Color${NaiveColorScene}`;
+type NaiveThemeColor = Partial<Record<NaiveColorKey, string>>;
+interface NaiveColorAction {
+ scene: NaiveColorScene;
+ handler: (color: string) => string;
+}
+
+/**
+ * Get naive theme colors
+ *
+ * @param colors Theme colors
+ * @param [recommended=false] Use recommended color. Default is `false`
+ */
+function getNaiveThemeColors(colors: App.Theme.ThemeColor, recommended = false) {
+ const colorActions: NaiveColorAction[] = [
+ { scene: '', handler: color => color },
+ { scene: 'Suppl', handler: color => color },
+ { scene: 'Hover', handler: color => getPaletteColorByNumber(color, 500, recommended) },
+ { scene: 'Pressed', handler: color => getPaletteColorByNumber(color, 700, recommended) },
+ { scene: 'Active', handler: color => addColorAlpha(color, 0.1) }
+ ];
+
+ const themeColors: NaiveThemeColor = {};
+
+ const colorEntries = Object.entries(colors) as [App.Theme.ThemeColorKey, string][];
+
+ colorEntries.forEach(color => {
+ colorActions.forEach(action => {
+ const [colorType, colorValue] = color;
+ const colorKey: NaiveColorKey = `${colorType}Color${action.scene}`;
+ themeColors[colorKey] = action.handler(colorValue);
+ });
+ });
+
+ return themeColors;
+}
+
+/**
+ * Get naive theme
+ *
+ * @param colors Theme colors
+ * @param settings Theme settings object
+ * @param overrides Optional manual overrides from preset
+ */
+export function getNaiveTheme(
+ colors: App.Theme.ThemeColor,
+ settings: App.Theme.ThemeSetting,
+ overrides?: GlobalThemeOverrides
+) {
+ const { primary: colorLoading } = colors;
+
+ const theme: GlobalThemeOverrides = {
+ common: {
+ ...getNaiveThemeColors(colors, settings.recommendColor),
+ borderRadius: `${settings.themeRadius}px`
+ },
+ LoadingBar: {
+ colorLoading
+ },
+ Tag: {
+ borderRadius: `${settings.themeRadius}px`
+ },
+ Button: {
+ textColorPrimary: '#ffffff'
+ }
+ };
+
+ // If there are overrides, merge them with priority
+ // overrides has higher priority than auto-generated theme
+ return overrides ? defu(overrides, theme) : theme;
+}
diff --git a/ruoyi-plus-soybean/src/store/plugins/index.ts b/ruoyi-plus-soybean/src/store/plugins/index.ts
new file mode 100755
index 0000000..dd929d4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/store/plugins/index.ts
@@ -0,0 +1,22 @@
+import type { PiniaPluginContext } from 'pinia';
+import { jsonClone } from '@sa/utils';
+import { SetupStoreId } from '@/enum';
+
+/**
+ * The plugin reset the state of the store which is written by setup syntax
+ *
+ * @param context
+ */
+export function resetSetupStore(context: PiniaPluginContext) {
+ const setupSyntaxIds = Object.values(SetupStoreId) as string[];
+
+ if (setupSyntaxIds.includes(context.store.$id)) {
+ const { $state } = context.store;
+
+ const defaultStore = jsonClone($state);
+
+ context.store.$reset = () => {
+ context.store.$patch(defaultStore);
+ };
+ }
+}
diff --git a/ruoyi-plus-soybean/src/styles/css/global.css b/ruoyi-plus-soybean/src/styles/css/global.css
new file mode 100755
index 0000000..a7ceba0
--- /dev/null
+++ b/ruoyi-plus-soybean/src/styles/css/global.css
@@ -0,0 +1,16 @@
+@import './reset.css';
+@import './nprogress.css';
+@import './transition.css';
+
+html,
+body,
+#app {
+ height: 100%;
+ font-family: inherit;
+ text-autospace: normal;
+}
+
+html {
+ overflow-x: hidden;
+ color: rgb(var(--base-text-color));
+}
diff --git a/ruoyi-plus-soybean/src/styles/css/nprogress.css b/ruoyi-plus-soybean/src/styles/css/nprogress.css
new file mode 100755
index 0000000..3e631b5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/styles/css/nprogress.css
@@ -0,0 +1,83 @@
+/* Make clicks pass-through */
+#nprogress {
+ pointer-events: none;
+}
+
+#nprogress .bar {
+ background: rgb(var(--nprogress-color));
+
+ position: fixed;
+ z-index: 1031;
+ top: 0;
+ left: 0;
+
+ width: 100%;
+ height: 2px;
+}
+
+/* Fancy blur effect */
+#nprogress .peg {
+ display: block;
+ position: absolute;
+ right: 0px;
+ width: 100px;
+ height: 100%;
+ box-shadow:
+ 0 0 10px rgb(var(--nprogress-color)),
+ 0 0 5px rgb(var(--nprogress-color));
+ opacity: 1;
+
+ -webkit-transform: rotate(3deg) translate(0px, -4px);
+ -ms-transform: rotate(3deg) translate(0px, -4px);
+ transform: rotate(3deg) translate(0px, -4px);
+}
+
+/* Remove these to get rid of the spinner */
+#nprogress .spinner {
+ display: block;
+ position: fixed;
+ z-index: 1031;
+ top: 15px;
+ right: 15px;
+}
+
+#nprogress .spinner-icon {
+ width: 18px;
+ height: 18px;
+ box-sizing: border-box;
+
+ border: solid 2px transparent;
+ border-top-color: rgb(var(--nprogress-color));
+ border-left-color: rgb(var(--nprogress-color));
+ border-radius: 50%;
+
+ -webkit-animation: nprogress-spinner 400ms linear infinite;
+ animation: nprogress-spinner 400ms linear infinite;
+}
+
+.nprogress-custom-parent {
+ overflow: hidden;
+ position: relative;
+}
+
+.nprogress-custom-parent #nprogress .spinner,
+.nprogress-custom-parent #nprogress .bar {
+ position: absolute;
+}
+
+@-webkit-keyframes nprogress-spinner {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ }
+}
+@keyframes nprogress-spinner {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/ruoyi-plus-soybean/src/styles/css/reset.css b/ruoyi-plus-soybean/src/styles/css/reset.css
new file mode 100755
index 0000000..77a0035
--- /dev/null
+++ b/ruoyi-plus-soybean/src/styles/css/reset.css
@@ -0,0 +1,385 @@
+/*
+1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
+2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
+2. [UnoCSS]: allow to override the default border color with css var `--un-default-border-color`
+*/
+
+*,
+::before,
+::after {
+ box-sizing: border-box; /* 1 */
+ border-width: 0; /* 2 */
+ border-style: solid; /* 2 */
+ border-color: var(--un-default-border-color, #e5e7eb); /* 2 */
+}
+
+/*
+* [Naive UI] Fix the icon size in the image preview toolbar
+*/
+.n-image-preview-toolbar .n-base-icon {
+ box-sizing: unset !important;
+}
+
+/*
+1. Use a consistent sensible line-height in all browsers.
+2. Prevent adjustments of font size after orientation changes in iOS.
+3. Use a more readable tab size.
+4. Use the user's configured `sans` font-family by default.
+*/
+
+html {
+ line-height: 1.5; /* 1 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+ -moz-tab-size: 4; /* 3 */
+ tab-size: 4; /* 3 */
+ font-family:
+ ui-sans-serif,
+ system-ui,
+ -apple-system,
+ BlinkMacSystemFont,
+ 'Segoe UI',
+ Roboto,
+ 'Helvetica Neue',
+ Arial,
+ 'Noto Sans',
+ sans-serif,
+ 'Apple Color Emoji',
+ 'Segoe UI Emoji',
+ 'Segoe UI Symbol',
+ 'Noto Color Emoji'; /* 4 */
+}
+
+/*
+1. Remove the margin in all browsers.
+2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
+*/
+
+body {
+ margin: 0; /* 1 */
+ line-height: inherit; /* 2 */
+}
+
+/*
+1. Add the correct height in Firefox.
+2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+3. Ensure horizontal rules are visible by default.
+*/
+
+hr {
+ height: 0; /* 1 */
+ color: inherit; /* 2 */
+ border-top-width: 1px; /* 3 */
+}
+
+/*
+Add the correct text decoration in Chrome, Edge, and Safari.
+*/
+
+abbr:where([title]) {
+ text-decoration: underline dotted;
+}
+
+/*
+Remove the default font size and weight for headings.
+*/
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-size: inherit;
+ font-weight: inherit;
+}
+
+/*
+Reset links to optimize for opt-in styling instead of opt-out.
+*/
+
+a {
+ color: inherit;
+ text-decoration: inherit;
+}
+
+/*
+Add the correct font weight in Edge and Safari.
+*/
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/*
+1. Use the user's configured `mono` font family by default.
+2. Correct the odd `em` font sizing in all browsers.
+*/
+
+code,
+kbd,
+samp,
+pre {
+ font-family:
+ ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/*
+Add the correct font size in all browsers.
+*/
+
+small {
+ font-size: 80%;
+}
+
+/*
+Prevent `sub` and `sup` elements from affecting the line height in all browsers.
+*/
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/*
+1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+3. Remove gaps between table borders by default.
+*/
+
+table {
+ text-indent: 0; /* 1 */
+ border-color: inherit; /* 2 */
+ border-collapse: collapse; /* 3 */
+}
+
+/*
+1. Change the font styles in all browsers.
+2. Remove the margin in Firefox and Safari.
+3. Remove default padding in all browsers.
+*/
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-feature-settings: inherit; /* 1 */
+ font-variation-settings: inherit; /* 1 */
+ font-size: 100%; /* 1 */
+ font-weight: inherit; /* 1 */
+ line-height: inherit; /* 1 */
+ color: inherit; /* 1 */
+ margin: 0; /* 2 */
+ padding: 0; /* 3 */
+}
+
+/*
+Remove the inheritance of text transform in Edge and Firefox.
+*/
+
+button,
+select {
+ text-transform: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Remove default button styles.
+*/
+
+button,
+[type='button'],
+[type='reset'],
+[type='submit'] {
+ -webkit-appearance: button; /* 1 */
+ /* background-color: transparent; */
+ background-image: none; /* 2 */
+}
+
+/*
+Use the modern Firefox focus style for all focusable elements.
+*/
+
+:-moz-focusring {
+ outline: auto;
+}
+
+/*
+Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
+*/
+
+:-moz-ui-invalid {
+ box-shadow: none;
+}
+
+/*
+Add the correct vertical alignment in Chrome and Firefox.
+*/
+
+progress {
+ vertical-align: baseline;
+}
+
+/*
+Correct the cursor style of increment and decrement buttons in Safari.
+*/
+
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/*
+1. Correct the odd appearance in Chrome and Safari.
+2. Correct the outline style in Safari.
+*/
+
+[type='search'] {
+ -webkit-appearance: textfield; /* 1 */
+ outline-offset: -2px; /* 2 */
+}
+
+/*
+Remove the inner padding in Chrome and Safari on macOS.
+*/
+
+::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Change font properties to `inherit` in Safari.
+*/
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button; /* 1 */
+ font: inherit; /* 2 */
+}
+
+/*
+Add the correct display in Chrome and Safari.
+*/
+
+summary {
+ display: list-item;
+}
+
+/*
+Removes the default spacing and border for appropriate elements.
+*/
+
+blockquote,
+dl,
+dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
+p,
+pre {
+ margin: 0;
+}
+
+fieldset {
+ margin: 0;
+ padding: 0;
+}
+
+legend {
+ padding: 0;
+}
+
+ol,
+ul,
+menu {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+/*
+Prevent resizing textareas horizontally by default.
+*/
+
+textarea {
+ resize: vertical;
+}
+
+/*
+1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
+2. Set the default placeholder color to the user's configured gray 400 color.
+*/
+
+input::placeholder,
+textarea::placeholder {
+ opacity: 1; /* 1 */
+ color: #9ca3af; /* 2 */
+}
+
+/*
+Set the default cursor for buttons.
+*/
+
+button,
+[role='button'] {
+ cursor: pointer;
+}
+
+/*
+Make sure disabled buttons don't get the pointer cursor.
+*/
+:disabled {
+ cursor: default;
+}
+
+/*
+1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
+2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
+ This can trigger a poorly considered lint error in some tools but is included by design.
+*/
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+ display: block; /* 1 */
+ vertical-align: middle; /* 2 */
+}
+
+/*
+Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
+*/
+
+img,
+video {
+ max-width: 100%;
+ height: auto;
+}
+
+/* Make elements with the HTML hidden attribute stay hidden by default */
+[hidden] {
+ display: none;
+}
diff --git a/ruoyi-plus-soybean/src/styles/css/transition.css b/ruoyi-plus-soybean/src/styles/css/transition.css
new file mode 100755
index 0000000..9e2a50b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/styles/css/transition.css
@@ -0,0 +1,82 @@
+/* fade */
+.fade-enter-active,
+.fade-leave-active {
+ transition: opacity 0.3s ease-in-out;
+}
+.fade-enter-from,
+.fade-leave-to {
+ opacity: 0;
+}
+
+/* fade-slide */
+.fade-slide-leave-active,
+.fade-slide-enter-active {
+ transition: all 0.3s;
+}
+.fade-slide-enter-from {
+ opacity: 0;
+ transform: translateX(-30px);
+}
+.fade-slide-leave-to {
+ opacity: 0;
+ transform: translateX(30px);
+}
+
+/* fade-bottom */
+.fade-bottom-enter-active,
+.fade-bottom-leave-active {
+ transition:
+ opacity 0.25s,
+ transform 0.3s;
+}
+.fade-bottom-enter-from {
+ opacity: 0;
+ transform: translateY(-10%);
+}
+.fade-bottom-leave-to {
+ opacity: 0;
+ transform: translateY(10%);
+}
+
+/* fade-scale */
+.fade-scale-leave-active,
+.fade-scale-enter-active {
+ transition: all 0.28s;
+}
+.fade-scale-enter-from {
+ opacity: 0;
+ transform: scale(1.2);
+}
+.fade-scale-leave-to {
+ opacity: 0;
+ transform: scale(0.8);
+}
+
+/* zoom-fade */
+.zoom-fade-enter-active,
+.zoom-fade-leave-active {
+ transition:
+ transform 0.2s,
+ opacity 0.3s ease-out;
+}
+.zoom-fade-enter-from {
+ opacity: 0;
+ transform: scale(0.92);
+}
+.zoom-fade-leave-to {
+ opacity: 0;
+ transform: scale(1.06);
+}
+
+/* zoom-out */
+.zoom-out-enter-active,
+.zoom-out-leave-active {
+ transition:
+ opacity 0.1s ease-in-out,
+ transform 0.15s ease-out;
+}
+.zoom-out-enter-from,
+.zoom-out-leave-to {
+ opacity: 0;
+ transform: scale(0);
+}
diff --git a/ruoyi-plus-soybean/src/styles/scss/custom.scss b/ruoyi-plus-soybean/src/styles/scss/custom.scss
new file mode 100755
index 0000000..ce4deb6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/styles/scss/custom.scss
@@ -0,0 +1,5 @@
+.table-search {
+ .n-collapse {
+ --n-title-font-size: 15px !important;
+ }
+}
diff --git a/ruoyi-plus-soybean/src/styles/scss/global.scss b/ruoyi-plus-soybean/src/styles/scss/global.scss
new file mode 100755
index 0000000..1754102
--- /dev/null
+++ b/ruoyi-plus-soybean/src/styles/scss/global.scss
@@ -0,0 +1,2 @@
+@forward 'scrollbar';
+@forward 'custom';
diff --git a/ruoyi-plus-soybean/src/styles/scss/loading.scss b/ruoyi-plus-soybean/src/styles/scss/loading.scss
new file mode 100755
index 0000000..99617dd
--- /dev/null
+++ b/ruoyi-plus-soybean/src/styles/scss/loading.scss
@@ -0,0 +1,105 @@
+@use 'sass:math';
+
+$base-size: 100px;
+$em-to-px: math.div($base-size, 2.5);
+$loader-color-1: rgb(var(--error-color) / 75%);
+$loader-color-2: rgb(var(--primary-color) / 75%);
+$loader-color-3: rgb(var(--success-color) / 75%);
+$loader-color-4: rgb(var(--warning-color) / 75%);
+
+@function loader-size($em-value) {
+ @return $em-to-px * $em-value;
+}
+
+/* From Uiverse.io by SchawnnahJ */
+.loader {
+ position: relative;
+ width: loader-size(2.5);
+ height: loader-size(2.5);
+ transform: rotate(165deg);
+}
+
+.loader:before,
+.loader:after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ display: block;
+ width: loader-size(0.5);
+ height: loader-size(0.5);
+ border-radius: loader-size(0.25);
+ transform: translate(-50%, -50%);
+}
+
+.loader:before {
+ animation: before8 2s infinite;
+}
+
+.loader:after {
+ animation: after6 2s infinite;
+}
+
+@keyframes before8 {
+ 0% {
+ width: loader-size(0.5);
+ box-shadow:
+ loader-size(1) loader-size(-0.5) $loader-color-1,
+ loader-size(-1) loader-size(0.5) $loader-color-2;
+ }
+
+ 35% {
+ width: loader-size(2.5);
+ box-shadow:
+ 0 loader-size(-0.5) $loader-color-1,
+ 0 loader-size(0.5) $loader-color-2;
+ }
+
+ 70% {
+ width: loader-size(0.5);
+ box-shadow:
+ loader-size(-1) loader-size(-0.5) $loader-color-1,
+ loader-size(1) loader-size(0.5) $loader-color-2;
+ }
+
+ 100% {
+ box-shadow:
+ loader-size(1) loader-size(-0.5) $loader-color-1,
+ loader-size(-1) loader-size(0.5) $loader-color-2;
+ }
+}
+
+@keyframes after6 {
+ 0% {
+ height: loader-size(0.5);
+ box-shadow:
+ loader-size(0.5) loader-size(1) $loader-color-3,
+ loader-size(-0.5) loader-size(-1) $loader-color-4;
+ }
+
+ 35% {
+ height: loader-size(2.5);
+ box-shadow:
+ loader-size(0.5) 0 $loader-color-3,
+ loader-size(-0.5) 0 $loader-color-4;
+ }
+
+ 70% {
+ height: loader-size(0.5);
+ box-shadow:
+ loader-size(0.5) loader-size(-1) $loader-color-3,
+ loader-size(-0.5) loader-size(1) $loader-color-4;
+ }
+
+ 100% {
+ box-shadow:
+ loader-size(0.5) loader-size(1) $loader-color-3,
+ loader-size(-0.5) loader-size(-1) $loader-color-4;
+ }
+}
+
+.loader {
+ position: absolute;
+ top: calc(50% - #{math.div(loader-size(2.5), 2)});
+ left: calc(50% - #{math.div(loader-size(2.5), 2)});
+}
diff --git a/ruoyi-plus-soybean/src/styles/scss/scrollbar.scss b/ruoyi-plus-soybean/src/styles/scss/scrollbar.scss
new file mode 100755
index 0000000..ba85b6b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/styles/scss/scrollbar.scss
@@ -0,0 +1,28 @@
+@mixin scrollbar($size: 7px, $color: rgba(0, 0, 0, 0.5), $dark-color: rgba(255, 255, 255, 0.5)) {
+ scrollbar-width: thin;
+ scrollbar-color: $color transparent;
+
+ // Dark theme override
+ .dark & {
+ // guide safari use light color scrollbar
+ color-scheme: dark;
+ scrollbar-color: $dark-color transparent;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: $color;
+ border-radius: $size;
+ }
+ &::-webkit-scrollbar-thumb:hover {
+ background-color: $color;
+ border-radius: $size;
+ }
+ &::-webkit-scrollbar {
+ width: $size;
+ height: $size;
+ }
+ &::-webkit-scrollbar-track-piece {
+ background-color: rgba(0, 0, 0, 0);
+ border-radius: 0;
+ }
+}
diff --git a/ruoyi-plus-soybean/src/theme/preset/azir.json b/ruoyi-plus-soybean/src/theme/preset/azir.json
new file mode 100755
index 0000000..73fba9a
--- /dev/null
+++ b/ruoyi-plus-soybean/src/theme/preset/azir.json
@@ -0,0 +1,53 @@
+{
+ "name": "Azir's Preset",
+ "desc": "It is a cold and elegant preset that Azir likes",
+ "i18nkey": "theme.appearance.preset.azir",
+ "version": "1.0.1",
+ "sort": 20,
+ "themeScheme": "light",
+ "recommendColor": true,
+ "themeColor": "#78a878",
+ "otherColor": {
+ "info": "#89b989",
+ "success": "#99c299",
+ "warning": "#d4bb9d",
+ "error": "#c49a9a"
+ },
+ "isInfoFollowPrimary": true,
+ "tokens": {
+ "light": {
+ "colors": {
+ "container": "rgb(255, 255, 255)",
+ "layout": "rgb(247, 250, 252)",
+ "inverted": "rgb(0, 20, 40)",
+ "base-text": "rgb(31, 31, 31)"
+ },
+ "boxShadow": {
+ "header": "0 1px 2px rgb(0, 21, 41, 0.08)",
+ "sider": "2px 0 8px 0 rgb(29, 35, 41, 0.05)",
+ "tab": "0 1px 2px rgb(0, 21, 41, 0.08)"
+ }
+ },
+ "dark": {
+ "colors": {
+ "container": "rgb(28, 28, 28)",
+ "layout": "rgb(18, 18, 18)",
+ "base-text": "rgb(224, 224, 224)"
+ }
+ }
+ },
+ "naiveui": {
+ "Alert": {
+ "borderRadiusMedium": "12px",
+ "fontWeightStrong": "600",
+ "paddingMedium": "0 20px"
+ },
+ "Card": {
+ "borderRadius": "16px",
+ "paddingMedium": "24px"
+ },
+ "Input": {
+ "borderRadius": "10px"
+ }
+ }
+}
diff --git a/ruoyi-plus-soybean/src/theme/preset/compact.json b/ruoyi-plus-soybean/src/theme/preset/compact.json
new file mode 100755
index 0000000..e42f647
--- /dev/null
+++ b/ruoyi-plus-soybean/src/theme/preset/compact.json
@@ -0,0 +1,41 @@
+{
+ "name": "Compact Preset",
+ "desc": "Compact layout preset for small screens",
+ "i18nkey": "theme.appearance.preset.compact",
+ "version": "1.0.1",
+ "sort": 30,
+ "themeRadius": 6,
+ "header": {
+ "height": 48,
+ "breadcrumb": {
+ "visible": false
+ },
+ "multilingual": {
+ "visible": false
+ },
+ "globalSearch": {
+ "visible": false
+ }
+ },
+ "tab": {
+ "visible": true,
+ "cache": true,
+ "height": 36,
+ "mode": "button",
+ "closeTabByMiddleClick": false
+ },
+ "sider": {
+ "inverted": false,
+ "width": 180,
+ "collapsedWidth": 48,
+ "mixWidth": 80,
+ "mixCollapsedWidth": 48,
+ "mixChildMenuWidth": 180
+ },
+ "footer": {
+ "visible": false
+ },
+ "table": {
+ "size": "small"
+ }
+}
diff --git a/ruoyi-plus-soybean/src/theme/preset/dark.json b/ruoyi-plus-soybean/src/theme/preset/dark.json
new file mode 100755
index 0000000..55bfa8c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/theme/preset/dark.json
@@ -0,0 +1,92 @@
+{
+ "name": "Dark Preset",
+ "desc": "Dark theme preset for night time usage",
+ "i18nkey": "theme.appearance.preset.dark",
+ "version": "1.0.1",
+ "sort": 40,
+ "themeScheme": "dark",
+ "grayscale": false,
+ "colourWeakness": false,
+ "recommendColor": false,
+ "themeColor": "#646cff",
+ "otherColor": {
+ "info": "#2080f0",
+ "success": "#52c41a",
+ "warning": "#faad14",
+ "error": "#f5222d"
+ },
+ "themeRadius": 6,
+ "isInfoFollowPrimary": true,
+ "layout": {
+ "mode": "vertical",
+ "scrollMode": "content"
+ },
+ "page": {
+ "animate": true,
+ "animateMode": "fade-slide"
+ },
+ "header": {
+ "height": 56,
+ "breadcrumb": {
+ "visible": true,
+ "showIcon": true
+ },
+ "multilingual": {
+ "visible": true
+ },
+ "globalSearch": {
+ "visible": true
+ }
+ },
+ "tab": {
+ "visible": true,
+ "cache": true,
+ "height": 44,
+ "mode": "chrome",
+ "closeTabByMiddleClick": false
+ },
+ "fixedHeaderAndTab": true,
+ "sider": {
+ "inverted": false,
+ "width": 220,
+ "collapsedWidth": 64,
+ "mixWidth": 90,
+ "mixCollapsedWidth": 64,
+ "mixChildMenuWidth": 200
+ },
+ "footer": {
+ "visible": true,
+ "fixed": false,
+ "height": 48,
+ "right": true
+ },
+ "watermark": {
+ "visible": false,
+ "text": "SoybeanAdmin",
+ "enableUserName": false,
+ "enableTime": false,
+ "timeFormat": "YYYY-MM-DD HH:mm"
+ },
+ "tokens": {
+ "light": {
+ "colors": {
+ "container": "rgb(255, 255, 255)",
+ "layout": "rgb(247, 250, 252)",
+ "inverted": "rgb(0, 20, 40)",
+ "base-text": "rgb(31, 31, 31)"
+ },
+ "boxShadow": {
+ "header": "0 1px 2px rgb(0, 21, 41, 0.08)",
+ "sider": "2px 0 8px 0 rgb(29, 35, 41, 0.05)",
+ "tab": "0 1px 2px rgb(0, 21, 41, 0.08)"
+ }
+ },
+ "dark": {
+ "colors": {
+ "container": "rgb(28, 28, 28)",
+ "layout": "rgb(18, 18, 18)",
+ "base-text": "rgb(224, 224, 224)"
+ }
+ }
+ }
+}
diff --git a/ruoyi-plus-soybean/src/theme/preset/default.json b/ruoyi-plus-soybean/src/theme/preset/default.json
new file mode 100755
index 0000000..fa0b005
--- /dev/null
+++ b/ruoyi-plus-soybean/src/theme/preset/default.json
@@ -0,0 +1,100 @@
+{
+ "name": "default",
+ "desc": "Default theme preset with balanced settings",
+ "i18nkey": "theme.appearance.preset.default",
+ "version": "1.0.0",
+ "sort": 0,
+ "themeScheme": "light",
+ "grayscale": false,
+ "colourWeakness": false,
+ "recommendColor": false,
+ "themeColor": "#0E42D2",
+ "otherColor": {
+ "info": "#0E42D2",
+ "success": "#009A29",
+ "warning": "#D25F00",
+ "error": "#CB2634"
+ },
+ "themeRadius": 6,
+ "isInfoFollowPrimary": true,
+ "layout": {
+ "mode": "vertical",
+ "scrollMode": "content"
+ },
+ "page": {
+ "animate": true,
+ "animateMode": "fade-slide"
+ },
+ "header": {
+ "height": 56,
+ "breadcrumb": {
+ "visible": true,
+ "showIcon": true
+ },
+ "multilingual": {
+ "visible": true
+ },
+ "globalSearch": {
+ "visible": true
+ }
+ },
+ "tab": {
+ "visible": true,
+ "cache": true,
+ "height": 44,
+ "mode": "chrome",
+ "closeTabByMiddleClick": false
+ },
+ "fixedHeaderAndTab": true,
+ "sider": {
+ "inverted": false,
+ "width": 230,
+ "collapsedWidth": 64,
+ "mixWidth": 90,
+ "mixCollapsedWidth": 64,
+ "mixChildMenuWidth": 200
+ },
+ "footer": {
+ "visible": true,
+ "fixed": false,
+ "height": 48,
+ "right": true
+ },
+ "watermark": {
+ "visible": false,
+ "text": "SoybeanAdmin",
+ "enableUserName": false,
+ "enableTime": false,
+ "timeFormat": "YYYY-MM-DD HH:mm"
+ },
+ "table": {
+ "bordered": true,
+ "bottomBordered": true,
+ "singleColumn": false,
+ "singleLine": true,
+ "size": "small",
+ "striped": false
+ },
+ "tokens": {
+ "light": {
+ "colors": {
+ "container": "rgb(255, 255, 255)",
+ "layout": "rgb(247, 250, 252)",
+ "inverted": "rgb(0, 20, 40)",
+ "base-text": "rgb(31, 31, 31)"
+ },
+ "boxShadow": {
+ "header": "0 1px 2px rgb(0, 21, 41, 0.08)",
+ "sider": "2px 0 8px 0 rgb(29, 35, 41, 0.05)",
+ "tab": "0 1px 2px rgb(0, 21, 41, 0.08)"
+ }
+ },
+ "dark": {
+ "colors": {
+ "container": "rgb(28, 28, 28)",
+ "layout": "rgb(18, 18, 18)",
+ "base-text": "rgb(224, 224, 224)"
+ }
+ }
+ }
+}
diff --git a/ruoyi-plus-soybean/src/theme/preset/soybean.json b/ruoyi-plus-soybean/src/theme/preset/soybean.json
new file mode 100755
index 0000000..ad9190b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/theme/preset/soybean.json
@@ -0,0 +1,91 @@
+{
+ "name": "Soybean",
+ "desc": "Soybean theme preset with balanced settings",
+ "i18nkey": "theme.appearance.preset.soybean",
+ "version": "1.0.0",
+ "sort": 10,
+ "themeScheme": "light",
+ "grayscale": false,
+ "colourWeakness": false,
+ "recommendColor": false,
+ "themeColor": "#646cff",
+ "otherColor": {
+ "info": "#2080f0",
+ "success": "#52c41a",
+ "warning": "#faad14",
+ "error": "#f5222d"
+ },
+ "themeRadius": 6,
+ "isInfoFollowPrimary": true,
+ "layout": {
+ "mode": "vertical",
+ "scrollMode": "content"
+ },
+ "page": {
+ "animate": true,
+ "animateMode": "fade-slide"
+ },
+ "header": {
+ "height": 56,
+ "breadcrumb": {
+ "visible": true,
+ "showIcon": true
+ },
+ "multilingual": {
+ "visible": true
+ },
+ "globalSearch": {
+ "visible": true
+ }
+ },
+ "tab": {
+ "visible": true,
+ "cache": true,
+ "height": 44,
+ "mode": "chrome"
+ },
+ "fixedHeaderAndTab": true,
+ "sider": {
+ "inverted": false,
+ "width": 230,
+ "collapsedWidth": 64,
+ "mixWidth": 90,
+ "mixCollapsedWidth": 64,
+ "mixChildMenuWidth": 200
+ },
+ "footer": {
+ "visible": true,
+ "fixed": false,
+ "height": 48,
+ "right": true
+ },
+ "watermark": {
+ "visible": false,
+ "text": "SoybeanAdmin",
+ "enableUserName": false,
+ "enableTime": false,
+ "timeFormat": "YYYY-MM-DD HH:mm"
+ },
+ "tokens": {
+ "light": {
+ "colors": {
+ "container": "rgb(255, 255, 255)",
+ "layout": "rgb(247, 250, 252)",
+ "inverted": "rgb(0, 20, 40)",
+ "base-text": "rgb(31, 31, 31)"
+ },
+ "boxShadow": {
+ "header": "0 1px 2px rgb(0, 21, 41, 0.08)",
+ "sider": "2px 0 8px 0 rgb(29, 35, 41, 0.05)",
+ "tab": "0 1px 2px rgb(0, 21, 41, 0.08)"
+ }
+ },
+ "dark": {
+ "colors": {
+ "container": "rgb(28, 28, 28)",
+ "layout": "rgb(18, 18, 18)",
+ "base-text": "rgb(224, 224, 224)"
+ }
+ }
+ }
+}
diff --git a/ruoyi-plus-soybean/src/theme/settings.ts b/ruoyi-plus-soybean/src/theme/settings.ts
new file mode 100755
index 0000000..66855fa
--- /dev/null
+++ b/ruoyi-plus-soybean/src/theme/settings.ts
@@ -0,0 +1,104 @@
+/** Default theme settings */
+export const themeSettings: App.Theme.ThemeSetting = {
+ themeScheme: 'auto',
+ grayscale: false,
+ colourWeakness: false,
+ recommendColor: false,
+ themeColor: '#0E42D2',
+ themeRadius: 6,
+ otherColor: {
+ info: '#0E42D2',
+ success: '#009A29',
+ warning: '#D25F00',
+ error: '#CB2634'
+ },
+ isInfoFollowPrimary: true,
+ layout: {
+ mode: 'vertical',
+ scrollMode: 'content'
+ },
+ page: {
+ animate: true,
+ animateMode: 'fade-slide'
+ },
+ header: {
+ height: 56,
+ breadcrumb: {
+ visible: true,
+ showIcon: true
+ },
+ multilingual: {
+ visible: true
+ },
+ globalSearch: {
+ visible: true
+ }
+ },
+ tab: {
+ visible: true,
+ cache: true,
+ height: 44,
+ mode: 'chrome',
+ closeTabByMiddleClick: true
+ },
+ fixedHeaderAndTab: true,
+ sider: {
+ inverted: false,
+ width: 230,
+ collapsedWidth: 64,
+ mixWidth: 90,
+ mixCollapsedWidth: 64,
+ mixChildMenuWidth: 200,
+ autoSelectFirstMenu: false
+ },
+ footer: {
+ visible: true,
+ fixed: false,
+ height: 48,
+ right: true
+ },
+ watermark: {
+ visible: import.meta.env.VITE_WATERMARK === 'Y',
+ text: 'RuoYi-Plus-Soybean',
+ enableUserName: true,
+ enableTime: false,
+ timeFormat: 'YYYY-MM-DD HH:mm'
+ },
+ table: {
+ bordered: true,
+ bottomBordered: true,
+ singleColumn: false,
+ singleLine: true,
+ size: 'small',
+ striped: false
+ },
+ tokens: {
+ light: {
+ colors: {
+ container: 'rgb(255, 255, 255)',
+ layout: 'rgb(247, 250, 252)',
+ inverted: 'rgb(0, 20, 40)',
+ 'base-text': 'rgb(31, 31, 31)'
+ },
+ boxShadow: {
+ header: '0 1px 2px rgb(0, 21, 41, 0.08)',
+ sider: '2px 0 8px 0 rgb(29, 35, 41, 0.05)',
+ tab: '0 1px 2px rgb(0, 21, 41, 0.08)'
+ }
+ },
+ dark: {
+ colors: {
+ container: 'rgb(28, 28, 28)',
+ layout: 'rgb(18, 18, 18)',
+ 'base-text': 'rgb(224, 224, 224)'
+ }
+ }
+ }
+};
+
+/**
+ * Override theme settings
+ *
+ * If publish new version, use `overrideThemeSettings` to override certain theme settings
+ */
+export const overrideThemeSettings: Partial<App.Theme.ThemeSetting> = {};
diff --git a/ruoyi-plus-soybean/src/theme/vars.ts b/ruoyi-plus-soybean/src/theme/vars.ts
new file mode 100755
index 0000000..3e7946a
--- /dev/null
+++ b/ruoyi-plus-soybean/src/theme/vars.ts
@@ -0,0 +1,35 @@
+/** Create color palette vars */
+function createColorPaletteVars() {
+ const colors: App.Theme.ThemeColorKey[] = ['primary', 'info', 'success', 'warning', 'error'];
+ const colorPaletteNumbers: App.Theme.ColorPaletteNumber[] = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
+
+ const colorPaletteVar = {} as App.Theme.ThemePaletteColor;
+
+ colors.forEach(color => {
+ colorPaletteVar[color] = `rgb(var(--${color}-color))`;
+ colorPaletteNumbers.forEach(number => {
+ colorPaletteVar[`${color}-${number}`] = `rgb(var(--${color}-${number}-color))`;
+ });
+ });
+
+ return colorPaletteVar;
+}
+
+const colorPaletteVars = createColorPaletteVars();
+
+/** Theme vars */
+export const themeVars: App.Theme.ThemeTokenCSSVars = {
+ colors: {
+ ...colorPaletteVars,
+ nprogress: 'rgb(var(--nprogress-color))',
+ container: 'rgb(var(--container-bg-color))',
+ layout: 'rgb(var(--layout-bg-color))',
+ inverted: 'rgb(var(--inverted-bg-color))',
+ 'base-text': 'rgb(var(--base-text-color))'
+ },
+ boxShadow: {
+ header: 'var(--header-box-shadow)',
+ sider: 'var(--sider-box-shadow)',
+ tab: 'var(--tab-box-shadow)'
+ }
+};
diff --git a/ruoyi-plus-soybean/src/typings/api/analy.hoister-data.api.d.ts b/ruoyi-plus-soybean/src/typings/api/analy.hoister-data.api.d.ts
new file mode 100755
index 0000000..6906498
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/analy.hoister-data.api.d.ts
@@ -0,0 +1,155 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+ /**
+ * namespace Qa
+ *
+ * backend api module: "Qa"
+ */
+ namespace Qa {
+ /** hoister data */
+ type HoisterData = Common.CommonRecord<{
+ /** 鏃堕棿 */
+ time: string;
+ /** key */
+ key: string;
+ /** 缃戠粶鐘舵�� 0寮傚父锛�1姝e父 */
+ online: number;
+ /** 浜ч噺 */
+ qty: number;
+ /** 1#鎻愬崌鏈虹姸鎬� */
+ tState1: number;
+ /** 2#鎻愬崌鏈虹姸鎬� */
+ tState2: number;
+ /** 3#鎻愬崌鏈虹姸鎬� */
+ tState3: number;
+ /** 4#鎻愬崌鏈虹姸鎬� */
+ tState4: number;
+ /** 5#鎻愬崌鏈虹姸鎬� */
+ tState5: number;
+ /** 6#鎻愬崌鏈虹姸鎬� */
+ tState6: number;
+ /** 7#鎻愬崌鏈虹姸鎬� */
+ tState7: number;
+ /** 8#鎻愬崌鏈虹姸鎬� */
+ tState8: number;
+ /** 9#鎻愬崌鏈虹姸鎬� */
+ tState9: number;
+ /** 10#鎻愬崌鏈虹姸鎬� */
+ tState10: number;
+ /** 11#鎻愬崌鏈虹姸鎬� */
+ tState11: number;
+ /** 12#鎻愬崌鏈虹姸鎬� */
+ tState12: number;
+ /** 1#鎺掑寘鏈虹姸鎬� */
+ pState1: number;
+ /** 2#鎺掑寘鏈虹姸鎬� */
+ pState2: number;
+ /** 3#鎺掑寘鏈虹姸鎬� */
+ pState3: number;
+ /** 4#鎺掑寘鏈虹姸鎬� */
+ pState4: number;
+ /** 1#鎻愬崌鏈轰骇閲� */
+ tQty1: number;
+ /** 2#鎻愬崌鏈轰骇閲� */
+ tQty2: number;
+ /** 3#鎻愬崌鏈轰骇閲� */
+ tQty3: number;
+ /** 4#鎻愬崌鏈轰骇閲� */
+ tQty4: number;
+ /** 5#鎻愬崌鏈轰骇閲� */
+ tQty5: number;
+ /** 6#鎻愬崌鏈轰骇閲� */
+ tQty6: number;
+ /** 7#鎻愬崌鏈轰骇閲� */
+ tQty7: number;
+ /** 8#鎻愬崌鏈轰骇閲� */
+ tQty8: number;
+ /** 9#鎻愬崌鏈轰骇閲� */
+ tQty9: number;
+ /** 10#鎻愬崌鏈轰骇閲� */
+ tQty10: number;
+ /** 11#鎻愬崌鏈轰骇閲� */
+ tQty11: number;
+ /** 12#鎻愬崌鏈轰骇閲� */
+ tQty12: number;
+ /** 1#鎺掑寘鏈轰骇閲� */
+ pQty1: number;
+ /** 2#鎺掑寘鏈轰骇閲� */
+ pQty2: number;
+ /** 3#鎺掑寘鏈轰骇閲� */
+ pQty3: number;
+ /** 4#鎺掑寘鏈轰骇閲� */
+ pQty4: number;
+ /** 鐝 */
+ shift: number;
+ /** 璁惧 */
+ equNo: number;
+ /** 澶囨敞 */
+ remark: string;
+ }>;
+
+ /** hoister data search params */
+ type HoisterDataSearchParams = CommonType.RecordNullable<
+ Pick<
+ Api.Qa.HoisterData,
+ | 'time'
+ | 'shift'
+ | 'equNo'
+ > &
+ Api.Common.CommonSearchParams
+ >;
+
+ /** hoister data operate params */
+ type HoisterDataOperateParams = CommonType.RecordNullable<
+ Pick<
+ Api.Qa.HoisterData,
+ | 'time'
+ | 'key'
+ | 'online'
+ | 'qty'
+ | 'tState1'
+ | 'tState2'
+ | 'tState3'
+ | 'tState4'
+ | 'tState5'
+ | 'tState6'
+ | 'tState7'
+ | 'tState8'
+ | 'tState9'
+ | 'tState10'
+ | 'tState11'
+ | 'tState12'
+ | 'pState1'
+ | 'pState2'
+ | 'pState3'
+ | 'pState4'
+ | 'tQty1'
+ | 'tQty2'
+ | 'tQty3'
+ | 'tQty4'
+ | 'tQty5'
+ | 'tQty6'
+ | 'tQty7'
+ | 'tQty8'
+ | 'tQty9'
+ | 'tQty10'
+ | 'tQty11'
+ | 'tQty12'
+ | 'pQty1'
+ | 'pQty2'
+ | 'pQty3'
+ | 'pQty4'
+ | 'shift'
+ | 'equNo'
+ | 'remark'
+ >
+ >;
+
+ /** hoister data list */
+ type HoisterDataList = Api.Common.PaginatingQueryRecord<HoisterData>;
+ }
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/analy.packer-data.api.d.ts b/ruoyi-plus-soybean/src/typings/api/analy.packer-data.api.d.ts
new file mode 100755
index 0000000..ee23b07
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/analy.packer-data.api.d.ts
@@ -0,0 +1,131 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+ /**
+ * namespace Qa
+ *
+ * backend api module: "Qa"
+ */
+ namespace Qa {
+ /** packer data */
+ type PackerData = Common.CommonRecord<{
+ /** 鏃堕棿 */
+ time: string;
+ /** key */
+ key: string;
+ /** 缃戠粶鐘舵��(0寮傚父锛�1姝e父) */
+ online: number;
+ /** 浜ч噺 */
+ qty: number;
+ /** 鍓旈櫎浜ч噺 */
+ badQty: number;
+ /** 灏忕洅鑶滄秷鑰� */
+ xiaohemoVal: number;
+ /** 鏉$洅鑶滄秷鑰� */
+ tiaohemoVal: number;
+ /** 灏忕洅绾告秷鑰� */
+ xiaohezhiVal: number;
+ /** 鏉$洅绾告秷鑰� */
+ tiaohezhiVal: number;
+ /** 鍐呰‖绾告秷鑰� */
+ neichenzhiVal: number;
+ /** 杩愯鏃堕棿 */
+ runTime: number;
+ /** 鍋滄満鏃堕棿 */
+ stopTime: number;
+ /** 鍋滄満娆℃暟 */
+ stopTimes: number;
+ /** 杞﹂�� */
+ speed: number;
+ /** 杩愯鐘舵��(-1 鏂綉 0鍋滄 1浣庨�熻繍琛� 2姝e父杩愯) */
+ runStatus: number;
+ /** 鎻愬崌鏈轰骇閲� */
+ tsQty: number;
+ /** 涓绘満浜ч噺锛堝皬鍖呮満锛� */
+ mainQty: number;
+ /** 涓绘満鍓旈櫎閲� */
+ mainBadQty: number;
+ /** 閫忓寘鏈轰骇閲� */
+ tbjQty: number;
+ /** 閫忓寘鏈哄墧闄ゅソ鍖� */
+ tbjGdQty: number;
+ /** 閫忓寘鏈哄墧闄ゅ潖鍖� */
+ tbjBadQty: number;
+ /** 鎺掑寘鏈轰骇閲� */
+ pbjQty: number;
+ /** 鐝 */
+ shift: number;
+ /** 璁惧 */
+ equNo: number;
+ }>;
+
+ /** packer data search params */
+ type PackerDataSearchParams = CommonType.RecordNullable<
+ Pick<
+ Api.Qa.PackerData,
+ | 'time'
+ | 'key'
+ | 'online'
+ | 'qty'
+ | 'badQty'
+ | 'xiaohemoVal'
+ | 'tiaohemoVal'
+ | 'xiaohezhiVal'
+ | 'tiaohezhiVal'
+ | 'neichenzhiVal'
+ | 'runTime'
+ | 'stopTime'
+ | 'stopTimes'
+ | 'speed'
+ | 'runStatus'
+ | 'tsQty'
+ | 'mainQty'
+ | 'mainBadQty'
+ | 'tbjQty'
+ | 'tbjGdQty'
+ | 'tbjBadQty'
+ | 'pbjQty'
+ | 'shift'
+ | 'equNo'
+ > &
+ Api.Common.CommonSearchParams
+ >;
+
+ /** packer data operate params */
+ type PackerDataOperateParams = CommonType.RecordNullable<
+ Pick<
+ Api.Qa.PackerData,
+ | 'time'
+ | 'key'
+ | 'online'
+ | 'qty'
+ | 'badQty'
+ | 'xiaohemoVal'
+ | 'tiaohemoVal'
+ | 'xiaohezhiVal'
+ | 'tiaohezhiVal'
+ | 'neichenzhiVal'
+ | 'runTime'
+ | 'stopTime'
+ | 'stopTimes'
+ | 'speed'
+ | 'runStatus'
+ | 'tsQty'
+ | 'mainQty'
+ | 'mainBadQty'
+ | 'tbjQty'
+ | 'tbjGdQty'
+ | 'tbjBadQty'
+ | 'pbjQty'
+ | 'shift'
+ | 'equNo'
+ >
+ >;
+
+ /** packer data list */
+ type PackerDataList = Api.Common.PaginatingQueryRecord<PackerData>;
+ }
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/analy.roller-data.api.d.ts b/ruoyi-plus-soybean/src/typings/api/analy.roller-data.api.d.ts
new file mode 100755
index 0000000..afcac20
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/analy.roller-data.api.d.ts
@@ -0,0 +1,123 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+ /**
+ * namespace Qa
+ *
+ * backend api module: "Qa"
+ */
+ namespace Qa {
+ /** roller data */
+ type RollerData = Common.CommonRecord<{
+ /** 鏃堕棿 */
+ time: string;
+ /** key */
+ key: string;
+ /** 鐝 */
+ shift: number;
+ /** 璁惧鍙� */
+ equNo: number;
+ /** 缃戠粶鐘舵�� */
+ online: number;
+ /** 浜ч噺 */
+ qty: number;
+ /** 鍓旈櫎浜ч噺 */
+ badQty: number;
+ /** 婊ゆ娑堣�� */
+ lvbangVal: number;
+ /** 鍗风儫绾告秷鑰� */
+ juanyanzhiVal: number;
+ /** 姘存澗绾告秷鑰� */
+ shuisongzhiVal: number;
+ /** 杩愯鏃堕棿 */
+ runTime: number;
+ /** 鍋滄満鏃堕棿 */
+ stopTime: number;
+ /** 鍋滄満娆℃暟 */
+ stopTimes: number;
+ /** 杞﹂�� */
+ speed: number;
+ /** 杩愯鐘舵�� */
+ runStatus: number;
+ /** 鍌ㄧ儫璁惧鍌ㄩ噺 */
+ cy: number;
+ /** 鍌ㄧ儫璁惧杞﹂�� */
+ cyCs: number;
+ /** 鍌ㄧ儫璁惧缃戠粶 */
+ cyOnline: string;
+ /** 鎺ユ敹鏈轰骇閲� */
+ recQty1: number;
+ /** 鎺ユ敹鏈轰骇閲�2 */
+ recQty2: number;
+ /** 鎻愬崌鏈轰骇閲� */
+ tsQty: number;
+ /** 鍖呰鏈轰骇閲� */
+ packerQty: number;
+ }>;
+
+ /** roller data search params */
+ type RollerDataSearchParams = CommonType.RecordNullable<
+ Pick<
+ Api.Qa.RollerData,
+ | 'time'
+ | 'key'
+ | 'shift'
+ | 'equNo'
+ | 'online'
+ | 'qty'
+ | 'badQty'
+ | 'lvbangVal'
+ | 'juanyanzhiVal'
+ | 'shuisongzhiVal'
+ | 'runTime'
+ | 'stopTime'
+ | 'stopTimes'
+ | 'speed'
+ | 'runStatus'
+ | 'cy'
+ | 'cyCs'
+ | 'cyOnline'
+ | 'recQty1'
+ | 'recQty2'
+ | 'tsQty'
+ | 'packerQty'
+ > &
+ Api.Common.CommonSearchParams
+ >;
+
+ /** roller data operate params */
+ type RollerDataOperateParams = CommonType.RecordNullable<
+ Pick<
+ Api.Qa.RollerData,
+ | 'time'
+ | 'key'
+ | 'shift'
+ | 'equNo'
+ | 'online'
+ | 'qty'
+ | 'badQty'
+ | 'lvbangVal'
+ | 'juanyanzhiVal'
+ | 'shuisongzhiVal'
+ | 'runTime'
+ | 'stopTime'
+ | 'stopTimes'
+ | 'speed'
+ | 'runStatus'
+ | 'cy'
+ | 'cyCs'
+ | 'cyOnline'
+ | 'recQty1'
+ | 'recQty2'
+ | 'tsQty'
+ | 'packerQty'
+ >
+ >;
+
+ /** roller data list */
+ type RollerDataList = Api.Common.PaginatingQueryRecord<RollerData>;
+ }
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/api.d.ts b/ruoyi-plus-soybean/src/typings/api/api.d.ts
new file mode 100755
index 0000000..dcfd83b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/api.d.ts
@@ -0,0 +1,88 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+ namespace Common {
+ /** common params of paginating */
+ interface PaginatingCommonParams {
+ /** current page number */
+ pageNum: number;
+ /** page size */
+ pageSize?: number;
+ /** total count */
+ total: number;
+ }
+
+ /** common params of paginating query list data */
+ interface PaginatingQueryRecord<T = any> extends PaginatingCommonParams {
+ rows: T[];
+ }
+
+ /** common search params of table */
+ type CommonSearchParams = Pick<Common.PaginatingCommonParams, 'pageNum' | 'pageSize'> &
+ CommonType.RecordNullable<{
+ orderByColumn: string;
+ isAsc: 'asc' | 'desc';
+ params: { [key: string]: any };
+ }>;
+
+ /**
+ * 鍚敤鐘舵��
+ *
+ * - "0": 姝e父
+ * - "1": 鍋滅敤
+ */
+ type EnableStatus = '0' | '1';
+
+ /**
+ * 鏄剧ず鐘舵��
+ *
+ * - "0": 鏄剧ず
+ * - "1": 闅愯棌
+ */
+ type VisibleStatus = '0' | '1';
+
+ /**
+ * 鏄惁鐘舵��
+ *
+ * - "Y": 鏄�
+ * - "N": 鍚�
+ */
+ type YesOrNoStatus = 'Y' | 'N';
+
+ /** common record */
+ type CommonRecord<T = any> = {
+ /** record creator */
+ createBy: string;
+ /** record dept */
+ createDept?: any;
+ /** record create time */
+ createTime: string;
+ /** record updater */
+ updateBy: string;
+ /** record update time */
+ updateTime: string;
+ } & T;
+
+ type CommonTenantRecord<T = any> = {
+ /** record tenant id */
+ tenantId: string;
+ } & CommonRecord<T>;
+
+ /** common tree record */
+ type CommonTreeRecord = {
+ /** record id */
+ id: CommonType.IdType;
+ /** record parent id */
+ parentId: CommonType.IdType;
+ /** record label */
+ label: string;
+ /** record weight */
+ weight: number;
+ /** record children */
+ children: CommonTreeRecord[];
+ }[];
+ }
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/auth.d.ts b/ruoyi-plus-soybean/src/typings/api/auth.d.ts
new file mode 100755
index 0000000..8ae1310
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/auth.d.ts
@@ -0,0 +1,110 @@
+declare namespace Api {
+ /**
+ * namespace Auth
+ *
+ * backend api module: "auth"
+ */
+ namespace Auth {
+ /** base login form */
+ interface LoginForm {
+ /** 瀹㈡埛绔� ID */
+ clientId?: string;
+ /** 鎺堟潈绫诲瀷 */
+ grantType?: string;
+ /** 绉熸埛ID */
+ tenantId?: string;
+ /** 楠岃瘉鐮� */
+ code?: string;
+ /** 鍞竴鏍囪瘑 */
+ uuid?: string;
+ }
+
+ /** password login form */
+ interface PwdLoginForm extends LoginForm {
+ /** 鐢ㄦ埛鍚� */
+ username?: string;
+ /** 瀵嗙爜 */
+ password?: string;
+ }
+
+ /** social login form */
+ interface SocialLoginForm extends LoginForm {
+ /** 鎺堟潈鐮� */
+ socialCode?: string;
+ /** 鎺堟潈鐘舵�� */
+ socialState?: string;
+ /** 鏉ユ簮 */
+ source?: string;
+ }
+
+ /** register form */
+ interface RegisterForm extends LoginForm {
+ /** 鐢ㄦ埛鍚� */
+ username?: string;
+ /** 瀵嗙爜 */
+ password?: string;
+ /** 纭瀵嗙爜 */
+ confirmPassword?: string;
+ /** 鐢ㄦ埛绫诲瀷 */
+ userType?: string;
+ }
+
+ /** login token data */
+ interface LoginToken {
+ /** 鎺堟潈浠ょ墝 */
+ access_token?: string;
+ /** 搴旂敤id */
+ client_id?: string;
+ /** 鎺堟潈浠ょ墝 access_token 鐨勬湁鏁堟湡 */
+ expire_in?: number;
+ /** 鐢ㄦ埛 openid */
+ openid?: string;
+ /** 鍒锋柊浠ょ墝 refresh_token 鐨勬湁鏁堟湡 */
+ refresh_expire_in?: number;
+ /** 鍒锋柊浠ょ墝 */
+ refresh_token?: string;
+ /** 浠ょ墝鏉冮檺 */
+ scope?: string;
+ }
+
+ /** userinfo */
+ interface UserInfo {
+ /** 鐢ㄦ埛淇℃伅 */
+ user?: Api.System.User & {
+ /** 鎵�灞炶鑹� */
+ roles: Api.System.Role[];
+ };
+ /** 瑙掕壊鍒楄〃 */
+ roles: string[];
+ /** 鑿滃崟鏉冮檺 */
+ permissions: string[];
+ }
+
+ /** tenant */
+ interface Tenant {
+ /** 浼佷笟鍚嶇О */
+ companyName: string;
+ /** 鍩熷悕 */
+ domain: string;
+ /** 绉熸埛缂栧彿 */
+ tenantId: string;
+ }
+
+ /** login tenant */
+ interface LoginTenant {
+ /** 绉熸埛寮�鍏� */
+ tenantEnabled: boolean;
+ /** 绉熸埛鍒楄〃 */
+ voList: Tenant[];
+ }
+
+ interface CaptchaCode {
+ /** 鏄惁寮�鍚獙璇佺爜 */
+ captchaEnabled: boolean;
+ /** 鍞竴鏍囪瘑 */
+ uuid?: string;
+ /** 楠岃瘉鐮佸浘鐗� */
+ img?: string;
+ }
+ }
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/demo.api.d.ts b/ruoyi-plus-soybean/src/typings/api/demo.api.d.ts
new file mode 100755
index 0000000..9b6a1c4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/demo.api.d.ts
@@ -0,0 +1,83 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+ /**
+ * namespace Demo
+ *
+ * backend api module: "Demo"
+ */
+ namespace Demo {
+ /** demo */
+ type Demo = Common.CommonRecord<{
+ /** 涓婚敭 */
+ id: CommonType.IdType;
+ /** 绉熸埛缂栧彿 */
+ tenantId: CommonType.IdType;
+ /** 閮ㄩ棬id */
+ deptId: CommonType.IdType;
+ /** 鐢ㄦ埛id */
+ userId: CommonType.IdType;
+ /** 鎺掑簭鍙� */
+ orderNum: number;
+ /** key閿� */
+ testKey: string;
+ /** 鍊� */
+ value: string;
+ /** 澶囨敞 */
+ remark: string;
+ /** 鐗堟湰 */
+ version: number;
+ /** 鍒犻櫎鏍囧織 */
+ delFlag: number;
+ }>;
+
+ /** demo search params */
+ type DemoSearchParams = CommonType.RecordNullable<
+ Pick<Api.Demo.Demo, 'deptId' | 'userId' | 'testKey' | 'value'> & Api.Common.CommonSearchParams
+ >;
+
+ /** demo operate params */
+ type DemoOperateParams = CommonType.RecordNullable<
+ Pick<Api.Demo.Demo, 'id' | 'deptId' | 'userId' | 'orderNum' | 'testKey' | 'value' | 'remark'>
+ >;
+
+ /** demo list */
+ type DemoList = Api.Common.PaginatingQueryRecord<Demo>;
+
+ /** tree */
+ type Tree = Common.CommonRecord<{
+ /** 涓婚敭 */
+ id: CommonType.IdType;
+ /** 绉熸埛缂栧彿 */
+ tenantId: CommonType.IdType;
+ /** 鐖秈d */
+ parentId: CommonType.IdType;
+ /** 閮ㄩ棬id */
+ deptId: CommonType.IdType;
+ /** 鐢ㄦ埛id */
+ userId: CommonType.IdType;
+ /** 鍊� */
+ treeName: string;
+ /** 鐗堟湰 */
+ version: number;
+ /** 鍒犻櫎鏍囧織 */
+ delFlag: number;
+ }>;
+
+ /** tree search params */
+ type TreeSearchParams = CommonType.RecordNullable<
+ Pick<Api.Demo.Tree, 'parentId' | 'deptId' | 'userId' | 'treeName'> & Api.Common.CommonSearchParams
+ >;
+
+ /** tree operate params */
+ type TreeOperateParams = CommonType.RecordNullable<
+ Pick<Api.Demo.Tree, 'id' | 'parentId' | 'deptId' | 'userId' | 'treeName'>
+ >;
+
+ /** tree list */
+ type TreeList = Tree[];
+ }
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/monitor.api.d.ts b/ruoyi-plus-soybean/src/typings/api/monitor.api.d.ts
new file mode 100755
index 0000000..06bc1eb
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/monitor.api.d.ts
@@ -0,0 +1,170 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+ /**
+ * namespace Monitor
+ *
+ * backend api module: "monitor"
+ */
+ namespace Monitor {
+ /** 涓氬姟鎿嶄綔绫诲瀷 */
+ type BusinessType = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
+
+ /** oper log */
+ type OperLog = Common.CommonRecord<{
+ /** 鏃ュ織涓婚敭 */
+ operId: CommonType.IdType;
+ /** 绉熸埛缂栧彿 */
+ tenantId: CommonType.IdType;
+ /** 绯荤粺妯″潡 */
+ title: string;
+ /** 鎿嶄綔绫诲瀷 */
+ businessType: Monitor.BusinessType;
+ /** 鏂规硶鍚嶇О */
+ method: string;
+ /** 璇锋眰鏂瑰紡 */
+ requestMethod: string;
+ /** 鎿嶄綔绫诲埆 */
+ operatorType: string;
+ /** 鎿嶄綔浜哄憳 */
+ operName: string;
+ /** 閮ㄩ棬鍚嶇О */
+ deptName: string;
+ /** 璇锋眰URL */
+ operUrl: string;
+ /** 鎿嶄綔IP */
+ operIp: string;
+ /** 鎿嶄綔鍦扮偣 */
+ operLocation: string;
+ /** 璇锋眰鍙傛暟 */
+ operParam: string;
+ /** 杩斿洖鍙傛暟 */
+ jsonResult: string;
+ /** 鎿嶄綔鐘舵�� */
+ status: Common.EnableStatus;
+ /** 閿欒娑堟伅 */
+ errorMsg: string;
+ /** 鎿嶄綔鏃堕棿 */
+ operTime: string;
+ /** 娑堣�楁椂闂� */
+ costTime: number;
+ }>;
+
+ /** oper log search params */
+ type OperLogSearchParams = CommonType.RecordNullable<
+ Pick<Api.Monitor.OperLog, 'title' | 'businessType' | 'operName' | 'operIp' | 'status' | 'operTime'> &
+ Api.Common.CommonSearchParams
+ >;
+
+ /** oper log list */
+ type OperLogList = Api.Common.PaginatingQueryRecord<OperLog>;
+
+ /** login infor */
+ type LoginInfor = Common.CommonRecord<{
+ /** 璁块棶ID */
+ infoId: CommonType.IdType;
+ /** 绉熸埛缂栧彿 */
+ tenantId: CommonType.IdType;
+ /** 鐢ㄦ埛璐﹀彿 */
+ userName: string;
+ /** 瀹㈡埛绔� */
+ clientKey: string;
+ /** 璁惧绫诲瀷 */
+ deviceType: System.DeviceType;
+ /** 鐧诲綍IP鍦板潃 */
+ ipaddr: string;
+ /** 鐧诲綍鍦扮偣 */
+ loginLocation: string;
+ /** 娴忚鍣ㄧ被鍨� */
+ browser: string;
+ /** 鎿嶄綔绯荤粺 */
+ os: string;
+ /** 鐧诲綍鐘舵�侊紙0鎴愬姛 1澶辫触锛� */
+ status: Common.EnableStatus;
+ /** 鎻愮ず娑堟伅 */
+ msg: string;
+ /** 璁块棶鏃堕棿 */
+ loginTime: string;
+ }>;
+
+ /** login infor search params */
+ type LoginInforSearchParams = CommonType.RecordNullable<
+ Pick<Api.Monitor.LoginInfor, 'userName' | 'ipaddr' | 'status'> & Api.Common.CommonSearchParams
+ >;
+
+ /** login infor list */
+ type LoginInforList = Api.Common.PaginatingQueryRecord<LoginInfor>;
+
+ /** cache info */
+ type CacheInfo = Common.CommonRecord<{
+ /** info */
+ info: {
+ /** Redis 鐗堟湰 */
+ redis_version: string;
+ /** 杩愯妯″紡 */
+ redis_mode: string;
+ /** 绔彛 */
+ tcp_port: number;
+ /** 瀹㈡埛绔暟 */
+ connected_clients: number;
+ /** 杩愯鏃堕棿(澶�) */
+ uptime_in_days: number;
+ /** 浣跨敤鍐呭瓨 */
+ used_memory_human: string;
+ /** 浣跨敤 CPU */
+ used_cpu_user_children: string;
+ /** 鍐呭瓨閰嶇疆 */
+ maxmemory_human: number;
+ /** AOF 鏄惁寮�鍚� */
+ aof_enabled: string;
+ /** RDB 鏄惁鎴愬姛 */
+ rdb_last_bgsave_status: string;
+ /** Key 鏁伴噺 */
+ dbSize: number;
+ /** 缃戠粶鍏ュ彛 */
+ instantaneous_input_kbps: number;
+ /** 缃戠粶鍑哄彛 */
+ instantaneous_output_kbps: number;
+ };
+ /** db size */
+ dbSize: number;
+ /** command stats */
+ commandStats: {
+ name: string;
+ value: number;
+ }[];
+ }>;
+
+ type OnlineUser = Common.CommonRecord<{
+ /** 鐢ㄦ埛璐﹀彿 */
+ userName: string;
+ /** 鐧诲綍IP鍦板潃 */
+ ipaddr: string;
+ /** 鐧诲綍鍦扮偣 */
+ loginLocation: string;
+ /** 娴忚鍣ㄧ被鍨� */
+ browser: string;
+ /** 鎿嶄綔绯荤粺 */
+ os: string;
+ /** 鎵�鍦ㄩ儴闂� */
+ deptName: string;
+ /** 璁惧绫诲瀷 */
+ deviceType: System.DeviceType;
+ /** 鐧诲綍鏃堕棿 */
+ loginTime: number;
+ /** 浠ょ墝ID */
+ tokenId: string;
+ }>;
+
+ /** online user list */
+ type OnlineUserList = Api.Common.PaginatingQueryRecord<OnlineUser>;
+
+ /** online user search params */
+ type OnlineUserSearchParams = CommonType.RecordNullable<
+ Pick<Api.Monitor.OnlineUser, 'userName' | 'ipaddr'> & Api.Common.CommonSearchParams
+ >;
+ }
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/route.d.ts b/ruoyi-plus-soybean/src/typings/api/route.d.ts
new file mode 100755
index 0000000..edb755e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/route.d.ts
@@ -0,0 +1,19 @@
+declare namespace Api {
+ /**
+ * namespace Route
+ *
+ * backend api module: "route"
+ */
+ namespace Route {
+ type ElegantConstRoute = import('@elegant-router/types').ElegantConstRoute;
+
+ interface MenuRoute extends ElegantConstRoute {
+ id: string;
+ }
+
+ interface UserRoute {
+ routes: MenuRoute[];
+ home: import('@elegant-router/types').LastLevelRouteKey;
+ }
+ }
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/system.api.d.ts b/ruoyi-plus-soybean/src/typings/api/system.api.d.ts
new file mode 100755
index 0000000..16c7a3d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/system.api.d.ts
@@ -0,0 +1,830 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+ /**
+ * namespace System
+ *
+ * backend api module: "system"
+ */
+ namespace System {
+ /** data scope */
+ type DataScope = '1' | '2' | '3' | '4' | '5' | '6';
+
+ /** role */
+ type Role = Common.CommonRecord<{
+ /** 鏁版嵁鑼冨洿锛�1锛氬叏閮ㄦ暟鎹潈闄� 2锛氳嚜瀹氭暟鎹潈闄� 3锛氭湰閮ㄩ棬鏁版嵁鏉冮檺 4锛氭湰閮ㄩ棬鍙婁互涓嬫暟鎹潈闄愶級 */
+ dataScope: DataScope;
+ /** 閮ㄩ棬鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀� */
+ deptCheckStrictly: boolean;
+ /** 鐢ㄦ埛鏄惁瀛樺湪姝よ鑹叉爣璇� 榛樿涓嶅瓨鍦� */
+ flag: boolean;
+ /** 鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀� */
+ menuCheckStrictly: boolean;
+ /** 澶囨敞 */
+ remark?: string;
+ /** 瑙掕壊ID */
+ roleId: CommonType.IdType;
+ /** 瑙掕壊鏉冮檺瀛楃涓� */
+ roleKey: string;
+ /** 瑙掕壊鍚嶇О */
+ roleName: string;
+ /** 鏄剧ず椤哄簭 */
+ roleSort: number;
+ /** 瑙掕壊鐘舵�侊紙0姝e父 1鍋滅敤锛� */
+ status: Common.EnableStatus;
+ /** 鏄惁绠$悊鍛� */
+ superAdmin: boolean;
+ }>;
+
+ /** role search params */
+ type RoleSearchParams = CommonType.RecordNullable<
+ Pick<Api.System.Role, 'roleName' | 'roleKey' | 'status'> & Api.Common.CommonSearchParams
+ >;
+
+ /** role operate params */
+ type RoleOperateParams = CommonType.RecordNullable<
+ Pick<
+ Api.System.Role,
+ | 'roleId'
+ | 'roleName'
+ | 'roleKey'
+ | 'roleSort'
+ | 'menuCheckStrictly'
+ | 'deptCheckStrictly'
+ | 'dataScope'
+ | 'status'
+ | 'remark'
+ > & { menuIds: CommonType.IdType[]; deptIds: CommonType.IdType[] }
+ >;
+
+ /** role list */
+ type RoleList = Common.PaginatingQueryRecord<Role>;
+
+ /** role menu tree select */
+ type RoleMenuTreeSelect = Common.CommonRecord<{
+ checkedKeys: CommonType.IdType[];
+ menus: MenuList;
+ }>;
+ /** teannt-package menu tree select */
+ type TenantPackageMenuTreeSelect = Common.CommonRecord<{
+ checkedKeys: CommonType.IdType[];
+ menus: MenuList;
+ }>;
+ /** role dept tree select */
+ type RoleDeptTreeSelect = Common.CommonRecord<{
+ checkedKeys: CommonType.IdType[];
+ depts: Dept[];
+ }>;
+
+ /** all role */
+ type AllRole = Pick<Role, 'roleId' | 'roleName' | 'roleKey'>;
+
+ /**
+ * user gender
+ *
+ * - "1": "male"
+ * - "2": "female"
+ */
+ type UserGender = '1' | '2';
+
+ /** user */
+ type User = Common.CommonTenantRecord<{
+ /** 鐢ㄦ埛ID */
+ userId: CommonType.IdType;
+ /** 閮ㄩ棬ID */
+ deptId: CommonType.IdType;
+ /** 閮ㄩ棬鍚嶇О */
+ deptName: string;
+ /** 鐢ㄦ埛璐﹀彿 */
+ userName: string;
+ /** 鐢ㄦ埛鏄电О */
+ nickName: string;
+ /** 鐢ㄦ埛绫诲瀷锛坰ys_user绯荤粺鐢ㄦ埛锛� */
+ userType: string;
+ /** 鐢ㄦ埛閭 */
+ email: string;
+ /** 鎵嬫満鍙风爜 */
+ phonenumber: string;
+ /** 鐢ㄦ埛鎬у埆锛�0鐢� 1濂� 2鏈煡锛� */
+ sex: string;
+ /** 澶村儚鍦板潃 */
+ avatar: string;
+ /** 瀵嗙爜 */
+ password: string;
+ /** 甯愬彿鐘舵�侊紙0姝e父 1鍋滅敤锛� */
+ status: Common.EnableStatus;
+ /** 鏈�鍚庣櫥褰旾P */
+ loginIp: string;
+ /** 鏈�鍚庣櫥褰曟椂闂� */
+ loginDate: Date;
+ /** 澶囨敞 */
+ remark?: string;
+ }>;
+
+ /** user search params */
+ type UserSearchParams = CommonType.RecordNullable<
+ Pick<User, 'deptId' | 'userName' | 'nickName' | 'phonenumber' | 'status'> & {
+ roleId: CommonType.IdType;
+ } & Common.CommonSearchParams
+ >;
+
+ /** user operate params */
+ type UserOperateParams = CommonType.RecordNullable<
+ Pick<
+ User,
+ | 'userId'
+ | 'deptId'
+ | 'userName'
+ | 'nickName'
+ | 'email'
+ | 'phonenumber'
+ | 'sex'
+ | 'password'
+ | 'status'
+ | 'remark'
+ > & { roleIds: CommonType.IdType[]; postIds: CommonType.IdType[] }
+ >;
+
+ /** user profile operate params */
+ type UserProfileOperateParams = CommonType.RecordNullable<Pick<User, 'nickName' | 'email' | 'phonenumber' | 'sex'>>;
+
+ /** user password operate params */
+ type UserPasswordOperateParams = CommonType.RecordNullable<{
+ oldPassword: string;
+ newPassword: string;
+ }>;
+
+ /** user info */
+ type UserInfo = {
+ /** user post ids */
+ postIds: string[];
+ /** user role ids */
+ roleIds: string[];
+ /** roles */
+ roles: Role[];
+ };
+
+ /** user list */
+ type UserList = Common.PaginatingQueryRecord<User>;
+
+ /** auth role */
+ type AuthRole = {
+ user: User;
+ roles: Role[];
+ };
+
+ /** social */
+ type Social = Common.CommonRecord<{
+ /** 涓婚敭 */
+ id: CommonType.IdType;
+ /** 鐢ㄦ埛ID */
+ userId: CommonType.IdType;
+ /** 绉熸埛ID */
+ tenantId: CommonType.IdType;
+ /** 璁よ瘉鐨勫敮涓�ID */
+ authId: string;
+ /** 鐢ㄦ埛鏉ユ簮 */
+ source: string;
+ /** 鐢ㄦ埛鐨勬巿鏉冧护鐗� */
+ accessToken: string;
+ /** 鐢ㄦ埛鐨勬巿鏉冧护鐗岀殑鏈夋晥鏈燂紝閮ㄥ垎骞冲彴鍙兘娌℃湁 */
+ expireIn: number;
+ /** 鍒锋柊浠ょ墝锛岄儴鍒嗗钩鍙板彲鑳芥病鏈� */
+ refreshToken: string;
+ /** 鐢ㄦ埛鐨� open id */
+ openId: string;
+ /** 鎺堟潈鐨勭涓夋柟璐﹀彿 */
+ userName: string;
+ /** 鎺堟潈鐨勭涓夋柟鏄电О */
+ nickName: string;
+ /** 鎺堟潈鐨勭涓夋柟閭 */
+ email: string;
+ /** 鎺堟潈鐨勭涓夋柟澶村儚鍦板潃 */
+ avatar: string;
+ /** 骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁 */
+ accessCode: string;
+ /** 鐢ㄦ埛鐨� unionid */
+ unionId: string;
+ /** 鎺堜簣鐨勬潈闄愶紝閮ㄥ垎骞冲彴鍙兘娌℃湁 */
+ scope: string;
+ /** 涓埆骞冲彴鐨勬巿鏉冧俊鎭紝閮ㄥ垎骞冲彴鍙兘娌℃湁 */
+ tokenType: string;
+ /** id token锛岄儴鍒嗗钩鍙板彲鑳芥病鏈� */
+ idToken: string;
+ /** 灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁 */
+ macAlgorithm: string;
+ /** 灏忕背骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁 */
+ macKey: string;
+ /** 鐢ㄦ埛鐨勬巿鏉僣ode锛岄儴鍒嗗钩鍙板彲鑳芥病鏈� */
+ code: string;
+ /** Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁 */
+ oauthToken: string;
+ /** Twitter骞冲彴鐢ㄦ埛鐨勯檮甯﹀睘鎬э紝閮ㄥ垎骞冲彴鍙兘娌℃湁 */
+ oauthTokenSecret: string;
+ }>;
+
+ /**
+ * icon type
+ *
+ * - "1": iconify icon
+ * - "2": local icon
+ */
+ type IconType = '1' | '2';
+
+ /**
+ * menu layout
+ *
+ * - "0": "榛樿甯冨眬"
+ * - "1": "绌虹櫧甯冨眬"
+ */
+ type MenuLayout = '0' | '1';
+
+ /**
+ * menu type
+ *
+ * - "M": "鐩綍"
+ * - "C": "鑿滃崟"
+ * - "F": "鎸夐挳"
+ */
+ type MenuType = 'M' | 'C' | 'F';
+
+ /**
+ * 鏄惁澶栭摼
+ *
+ * - "0": "鏄�"
+ * - "1": "鍚�"
+ * - "2": "iframe"
+ */
+ type IsMenuFrame = '0' | '1' | '2';
+
+ type Menu = Common.CommonRecord<{
+ /** 鑿滃崟 ID */
+ menuId: CommonType.IdType;
+ /** 鐖惰彍鍗� ID */
+ parentId: CommonType.IdType;
+ /** 鑿滃崟鍚嶇О */
+ menuName: string;
+ /** 鏄剧ず椤哄簭 */
+ orderNum: number;
+ /** 璺敱鍦板潃 */
+ path: string;
+ /** 缁勪欢璺緞 */
+ component: string;
+ /** 璺敱鍙傛暟 */
+ queryParam: string;
+ /** 鏄惁涓哄閾撅紙0鏄� 1鍚� 2iframe锛� */
+ isFrame: IsMenuFrame;
+ /** 鏄惁缂撳瓨锛�0缂撳瓨 1涓嶇紦瀛橈級 */
+ isCache: Common.EnableStatus;
+ /** 鑿滃崟绫诲瀷锛圡鐩綍 C鑿滃崟 F鎸夐挳锛� */
+ menuType: MenuType;
+ /** 鏄剧ず鐘舵�侊紙0鏄剧ず 1闅愯棌锛� */
+ visible: Common.VisibleStatus;
+ /** 鑿滃崟鐘舵�侊紙0姝e父 1鍋滅敤锛� */
+ status: Common.EnableStatus;
+ /** 鏉冮檺鏍囪瘑 */
+ perms: string;
+ /** 鑿滃崟鍥炬爣 */
+ icon: string;
+ /** 澶囨敞 */
+ remark?: string;
+ /** 鐖惰彍鍗曞悕绉� */
+ parentName: string;
+ /** 瀛愯彍鍗� */
+ children: MenuList;
+ id?: CommonType.IdType;
+ label?: string;
+ }>;
+
+ /** menu list */
+ type MenuList = Menu[];
+
+ /** menu search params */
+ type MenuSearchParams = CommonType.RecordNullable<Pick<Menu, 'menuName' | 'status' | 'menuType' | 'parentId'>>;
+
+ /** menu operate params */
+ type MenuOperateParams = CommonType.RecordNullable<
+ Pick<
+ Menu,
+ | 'menuId'
+ | 'menuName'
+ | 'parentId'
+ | 'orderNum'
+ | 'path'
+ | 'component'
+ | 'queryParam'
+ | 'isFrame'
+ | 'isCache'
+ | 'menuType'
+ | 'visible'
+ | 'status'
+ | 'perms'
+ | 'icon'
+ | 'remark'
+ >
+ >;
+
+ /** 瀛楀吀绫诲瀷 */
+ type DictType = Common.CommonRecord<{
+ /** 瀛楀吀涓婚敭 */
+ dictId: CommonType.IdType;
+ /** 瀛楀吀鍚嶇О */
+ dictName: string;
+ /** 瀛楀吀绫诲瀷 */
+ dictType: string;
+ /** 澶囨敞 */
+ remark: string;
+ }>;
+
+ /** dict type search params */
+ type DictTypeSearchParams = CommonType.RecordNullable<
+ Pick<Api.System.DictType, 'dictName' | 'dictType'> & Api.Common.CommonSearchParams
+ >;
+
+ /** dict type operate params */
+ type DictTypeOperateParams = CommonType.RecordNullable<
+ Pick<Api.System.DictType, 'dictId' | 'dictName' | 'dictType' | 'remark'>
+ >;
+
+ /** dict type list */
+ type DictTypeList = Api.Common.PaginatingQueryRecord<DictType>;
+
+ /** 瀛楀吀鏁版嵁 */
+ type DictData = Common.CommonRecord<{
+ /** 鏍峰紡灞炴�э紙鍏朵粬鏍峰紡鎵╁睍锛� */
+ cssClass: string;
+ /** 瀛楀吀缂栫爜 */
+ dictCode: CommonType.IdType;
+ /** 瀛楀吀鏍囩 */
+ dictLabel: string;
+ /** 瀛楀吀鎺掑簭 */
+ dictSort: number;
+ /** 瀛楀吀绫诲瀷 */
+ dictType: string;
+ /** 瀛楀吀閿�� */
+ dictValue: string;
+ /** 鏄惁榛樿锛圷鏄� N鍚︼級 */
+ isDefault: Common.YesOrNoStatus;
+ /** 琛ㄦ牸鍥炴樉鏍峰紡 */
+ listClass: NaiveUI.ThemeColor;
+ /** 澶囨敞 */
+ remark: string;
+ }>;
+
+ /** dict data search params */
+ type DictDataSearchParams = CommonType.RecordNullable<
+ Pick<Api.System.DictData, 'dictLabel' | 'dictType'> & Api.Common.CommonSearchParams
+ >;
+
+ /** dict data operate params */
+ type DictDataOperateParams = CommonType.RecordNullable<
+ Pick<
+ Api.System.DictData,
+ | 'dictCode'
+ | 'dictSort'
+ | 'dictLabel'
+ | 'dictValue'
+ | 'dictType'
+ | 'cssClass'
+ | 'listClass'
+ | 'isDefault'
+ | 'remark'
+ >
+ >;
+
+ /** dict data list */
+ type DictDataList = Api.Common.PaginatingQueryRecord<DictData>;
+
+ /** dept */
+ type Dept = Api.Common.CommonRecord<{
+ /** 閮ㄩ棬id */
+ deptId: CommonType.IdType;
+ /** 绉熸埛缂栧彿 */
+ tenantId: CommonType.IdType;
+ /** 鐖堕儴闂╥d */
+ parentId: CommonType.IdType;
+ /** 绁栫骇鍒楄〃 */
+ ancestors: string;
+ /** 閮ㄩ棬鍚嶇О */
+ deptName: string;
+ /** 閮ㄩ棬绫诲埆缂栫爜 */
+ deptCategory: string;
+ /** 鏄剧ず椤哄簭 */
+ orderNum: number;
+ /** 璐熻矗浜� */
+ leader: number;
+ /** 鑱旂郴鐢佃瘽 */
+ phone: string;
+ /** 閭 */
+ email: string;
+ /** 閮ㄩ棬鐘舵�侊紙0姝e父 1鍋滅敤锛� */
+ status: Common.EnableStatus;
+ /** 瀛愰儴闂� */
+ children: Dept[];
+ }>;
+
+ /** dept search params */
+ type DeptSearchParams = CommonType.RecordNullable<
+ Pick<Api.System.Dept, 'deptName' | 'status'> & Api.Common.CommonSearchParams
+ >;
+
+ /** dept operate params */
+ type DeptOperateParams = CommonType.RecordNullable<
+ Pick<
+ Api.System.Dept,
+ 'deptId' | 'parentId' | 'deptName' | 'deptCategory' | 'orderNum' | 'leader' | 'phone' | 'email' | 'status'
+ >
+ >;
+
+ /** dept list */
+ type DeptList = Api.Common.PaginatingQueryRecord<Dept>;
+
+ /** post */
+ type Post = Common.CommonRecord<{
+ /** 宀椾綅ID */
+ postId: CommonType.IdType;
+ /** 绉熸埛缂栧彿 */
+ tenantId: CommonType.IdType;
+ /** 閮ㄩ棬id */
+ deptId: CommonType.IdType;
+ /** 宀椾綅缂栫爜 */
+ postCode: string;
+ /** 绫诲埆缂栫爜 */
+ postCategory: string;
+ /** 宀椾綅鍚嶇О */
+ postName: string;
+ /** 鏄剧ず椤哄簭 */
+ postSort: number;
+ /** 鐘舵�侊紙0姝e父 1鍋滅敤锛� */
+ status: Common.EnableStatus;
+ /** 澶囨敞 */
+ remark: string;
+ }>;
+
+ /** post search params */
+ type PostSearchParams = CommonType.RecordNullable<
+ Pick<Api.System.Post, 'deptId' | 'postCode' | 'postName' | 'status'> & {
+ belongDeptId: CommonType.IdType;
+ } & Api.Common.CommonSearchParams
+ >;
+
+ /** post operate params */
+ type PostOperateParams = CommonType.RecordNullable<
+ Pick<
+ Api.System.Post,
+ 'postId' | 'deptId' | 'postCode' | 'postCategory' | 'postName' | 'postSort' | 'status' | 'remark'
+ >
+ >;
+
+ /** post list */
+ type PostList = Api.Common.PaginatingQueryRecord<Post>;
+
+ /** config */
+ type Config = Common.CommonRecord<{
+ /** 鍙傛暟涓婚敭 */
+ configId: CommonType.IdType;
+ /** 绉熸埛缂栧彿 */
+ tenantId: CommonType.IdType;
+ /** 鍙傛暟鍚嶇О */
+ configName: string;
+ /** 鍙傛暟閿悕 */
+ configKey: string;
+ /** 鍙傛暟閿�� */
+ configValue: string;
+ /** 鏄惁鍐呯疆 */
+ configType: Common.YesOrNoStatus;
+ /** 澶囨敞 */
+ remark: string;
+ }>;
+
+ /** config search params */
+ type ConfigSearchParams = CommonType.RecordNullable<
+ Pick<Api.System.Config, 'configName' | 'configKey' | 'configType' | 'createTime'> & Api.Common.CommonSearchParams
+ >;
+
+ /** config operate params */
+ type ConfigOperateParams = CommonType.RecordNullable<
+ Pick<Api.System.Config, 'configId' | 'configName' | 'configKey' | 'configValue' | 'configType' | 'remark'>
+ >;
+
+ /** config list */
+ type ConfigList = Api.Common.PaginatingQueryRecord<Config>;
+
+ /** tenant */
+ type Tenant = Common.CommonRecord<{
+ /** id */
+ id: CommonType.IdType;
+ /** 绉熸埛缂栧彿 */
+ tenantId: CommonType.IdType;
+ /** 鑱旂郴浜� */
+ contactUserName: string;
+ /** 鑱旂郴鐢佃瘽 */
+ contactPhone: string;
+ /** 浼佷笟鍚嶇О */
+ companyName: string;
+ /** 缁熶竴绀句細淇$敤浠g爜 */
+ licenseNumber: string;
+ /** 鍦板潃 */
+ address: string;
+ /** 浼佷笟绠�浠� */
+ intro: string;
+ /** 鍩熷悕 */
+ domain: string;
+ /** 澶囨敞 */
+ remark: string;
+ /** 绉熸埛濂楅缂栧彿 */
+ packageId: CommonType.IdType;
+ /** 杩囨湡鏃堕棿 */
+ expireTime: string;
+ /** 鐢ㄦ埛鏁伴噺锛�-1涓嶉檺鍒讹級 */
+ accountCount: number;
+ /** 绉熸埛鐘舵�侊紙0姝e父 1鍋滅敤锛� */
+ status: Common.EnableStatus;
+ /** 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛� */
+ delFlag: string;
+ }>;
+
+ /** tenant search params */
+ type TenantSearchParams = CommonType.RecordNullable<
+ Pick<Api.System.Tenant, 'tenantId' | 'contactUserName' | 'contactPhone' | 'companyName'> &
+ Api.Common.CommonSearchParams
+ >;
+
+ /** tenant operate params */
+ type TenantOperateParams = CommonType.RecordNullable<
+ Pick<
+ Api.System.Tenant,
+ | 'id'
+ | 'tenantId'
+ | 'contactUserName'
+ | 'contactPhone'
+ | 'companyName'
+ | 'licenseNumber'
+ | 'address'
+ | 'intro'
+ | 'domain'
+ | 'remark'
+ | 'packageId'
+ | 'expireTime'
+ | 'accountCount'
+ | 'status'
+ > & {
+ username: string;
+ password: string;
+ }
+ >;
+
+ /** tenant package sync params */
+ type TenantPackageSyncParams = CommonType.RecordNullable<Pick<Api.System.Tenant, 'tenantId' | 'packageId'>>;
+
+ /** tenant list */
+ type TenantList = Api.Common.PaginatingQueryRecord<Tenant>;
+
+ /** tenant package */
+ type TenantPackage = Common.CommonRecord<{
+ /** 绉熸埛濂楅id */
+ packageId: CommonType.IdType;
+ /** 濂楅鍚嶇О */
+ packageName: string;
+ /** 鍏宠仈鑿滃崟id */
+ menuIds: CommonType.IdType[];
+ /** 澶囨敞 */
+ remark: string;
+ /** 鑿滃崟鏍戦�夋嫨椤规槸鍚﹀叧鑱旀樉绀� */
+ menuCheckStrictly: boolean;
+ /** 鐘舵�侊紙0姝e父 1鍋滅敤锛� */
+ status: Common.EnableStatus;
+ /** 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛� */
+ delFlag: string;
+ }>;
+
+ /** tenant package search params */
+ type TenantPackageSearchParams = CommonType.RecordNullable<
+ Pick<Api.System.TenantPackage, 'packageName' | 'menuIds' | 'menuCheckStrictly' | 'status'> &
+ Api.Common.CommonSearchParams
+ >;
+
+ /** tenant package operate params */
+ type TenantPackageOperateParams = CommonType.RecordNullable<
+ Pick<
+ Api.System.TenantPackage,
+ 'packageId' | 'packageName' | 'menuIds' | 'remark' | 'menuCheckStrictly' | 'status'
+ >
+ >;
+
+ /** tenant package list */
+ type TenantPackageList = Api.Common.PaginatingQueryRecord<TenantPackage>;
+
+ /** tenant package select list */
+ type TenantPackageSelectList = Common.CommonRecord<Pick<TenantPackage, 'packageId' | 'packageName'>>;
+
+ /** 閫氱煡鍏憡绫诲瀷 */
+ type NoticeType = '1' | '2';
+
+ /** notice */
+ type Notice = Common.CommonRecord<{
+ /** 鍏憡ID */
+ noticeId: CommonType.IdType;
+ /** 绉熸埛缂栧彿 */
+ tenantId: CommonType.IdType;
+ /** 鍏憡鏍囬 */
+ noticeTitle: string;
+ /** 鍏憡绫诲瀷 */
+ noticeType: System.NoticeType;
+ /** 鍏憡鍐呭 */
+ noticeContent: string;
+ /** 鍏憡鐘舵�� */
+ status: Common.EnableStatus;
+ /** 鍒涘缓鑰� */
+ createByName: string;
+ /** 澶囨敞 */
+ remark: string;
+ }>;
+
+ /** notice search params */
+ type NoticeSearchParams = CommonType.RecordNullable<
+ Pick<Api.System.Notice, 'noticeTitle' | 'noticeType'> & Api.Common.CommonSearchParams
+ >;
+
+ /** notice operate params */
+ type NoticeOperateParams = CommonType.RecordNullable<
+ Pick<Api.System.Notice, 'noticeId' | 'noticeTitle' | 'noticeType' | 'noticeContent' | 'status'>
+ >;
+
+ /** notice list */
+ type NoticeList = Api.Common.PaginatingQueryRecord<Notice>;
+
+ /** 鎺堟潈绫诲瀷 */
+ type GrantType = 'password' | 'sms' | 'password' | 'email' | 'xcx' | 'social';
+
+ /** 璁惧绫诲瀷 */
+ type DeviceType = 'pc' | 'android' | 'ios' | 'xcx';
+
+ /** client */
+ type Client = Common.CommonRecord<{
+ /** id */
+ id: CommonType.IdType;
+ /** 瀹㈡埛绔痠d */
+ clientId: string;
+ /** 瀹㈡埛绔痥ey */
+ clientKey: string;
+ /** 瀹㈡埛绔閽� */
+ clientSecret: string;
+ /** 鎺堟潈绫诲瀷 */
+ grantType: System.GrantType;
+ /** 鎺堟潈绫诲瀷鍒楄〃 */
+ grantTypeList: System.GrantType[];
+ /** 璁惧绫诲瀷 */
+ deviceType: System.DeviceType;
+ /** token娲昏穬瓒呮椂鏃堕棿 */
+ activeTimeout: number;
+ /** token鍥哄畾瓒呮椂 */
+ timeout: number;
+ /** 鐘舵�� */
+ status: Common.EnableStatus;
+ /** 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 1浠h〃鍒犻櫎锛� */
+ delFlag: string;
+ }>;
+
+ /** client search params */
+ type ClientSearchParams = CommonType.RecordNullable<
+ Pick<Api.System.Client, 'clientKey' | 'clientSecret' | 'status'> & Api.Common.CommonSearchParams
+ >;
+
+ /** client operate params */
+ type ClientOperateParams = CommonType.RecordNullable<
+ Pick<
+ Api.System.Client,
+ | 'id'
+ | 'clientId'
+ | 'clientKey'
+ | 'clientSecret'
+ | 'grantTypeList'
+ | 'deviceType'
+ | 'activeTimeout'
+ | 'timeout'
+ | 'status'
+ >
+ >;
+
+ /** client list */
+ type ClientList = Api.Common.PaginatingQueryRecord<Client>;
+
+ /** social source */
+ type SocialSource =
+ | 'maxkey'
+ | 'topiam'
+ | 'qq'
+ | 'weibo'
+ | 'gitee'
+ | 'dingtalk'
+ | 'baidu'
+ | 'csdn'
+ | 'coding'
+ | 'oschina'
+ | 'alipay_wallet'
+ | 'wechat_open'
+ | 'wechat_mp'
+ | 'wechat_enterprise'
+ | 'gitlab'
+ | 'github';
+
+ /** oss */
+ type Oss = Common.CommonRecord<{
+ /** 瀵硅薄瀛樺偍涓婚敭 */
+ ossId: CommonType.IdType;
+ /** 绉熸埛缂栧彿 */
+ tenantId: CommonType.IdType;
+ /** 鏂囦欢鍚� */
+ fileName: string;
+ /** 鍘熷悕 */
+ originalName: string;
+ /** 鏂囦欢鍚庣紑鍚� */
+ fileSuffix: string;
+ /** URL鍦板潃 */
+ url: string;
+ /** 鎵╁睍灞炴�� */
+ ext1: string;
+ /** 鏈嶅姟鍟� */
+ service: string;
+ /** 鍒涘缓鑰呭悕绉� */
+ createByName: string;
+ }>;
+
+ /** oss search params */
+ type OssSearchParams = CommonType.RecordNullable<
+ Pick<Api.System.Oss, 'fileName' | 'originalName' | 'fileSuffix' | 'service'> & Api.Common.CommonSearchParams
+ >;
+
+ /** oss list */
+ type OssList = Api.Common.PaginatingQueryRecord<Oss>;
+
+ /** oss access policy */
+ type OssAccessPolicy = '0' | '1' | '2';
+
+ /** oss config */
+ type OssConfig = Common.CommonRecord<{
+ /** 涓婚敭 */
+ ossConfigId: CommonType.IdType;
+ /** 绉熸埛缂栧彿 */
+ tenantId: CommonType.IdType;
+ /** 閰嶇疆鍚嶇О */
+ configKey: string;
+ /** accessKey */
+ accessKey: string;
+ /** 绉橀挜secretKey */
+ secretKey: string;
+ /** 妗跺悕绉� */
+ bucketName: string;
+ /** 鍓嶇紑 */
+ prefix: string;
+ /** 璁块棶绔欑偣 */
+ endpoint: string;
+ /** 鑷畾涔夊煙鍚� */
+ domain: string;
+ /** 鏄惁https锛圷=鏄�,N=鍚︼級 */
+ isHttps: Common.YesOrNoStatus;
+ /** 鍩� */
+ region: string;
+ /** 妗舵潈闄愮被鍨� */
+ accessPolicy: System.OssAccessPolicy;
+ /** 鏄惁榛樿锛�0=鏄�,1=鍚︼級 */
+ status: Common.EnableStatus;
+ /** 鎵╁睍瀛楁 */
+ ext1: string;
+ /** 澶囨敞 */
+ remark: string;
+ }>;
+
+ /** oss config search params */
+ type OssConfigSearchParams = CommonType.RecordNullable<
+ Pick<Api.System.OssConfig, 'configKey' | 'bucketName' | 'region' | 'status'> & Api.Common.CommonSearchParams
+ >;
+
+ /** oss config operate params */
+ type OssConfigOperateParams = CommonType.RecordNullable<
+ Pick<
+ Api.System.OssConfig,
+ | 'ossConfigId'
+ | 'configKey'
+ | 'accessKey'
+ | 'secretKey'
+ | 'bucketName'
+ | 'prefix'
+ | 'endpoint'
+ | 'domain'
+ | 'isHttps'
+ | 'region'
+ | 'accessPolicy'
+ | 'status'
+ | 'remark'
+ >
+ >;
+
+ /** oss config list */
+ type OssConfigList = Api.Common.PaginatingQueryRecord<OssConfig>;
+ }
+}
diff --git a/ruoyi-plus-soybean/src/typings/api/tool.api.d.ts b/ruoyi-plus-soybean/src/typings/api/tool.api.d.ts
new file mode 100755
index 0000000..52560be
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/api/tool.api.d.ts
@@ -0,0 +1,190 @@
+/**
+ * Namespace Api
+ *
+ * All backend api type
+ */
+declare namespace Api {
+ /**
+ * namespace Tool
+ *
+ * backend api module: "tool"
+ */
+ namespace Tool {
+ /** 鐢熸垚妯℃澘 */
+ type TplCategory = 'crud' | 'tree';
+
+ /** Java绫诲瀷 */
+ type JavaType = 'Long' | 'String' | 'Integer' | 'Double' | 'BigDecimal' | 'Date' | 'Boolean';
+
+ /** 鏌ヨ鏂瑰紡 */
+ type QueryType = 'EQ' | 'NE' | 'GT' | 'GE' | 'LT' | 'LE' | 'LIKE' | 'BETWEEN';
+
+ /** 鏄剧ず绫诲瀷 */
+ type HtmlType =
+ | 'input'
+ | 'textarea'
+ | 'select'
+ | 'radio'
+ | 'checkbox'
+ | 'datetime'
+ | 'imageUpload'
+ | 'fileUpload'
+ | 'editor';
+
+ /**
+ * 鐢熸垚浠g爜鏂瑰紡
+ *
+ * - 0: zip鍘嬬缉鍖�
+ * - 1: 鑷畾涔夎矾寰�
+ */
+ type GenType = '0' | '1';
+
+ /** 浠g爜鐢熸垚涓氬姟琛� */
+ export type GenTable = Common.CommonRecord<{
+ /** 鐢熸垚涓氬姟鍚� */
+ businessName: string;
+ /** 瀹炰綋绫诲悕绉�(棣栧瓧姣嶅ぇ鍐�) */
+ className: string;
+ /** 琛ㄥ垪淇℃伅 */
+ columns?: GenTableColumn[];
+ /** 鏄惁鍗曡〃锛堝鍒犳敼鏌ワ級 */
+ crud?: boolean;
+ /** 鏁版嵁婧愬悕绉� */
+ dataName: string;
+ /** 鐢熸垚浣滆�� */
+ functionAuthor: string;
+ /** 鐢熸垚鍔熻兘鍚� */
+ functionName: string;
+ /** 鐢熸垚璺緞锛堜笉濉粯璁ら」鐩矾寰勶級 */
+ genPath?: string;
+ /** 鐢熸垚浠g爜鏂瑰紡锛�0zip鍘嬬缉鍖� 1鑷畾涔夎矾寰勶級 */
+ genType?: GenType;
+ /** 鑿滃崟 id 鍒楄〃 */
+ menuIds?: CommonType.IdType[];
+ /** 鐢熸垚妯″潡鍚� */
+ moduleName: string;
+ /** 鍏跺畠鐢熸垚閫夐」 */
+ options?: string;
+ /** 鐢熸垚鍖呰矾寰� */
+ packageName: string;
+ /** 涓婄骇鑿滃崟ID瀛楁 */
+ parentMenuId?: CommonType.IdType;
+ /** 涓婄骇鑿滃崟鍚嶇О瀛楁 */
+ parentMenuName?: string;
+ /** 涓婚敭淇℃伅 */
+ pkColumn?: GenTableColumn;
+ /** 澶囨敞 */
+ remark?: string;
+ /** 鏈〃鍏宠仈鐖惰〃鐨勫閿悕 */
+ subTableFkName?: string;
+ /** 鍏宠仈鐖惰〃鐨勮〃鍚� */
+ subTableName?: string;
+ /** 琛ㄦ弿杩� */
+ tableComment: string;
+ /** 缂栧彿 */
+ tableId?: CommonType.IdType;
+ /** 琛ㄥ悕绉� */
+ tableName: string;
+ /** 浣跨敤鐨勬ā鏉匡紙crud鍗曡〃鎿嶄綔 tree鏍戣〃鎿嶄綔 sub涓诲瓙琛ㄦ搷浣滐級 */
+ tplCategory?: TplCategory;
+ /** 鏄惁tree鏍戣〃鎿嶄綔 */
+ tree?: boolean;
+ /** 鏍戠紪鐮佸瓧娈� */
+ treeCode?: string;
+ /** 鏍戝悕绉板瓧娈� */
+ treeName?: string;
+ /** 鏍戠埗缂栫爜瀛楁 */
+ treeParentCode?: string;
+ params: { [key: string]: any };
+ }>;
+
+ /** 浠g爜鐢熸垚涓氬姟瀛楁 */
+ export type GenTableColumn = Common.CommonRecord<{
+ /** 鍒楁弿杩� */
+ columnComment?: string;
+ /** 缂栧彿 */
+ columnId?: CommonType.IdType;
+ /** 鍒楀悕绉� */
+ columnName?: string;
+ /** 鍒楃被鍨� */
+ columnType?: string;
+ /** 瀛楀吀绫诲瀷 */
+ dictType?: string;
+ /** 鏄惁缂栬緫瀛楁锛�1鏄級 */
+ edit?: boolean;
+ /** 鏄剧ず绫诲瀷锛坕nput鏂囨湰妗嗐�乼extarea鏂囨湰鍩熴�乻elect涓嬫媺妗嗐�乧heckbox澶嶉�夋銆乺adio鍗曢�夋銆乨atetime鏃ユ湡鎺т欢銆乮mage鍥剧墖涓婁紶鎺т欢銆乽pload鏂囦欢涓婁紶鎺т欢銆乪ditor瀵屾枃鏈帶浠讹級 */
+ htmlType?: HtmlType;
+ /** 鏄惁鑷锛�1鏄級 */
+ increment?: boolean;
+ /** 鏄惁涓烘彃鍏ュ瓧娈碉紙1鏄級 */
+ insert?: boolean;
+ /** 鏄惁缂栬緫瀛楁锛�1鏄級 */
+ isEdit?: string;
+ /** 鏄惁鑷锛�1鏄級 */
+ isIncrement?: string;
+ /** 鏄惁涓烘彃鍏ュ瓧娈碉紙1鏄級 */
+ isInsert?: string;
+ /** 鏄惁鍒楄〃瀛楁锛�1鏄級 */
+ isList?: string;
+ /** 鏄惁涓婚敭锛�1鏄級 */
+ isPk?: string;
+ /** 鏄惁鏌ヨ瀛楁锛�1鏄級 */
+ isQuery?: string;
+ /** 鏄惁蹇呭~锛�1鏄級 */
+ isRequired?: string;
+ /** JAVA瀛楁鍚� */
+ javaField: string;
+ /** JAVA绫诲瀷 */
+ javaType?: JavaType;
+ /** 鏄惁鍒楄〃瀛楁锛�1鏄級 */
+ list?: boolean;
+ /** 鏄惁涓婚敭锛�1鏄級 */
+ pk?: boolean;
+ /** 鏄惁鏌ヨ瀛楁锛�1鏄級 */
+ query?: boolean;
+ /** 鏌ヨ鏂瑰紡锛圗Q绛変簬銆丯E涓嶇瓑浜庛�丟T澶т簬銆丩T灏忎簬銆丩IKE妯$硦銆丅ETWEEN鑼冨洿锛� */
+ queryType?: QueryType;
+ /** 鏄惁蹇呭~锛�1鏄級 */
+ required?: boolean;
+ /** 鎺掑簭 */
+ sort?: number;
+ /** 鏄惁鍩虹被瀛楁 */
+ superColumn?: boolean;
+ /** 褰掑睘琛ㄧ紪鍙� */
+ tableId?: CommonType.IdType;
+ /** 鍙敤瀛楁 */
+ usableColumn?: boolean;
+ }>;
+
+ /** gen table search params */
+ type GenTableSearchParams = CommonType.RecordNullable<
+ Pick<GenTable, 'dataName' | 'tableName' | 'tableComment'> & Common.CommonSearchParams
+ >;
+
+ /** gen table list */
+ type GenTableList = Common.PaginatingQueryRecord<GenTable>;
+
+ /** gen table search params */
+ type GenTableDbSearchParams = CommonType.RecordNullable<
+ Pick<GenTable, 'dataName' | 'tableName' | 'tableComment'> & Common.CommonSearchParams
+ >;
+
+ /** gen table preview */
+ type GenTablePreview = Record<string, string>;
+
+ /** gen table db list */
+ type GenTableDbList = Common.PaginatingQueryRecord<
+ Common.CommonRecord<Pick<GenTable, 'tableName' | 'tableComment'>>
+ >;
+
+ /** gen table info */
+ type GenTableInfo = {
+ /** 瀛楁淇℃伅 */
+ rows: GenTableColumn[];
+ /** 鐢熸垚淇℃伅 */
+ tables: GenTable[];
+ /** 鍩烘湰淇℃伅 */
+ info: GenTable;
+ };
+ }
+}
diff --git a/ruoyi-plus-soybean/src/typings/app.d.ts b/ruoyi-plus-soybean/src/typings/app.d.ts
new file mode 100755
index 0000000..e598a41
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/app.d.ts
@@ -0,0 +1,1130 @@
+/** The global namespace for the app */
+declare namespace App {
+ /** Theme namespace */
+ namespace Theme {
+ type ColorPaletteNumber = import('@sa/color').ColorPaletteNumber;
+
+ /** NaiveUI theme overrides that can be specified in preset */
+ type NaiveUIThemeOverride = import('naive-ui').GlobalThemeOverrides;
+
+ /** Theme setting */
+ interface ThemeSetting {
+ /** Theme scheme */
+ themeScheme: UnionKey.ThemeScheme;
+ /** grayscale mode */
+ grayscale: boolean;
+ /** colour weakness mode */
+ colourWeakness: boolean;
+ /** Whether to recommend color */
+ recommendColor: boolean;
+ /** Theme color */
+ themeColor: string;
+ /** Theme radius */
+ themeRadius: number;
+ /** Other color */
+ otherColor: OtherColor;
+ /** Whether info color is followed by the primary color */
+ isInfoFollowPrimary: boolean;
+ /** Layout */
+ layout: {
+ /** Layout mode */
+ mode: UnionKey.ThemeLayoutMode;
+ /** Scroll mode */
+ scrollMode: UnionKey.ThemeScrollMode;
+ };
+ /** Page */
+ page: {
+ /** Whether to show the page transition */
+ animate: boolean;
+ /** Page animate mode */
+ animateMode: UnionKey.ThemePageAnimateMode;
+ };
+ /** Header */
+ header: {
+ /** Header height */
+ height: number;
+ /** Header breadcrumb */
+ breadcrumb: {
+ /** Whether to show the breadcrumb */
+ visible: boolean;
+ /** Whether to show the breadcrumb icon */
+ showIcon: boolean;
+ };
+ /** Multilingual */
+ multilingual: {
+ /** Whether to show the multilingual */
+ visible: boolean;
+ };
+ globalSearch: {
+ /** Whether to show the GlobalSearch */
+ visible: boolean;
+ };
+ };
+ /** Tab */
+ tab: {
+ /** Whether to show the tab */
+ visible: boolean;
+ /**
+ * Whether to cache the tab
+ *
+ * If cache, the tabs will get from the local storage when the page is refreshed
+ */
+ cache: boolean;
+ /** Tab height */
+ height: number;
+ /** Tab mode */
+ mode: UnionKey.ThemeTabMode;
+ /** Whether to close tab by middle click */
+ closeTabByMiddleClick: boolean;
+ };
+ /** Fixed header and tab */
+ fixedHeaderAndTab: boolean;
+ /** Sider */
+ sider: {
+ /** Inverted sider */
+ inverted: boolean;
+ /** Sider width */
+ width: number;
+ /** Collapsed sider width */
+ collapsedWidth: number;
+ /** Sider width when the layout is 'vertical-mix', 'top-hybrid-sidebar-first', or 'top-hybrid-header-first' */
+ mixWidth: number;
+ /**
+ * Collapsed sider width when the layout is 'vertical-mix', 'top-hybrid-sidebar-first', or
+ * 'top-hybrid-header-first'
+ */
+ mixCollapsedWidth: number;
+ /** Child menu width when the layout is 'vertical-mix', 'top-hybrid-sidebar-first', or 'top-hybrid-header-first' */
+ mixChildMenuWidth: number;
+ /** Whether to auto select the first submenu */
+ autoSelectFirstMenu: boolean;
+ };
+ /** Footer */
+ footer: {
+ /** Whether to show the footer */
+ visible: boolean;
+ /** Whether fixed the footer */
+ fixed: boolean;
+ /** Footer height */
+ height: number;
+ /**
+ * Whether float the footer to the right when the layout is 'top-hybrid-sidebar-first' or
+ * 'top-hybrid-header-first'
+ */
+ right: boolean;
+ };
+ /** Watermark */
+ watermark: {
+ /** Whether to show the watermark */
+ visible: boolean;
+ /** Watermark text */
+ text: string;
+ /** Whether to use user name as watermark text */
+ enableUserName: boolean;
+ /** Whether to use current time as watermark text */
+ enableTime: boolean;
+ /** Time format for watermark text */
+ timeFormat: string;
+ };
+ table: {
+ /** Whether to show the table border */
+ bordered: boolean;
+ /** Whether to show the table bottom border */
+ bottomBordered: boolean;
+ /** Whether to show the table single column */
+ singleColumn: boolean;
+ /** Whether to show the table single line */
+ singleLine: boolean;
+ /** Whether to show the table size */
+ size: UnionKey.ThemeTableSize;
+ /** Whether to show the table striped */
+ striped: boolean;
+ };
+ /** define some theme settings tokens, will transform to css variables */
+ tokens: {
+ light: ThemeSettingToken;
+ dark?: {
+ [K in keyof ThemeSettingToken]?: Partial<ThemeSettingToken[K]>;
+ };
+ };
+ }
+
+ interface OtherColor {
+ info: string;
+ success: string;
+ warning: string;
+ error: string;
+ }
+
+ interface ThemeColor extends OtherColor {
+ primary: string;
+ }
+
+ type ThemeColorKey = keyof ThemeColor;
+
+ type ThemePaletteColor = {
+ [key in ThemeColorKey | `${ThemeColorKey}-${ColorPaletteNumber}`]: string;
+ };
+
+ type BaseToken = Record<string, Record<string, string>>;
+
+ interface ThemeSettingTokenColor {
+ /** the progress bar color, if not set, will use the primary color */
+ nprogress?: string;
+ container: string;
+ layout: string;
+ inverted: string;
+ 'base-text': string;
+ }
+
+ interface ThemeSettingTokenBoxShadow {
+ header: string;
+ sider: string;
+ tab: string;
+ }
+
+ interface ThemeSettingToken {
+ colors: ThemeSettingTokenColor;
+ boxShadow: ThemeSettingTokenBoxShadow;
+ }
+
+ type ThemeTokenColor = ThemePaletteColor & ThemeSettingTokenColor;
+
+ /** Theme token CSS variables */
+ type ThemeTokenCSSVars = {
+ colors: ThemeTokenColor & { [key: string]: string };
+ boxShadow: ThemeSettingTokenBoxShadow & { [key: string]: string };
+ };
+ }
+
+ /** Global namespace */
+ namespace Global {
+ type VNode = import('vue').VNode;
+ type RouteLocationNormalizedLoaded = import('vue-router').RouteLocationNormalizedLoaded;
+ type RouteKey = import('@elegant-router/types').RouteKey;
+ type RouteMap = import('@elegant-router/types').RouteMap;
+ type RoutePath = import('@elegant-router/types').RoutePath;
+ type LastLevelRouteKey = import('@elegant-router/types').LastLevelRouteKey;
+
+ /** The router push options */
+ type RouterPushOptions = {
+ query?: Record<string, string>;
+ params?: Record<string, string>;
+ };
+
+ /** The global header props */
+ interface HeaderProps {
+ /** Whether to show the logo */
+ showLogo?: boolean;
+ /** Whether to show the menu toggler */
+ showMenuToggler?: boolean;
+ /** Whether to show the menu */
+ showMenu?: boolean;
+ }
+
+ /** The global menu */
+ type Menu = {
+ /**
+ * The menu key
+ *
+ * Equal to the route key
+ */
+ key: string;
+ /** The menu label */
+ label: string;
+ /** The menu i18n key */
+ i18nKey?: I18n.I18nKey | null;
+ /** The route key */
+ routeKey: RouteKey;
+ /** The route path */
+ routePath: RoutePath;
+ /** The menu icon */
+ icon?: () => VNode;
+ /** The menu children */
+ children?: Menu[];
+ };
+
+ type Breadcrumb = Omit<Menu, 'children'> & {
+ options?: Breadcrumb[];
+ };
+
+ /** Tab route */
+ type TabRoute = Pick<RouteLocationNormalizedLoaded, 'name' | 'path' | 'meta'> &
+ Partial<Pick<RouteLocationNormalizedLoaded, 'fullPath' | 'query' | 'matched'>>;
+
+ /** The global tab */
+ type Tab = {
+ /** The tab id */
+ id: string;
+ /** The tab label */
+ label: string;
+ /**
+ * The new tab label
+ *
+ * If set, the tab label will be replaced by this value
+ */
+ newLabel?: string;
+ /**
+ * The old tab label
+ *
+ * when reset the tab label, the tab label will be replaced by this value
+ */
+ oldLabel?: string;
+ /** The tab route key */
+ routeKey: LastLevelRouteKey;
+ /** The tab route path */
+ routePath: RouteMap[LastLevelRouteKey];
+ /** The tab route full path */
+ fullPath: string;
+ /** The tab fixed index */
+ fixedIndex?: number | null;
+ /**
+ * Tab icon
+ *
+ * Iconify icon
+ */
+ icon?: string;
+ /**
+ * Tab local icon
+ *
+ * Local icon
+ */
+ localIcon?: string;
+ /** I18n key */
+ i18nKey?: I18n.I18nKey | null;
+ };
+
+ /** Form rule */
+ type FormRule = import('naive-ui').FormItemRule;
+
+ /** The global dropdown key */
+ type DropdownKey = 'closeCurrent' | 'closeOther' | 'closeLeft' | 'closeRight' | 'closeAll' | 'pin' | 'unpin';
+ }
+
+ /**
+ * I18n namespace
+ *
+ * Locales type
+ */
+ namespace I18n {
+ type RouteKey = import('@elegant-router/types').RouteKey;
+
+ type LangType = 'en-US' | 'zh-CN';
+
+ type LangOption = {
+ label: string;
+ key: LangType;
+ };
+
+ type I18nRouteKey = Exclude<RouteKey, 'root' | 'not-found'>;
+
+ type FormMsg = {
+ required: string;
+ invalid: string;
+ tooltip?: string;
+ };
+
+ type Schema = {
+ system: {
+ title: string;
+ updateTitle: string;
+ updateContent: string;
+ updateConfirm: string;
+ updateCancel: string;
+ };
+ common: {
+ action: string;
+ add: string;
+ addSuccess: string;
+ backToHome: string;
+ batchDelete: string;
+ import: string;
+ export: string;
+ importSuccess: string;
+ importFail: string;
+ importTemplate: string;
+ downloadTemplate: string;
+ importResult: string;
+ importEnd: string;
+ importFormat: string;
+ importSize: string;
+ importTip: string;
+ exportSuccess: string;
+ exportFail: string;
+ updateExisting: string;
+ cancel: string;
+ close: string;
+ check: string;
+ expandColumn: string;
+ columnSetting: string;
+ config: string;
+ login: string;
+ confirm: string;
+ save: string;
+ delete: string;
+ deleteSuccess: string;
+ confirmDelete: string;
+ edit: string;
+ download: string;
+ warning: string;
+ error: string;
+ index: string;
+ keywordSearch: string;
+ logout: string;
+ logoutConfirm: string;
+ lookForward: string;
+ modify: string;
+ modifySuccess: string;
+ noData: string;
+ operate: string;
+ pleaseCheckValue: string;
+ refresh: string;
+ reset: string;
+ search: string;
+ switch: string;
+ tip: string;
+ trigger: string;
+ update: string;
+ updateSuccess: string;
+ saveSuccess: string;
+ noChange: string;
+ userCenter: string;
+ yesOrNo: {
+ yes: string;
+ no: string;
+ };
+ second: string;
+ selected: string;
+ anyRecords: string;
+ clear: string;
+ noSelectRecord: string;
+ };
+ request: {
+ logout: string;
+ logoutMsg: string;
+ logoutWithModal: string;
+ logoutWithModalMsg: string;
+ refreshToken: string;
+ tokenExpired: string;
+ };
+ theme: {
+ themeDrawerTitle: string;
+ tabs: {
+ appearance: string;
+ layout: string;
+ general: string;
+ preset: string;
+ };
+ appearance: {
+ themeSchema: { title: string } & Record<UnionKey.ThemeScheme, string>;
+ grayscale: string;
+ colourWeakness: string;
+ themeColor: {
+ title: string;
+ followPrimary: string;
+ } & Record<Theme.ThemeColorKey, string>;
+ recommendColor: string;
+ recommendColorDesc: string;
+ themeRadius: {
+ title: string;
+ };
+ preset: {
+ title: string;
+ apply: string;
+ applySuccess: string;
+ [key: string]:
+ | {
+ name: string;
+ desc: string;
+ }
+ | string;
+ };
+ };
+ layout: {
+ layoutMode: { title: string } & Record<UnionKey.ThemeLayoutMode, string> & {
+ [K in `${UnionKey.ThemeLayoutMode}_detail`]: string;
+ };
+ tab: {
+ title: string;
+ visible: string;
+ cache: string;
+ cacheTip: string;
+ height: string;
+ mode: { title: string } & Record<UnionKey.ThemeTabMode, string>;
+ closeByMiddleClick: string;
+ closeByMiddleClickTip: string;
+ };
+ header: {
+ title: string;
+ height: string;
+ breadcrumb: {
+ visible: string;
+ showIcon: string;
+ };
+ };
+ sider: {
+ title: string;
+ inverted: string;
+ width: string;
+ collapsedWidth: string;
+ mixWidth: string;
+ mixCollapsedWidth: string;
+ mixChildMenuWidth: string;
+ autoSelectFirstMenu: string;
+ autoSelectFirstMenuTip: string;
+ };
+ footer: {
+ title: string;
+ visible: string;
+ fixed: string;
+ height: string;
+ right: string;
+ };
+ content: {
+ title: string;
+ scrollMode: { title: string; tip: string } & Record<UnionKey.ThemeScrollMode, string>;
+ page: {
+ animate: string;
+ mode: { title: string } & Record<UnionKey.ThemePageAnimateMode, string>;
+ };
+ fixedHeaderAndTab: string;
+ };
+ };
+ general: {
+ title: string;
+ watermark: {
+ title: string;
+ visible: string;
+ text: string;
+ enableUserName: string;
+ enableTime: string;
+ timeFormat: string;
+ };
+ multilingual: {
+ title: string;
+ visible: string;
+ };
+ globalSearch: {
+ title: string;
+ visible: string;
+ };
+ };
+ configOperation: {
+ copyConfig: string;
+ copySuccessMsg: string;
+ resetConfig: string;
+ resetSuccessMsg: string;
+ };
+ tablePropsTitle: string;
+ table: {
+ size: { title: string } & Record<UnionKey.ThemeTableSize, string>;
+ bordered: string;
+ bottomBordered: string;
+ singleColumn: string;
+ singleLine: string;
+ striped: string;
+ };
+ };
+ route: Record<I18nRouteKey, string>;
+ menu: Record<string, string>;
+ dict: Record<string, Record<string, string>>;
+ page: {
+ common: {
+ id: string;
+ createBy: string;
+ createTime: string;
+ updateBy: string;
+ updateTime: string;
+ remark: string;
+ form: {
+ remark: FormMsg;
+ };
+ };
+ login: {
+ common: {
+ title: string;
+ subTitle: string;
+ loginOrRegister: string;
+ register: string;
+ userNamePlaceholder: string;
+ phonePlaceholder: string;
+ codePlaceholder: string;
+ passwordPlaceholder: string;
+ confirmPasswordPlaceholder: string;
+ codeLogin: string;
+ confirm: string;
+ back: string;
+ validateSuccess: string;
+ loginSuccess: string;
+ welcomeBack: string;
+ };
+ pwdLogin: {
+ title: string;
+ rememberMe: string;
+ forgetPassword: string;
+ register: string;
+ otherAccountLogin: string;
+ otherLoginMode: string;
+ superAdmin: string;
+ admin: string;
+ user: string;
+ };
+ codeLogin: {
+ title: string;
+ getCode: string;
+ reGetCode: string;
+ sendCodeSuccess: string;
+ imageCodePlaceholder: string;
+ };
+ register: {
+ title: string;
+ agreement: string;
+ protocol: string;
+ policy: string;
+ };
+ resetPwd: {
+ title: string;
+ };
+ bindWeChat: {
+ title: string;
+ };
+ };
+ home: {
+ branchDesc: string;
+ greeting: string;
+ weatherDesc: string;
+ projectCount: string;
+ todo: string;
+ message: string;
+ downloadCount: string;
+ registerCount: string;
+ schedule: string;
+ study: string;
+ work: string;
+ rest: string;
+ entertainment: string;
+ visitCount: string;
+ turnover: string;
+ dealCount: string;
+ projectNews: {
+ title: string;
+ moreNews: string;
+ desc1: string;
+ desc2: string;
+ desc3: string;
+ desc4: string;
+ desc5: string;
+ };
+ creativity: string;
+ };
+ system: {
+ client: {
+ title: string;
+ clientId: string;
+ clientKey: string;
+ clientSecret: string;
+ grantTypeList: string;
+ deviceType: string;
+ activeTimeout: string;
+ timeout: string;
+ status: string;
+ form: {
+ clientId: FormMsg;
+ clientKey: FormMsg;
+ clientSecret: FormMsg;
+ grantTypeList: FormMsg;
+ deviceType: FormMsg;
+ activeTimeout: FormMsg;
+ timeout: FormMsg;
+ status: FormMsg;
+ };
+ addClient: string;
+ editClient: string;
+ };
+ config: {
+ title: string;
+ configName: string;
+ configKey: string;
+ configValue: string;
+ configType: string;
+ remark: string;
+ createTime: string;
+ refreshCache: string;
+ refreshCacheSuccess: string;
+ form: {
+ configId: FormMsg;
+ configName: FormMsg;
+ configKey: FormMsg;
+ configValue: FormMsg;
+ configType: FormMsg;
+ remark: FormMsg;
+ };
+ addConfig: string;
+ editConfig: string;
+ };
+ dept: {
+ empty: string;
+ title: string;
+ parentId: string;
+ deptName: string;
+ orderNum: string;
+ deptCategory: string;
+ leader: string;
+ phone: string;
+ email: string;
+ status: string;
+ sort: string;
+ createTime: string;
+ expandAll: string;
+ collapseAll: string;
+ form: {
+ parentId: FormMsg;
+ deptName: FormMsg;
+ orderNum: FormMsg;
+ deptCategory: FormMsg;
+ leader: FormMsg;
+ phone: FormMsg;
+ email: FormMsg;
+ status: FormMsg;
+ sort: FormMsg;
+ deptId: FormMsg;
+ };
+ error: {
+ getDeptDataFail: string;
+ getDeptUserDataFail: string;
+ };
+ placeholder: {
+ defaultLeaderPlaceHolder: string;
+ addDataLeaderPlaceHolder: string;
+ deptUserIsEmptyLeaderPlaceHolder: string;
+ };
+ addDept: string;
+ editDept: string;
+ };
+ dict: {
+ title: string;
+ dictTypeTitle: string;
+ dictName: string;
+ dictType: string;
+ status: string;
+ remark: string;
+ createTime: string;
+ refreshCacheSuccess: string;
+ refreshCache: string;
+ confirmDeleteDictType: string;
+ data: {
+ title: string;
+ label: string;
+ value: string;
+ dictSort: string;
+ isDefault: string;
+ listClass: string;
+ cssClass: string;
+ status: string;
+ remark: string;
+ createTime: string;
+ };
+ form: {
+ dictId: FormMsg;
+ dictCode: FormMsg;
+ dictName: FormMsg;
+ dictType: FormMsg;
+ status: FormMsg;
+ remark: FormMsg;
+ dictLabel: FormMsg;
+ dictValue: FormMsg;
+ dictSort: FormMsg;
+ isDefault: FormMsg;
+ listClass: FormMsg;
+ cssClass: FormMsg;
+ };
+ addDict: string;
+ editDict: string;
+ addDictData: string;
+ editDictData: string;
+ addDictType: string;
+ editDictType: string;
+ exportDictType: string;
+ refreshDictType: string;
+ dictTypeIsEmpty: string;
+ };
+ menu: {
+ title: string;
+ parentId: string;
+ iconType: string;
+ menuName: string;
+ icon: string;
+ orderNum: string;
+ perms: string;
+ component: string;
+ path: string;
+ layout: string;
+ externalPath: string;
+ query: string;
+ iframeQuery: string;
+ isFrame: string;
+ isCache: string;
+ menuType: string;
+ visible: string;
+ status: string;
+ createTime: string;
+ cache: string;
+ noCache: string;
+ rootName: string;
+ buttonPermissionList: string;
+ emptyMenu: string;
+ menuDetail: string;
+ cascadeDeleteContent: string;
+ iconifyTip: string;
+ isFrameTip: string;
+ isCacheTip: string;
+ visibleTip: string;
+ statusTip: string;
+ permsTip: string;
+ componentTip: string;
+ pathTip: string;
+ layoutTip: string;
+ form: {
+ parentId: FormMsg;
+ menuType: FormMsg;
+ menuIds: FormMsg;
+ icon: FormMsg;
+ menuName: FormMsg;
+ orderNum: FormMsg;
+ perms: FormMsg;
+ isFrame: FormMsg;
+ path: FormMsg;
+ component: FormMsg;
+ query: FormMsg;
+ isCache: FormMsg;
+ visible: FormMsg;
+ status: FormMsg;
+ permission: FormMsg;
+ };
+ placeholder: {
+ iconifyIconPlaceholder: string;
+ localIconPlaceholder: string;
+ queryKey: string;
+ queryValue: string;
+ queryIframe: string;
+ };
+ directory: string;
+ menu: string;
+ button: string;
+ addMenu: string;
+ addChildMenu: string;
+ editMenu: string;
+ cascadeDelete: string;
+ };
+ notice: {
+ title: string;
+ noticeTitle: string;
+ noticeType: string;
+ noticeContent: string;
+ status: string;
+ createTime: string;
+ form: {
+ noticeTitle: FormMsg;
+ noticeType: FormMsg;
+ noticeContent: FormMsg;
+ status: FormMsg;
+ };
+ addNotice: string;
+ editNotice: string;
+ };
+ oss: {
+ title: string;
+ fileName: string;
+ originalName: string;
+ fileSuffix: string;
+ url: string;
+ createTime: string;
+ service: string;
+ form: {
+ file: FormMsg;
+ };
+ upload: string;
+ preview: string;
+ download: string;
+ copy: string;
+ copySuccess: string;
+ };
+ ossConfig: {
+ title: string;
+ configKey: string;
+ accessKey: string;
+ secretKey: string;
+ bucketName: string;
+ prefix: string;
+ endpoint: string;
+ domain: string;
+ isHttps: string;
+ region: string;
+ status: string;
+ remark: string;
+ createTime: string;
+ form: {
+ configKey: FormMsg;
+ accessKey: FormMsg;
+ secretKey: FormMsg;
+ bucketName: FormMsg;
+ prefix: FormMsg;
+ endpoint: FormMsg;
+ domain: FormMsg;
+ isHttps: FormMsg;
+ region: FormMsg;
+ status: FormMsg;
+ remark: FormMsg;
+ };
+ addOssConfig: string;
+ editOssConfig: string;
+ };
+ post: {
+ title: string;
+ postCode: string;
+ postName: string;
+ postSort: string;
+ status: string;
+ remark: string;
+ createTime: string;
+ form: {
+ postCode: FormMsg;
+ postName: FormMsg;
+ postSort: FormMsg;
+ status: FormMsg;
+ remark: FormMsg;
+ };
+ addPost: string;
+ editPost: string;
+ };
+ role: {
+ title: string;
+ roleName: string;
+ roleKey: string;
+ roleSort: string;
+ status: string;
+ remark: string;
+ menuPermission: string;
+ dataScope: string;
+ createTime: string;
+ form: {
+ roleName: FormMsg;
+ roleKey: FormMsg;
+ roleSort: FormMsg;
+ status: FormMsg;
+ remark: FormMsg;
+ menuIds: FormMsg;
+ deptIds: FormMsg;
+ };
+ addRole: string;
+ editRole: string;
+ configPermission: string;
+ authorizedUsers: string;
+ selectMenuPermission: string;
+ selectDataScope: string;
+ selectDeptPermission: string;
+ };
+ tenant: {
+ title: string;
+ tenantName: string;
+ tenantId: string;
+ contactUserName: string;
+ contactPhone: string;
+ companyName: string;
+ licenseNumber: string;
+ address: string;
+ intro: string;
+ domain: string;
+ packageId: string;
+ expireTime: string;
+ accountCount: string;
+ status: string;
+ createTime: string;
+ form: {
+ tenantName: FormMsg;
+ contactUserName: FormMsg;
+ contactPhone: FormMsg;
+ companyName: FormMsg;
+ licenseNumber: FormMsg;
+ address: FormMsg;
+ intro: FormMsg;
+ domain: FormMsg;
+ packageId: FormMsg;
+ expireTime: FormMsg;
+ accountCount: FormMsg;
+ status: FormMsg;
+ };
+ addTenant: string;
+ editTenant: string;
+ };
+ tenantPackage: {
+ title: string;
+ packageName: string;
+ menuIds: string;
+ remark: string;
+ status: string;
+ createTime: string;
+ form: {
+ packageName: FormMsg;
+ menuIds: FormMsg;
+ status: FormMsg;
+ remark: FormMsg;
+ };
+ addTenantPackage: string;
+ editTenantPackage: string;
+ statusChangeSuccess: string;
+ };
+ user: {
+ title: string;
+ userName: string;
+ nickName: string;
+ deptName: string;
+ phonenumber: string;
+ status: string;
+ createTime: string;
+ password: string;
+ confirmPassword: string;
+ sex: string;
+ roleIds: string;
+ postIds: string;
+ email: string;
+ avatar: string;
+ remark: string;
+ form: {
+ userName: FormMsg;
+ nickName: FormMsg;
+ deptId: FormMsg;
+ phonenumber: FormMsg;
+ status: FormMsg;
+ password: FormMsg;
+ confirmPassword: FormMsg;
+ sex: FormMsg;
+ roleIds: FormMsg;
+ postIds: FormMsg;
+ email: FormMsg;
+ remark: FormMsg;
+ };
+ addUser: string;
+ editUser: string;
+ resetPassword: string;
+ importUsers: string;
+ exportTemplate: string;
+ importSuccess: string;
+ statusChangeSuccess: string;
+ };
+ };
+ about: {
+ title: string;
+ introduction: string;
+ projectInfo: {
+ title: string;
+ version: string;
+ latestBuildTime: string;
+ documentLink: string;
+ previewLink: string;
+ repositoryLink: string;
+ };
+ prdDep: string;
+ devDep: string;
+ };
+ };
+ form: {
+ required: string;
+ userName: FormMsg;
+ phone: FormMsg;
+ pwd: FormMsg;
+ confirmPwd: FormMsg;
+ code: FormMsg;
+ email: FormMsg;
+ };
+ dropdown: Record<Global.DropdownKey, string>;
+ icon: {
+ themeConfig: string;
+ themeSchema: string;
+ lang: string;
+ fullscreen: string;
+ fullscreenExit: string;
+ reload: string;
+ collapse: string;
+ expand: string;
+ pin: string;
+ unpin: string;
+ };
+ datatable: {
+ itemCount: string;
+ };
+ };
+
+ type GetI18nKey<T extends Record<string, unknown>, K extends keyof T = keyof T> = K extends string
+ ? T[K] extends Record<string, unknown>
+ ? `${K}.${GetI18nKey<T[K]>}`
+ : K
+ : never;
+
+ type I18nKey = GetI18nKey<Schema>;
+
+ type TranslateOptions<Locales extends string> = import('vue-i18n').TranslateOptions<Locales>;
+
+ interface $T {
+ (key: I18nKey): string;
+ (key: I18nKey, plural: number, options?: TranslateOptions<LangType>): string;
+ (key: I18nKey, defaultMsg: string, options?: TranslateOptions<I18nKey>): string;
+ (key: I18nKey, list: unknown[], options?: TranslateOptions<I18nKey>): string;
+ (key: I18nKey, list: unknown[], plural: number): string;
+ (key: I18nKey, list: unknown[], defaultMsg: string): string;
+ (key: I18nKey, named: Record<string, unknown>, options?: TranslateOptions<LangType>): string;
+ (key: I18nKey, named: Record<string, unknown>, plural: number): string;
+ (key: I18nKey, named: Record<string, unknown>, defaultMsg: string): string;
+ }
+ }
+
+ /** Service namespace */
+ namespace Service {
+ /** Other baseURL key */
+ type OtherBaseURLKey = 'demo';
+
+ interface ServiceConfigItem {
+ /** The backend service base url */
+ baseURL: string;
+ /** The proxy pattern of the backend service base url */
+ proxyPattern: string;
+ ws?: boolean;
+ }
+
+ interface OtherServiceConfigItem extends ServiceConfigItem {
+ key: OtherBaseURLKey;
+ }
+
+ /** The backend service config */
+ interface ServiceConfig extends ServiceConfigItem {
+ /** Other backend service config */
+ other: OtherServiceConfigItem[];
+ }
+
+ interface SimpleServiceConfig extends Pick<ServiceConfigItem, 'baseURL'> {
+ other: Record<OtherBaseURLKey, string>;
+ }
+
+ /** The backend service response data */
+ type Response<T = unknown> = {
+ /** The backend service response code */
+ code: string;
+ /** The backend service response message */
+ msg: string;
+ /** The backend service response data */
+ data: T;
+ rows?: any[];
+ total?: number;
+ };
+
+ /** The demo backend service response data */
+ type DemoResponse<T = unknown> = {
+ /** The backend service response code */
+ status: string;
+ /** The backend service response message */
+ message: string;
+ /** The backend service response data */
+ result: T;
+ };
+ }
+}
diff --git a/ruoyi-plus-soybean/src/typings/common.d.ts b/ruoyi-plus-soybean/src/typings/common.d.ts
new file mode 100755
index 0000000..b2c16e1
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/common.d.ts
@@ -0,0 +1,46 @@
+/** The common type namespace */
+declare namespace CommonType {
+ /** The strategic pattern */
+ interface StrategicPattern {
+ /** The condition */
+ condition: boolean;
+ /** If the condition is true, then call the action function */
+ callback: () => void;
+ }
+
+ /**
+ * The option type
+ *
+ * @property value: The option value
+ * @property label: The option label
+ */
+ type Option<K = string, M = string> = { value: K; label: M };
+
+ /** The record type */
+ type Record<K extends string | number = string> = { [key in K]: string };
+
+ type YesOrNo = 'Y' | 'N';
+
+ /** add null to all properties */
+ type RecordNullable<T> = {
+ [K in keyof T]?: T[K] | null;
+ };
+
+ /** The id type */
+ type IdType = string | number;
+
+ /** The res error code */
+ type ErrorCode = '401' | '403' | '404' | 'default';
+
+ /** The configuration options for constructing tree structure data */
+ type TreeConfig<T> = {
+ /** id field name */
+ idField?: keyof T;
+ /** parent id field name */
+ parentIdField?: keyof T;
+ /** children field name */
+ childrenField?: keyof T;
+ /** filter function */
+ filterFn?: (node: any) => boolean;
+ };
+}
diff --git a/ruoyi-plus-soybean/src/typings/components.d.ts b/ruoyi-plus-soybean/src/typings/components.d.ts
new file mode 100755
index 0000000..a3a54e0
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/components.d.ts
@@ -0,0 +1,312 @@
+/* eslint-disable */
+// @ts-nocheck
+// biome-ignore lint: disable
+// oxlint-disable
+// ------
+// Generated by unplugin-vue-components
+// Read more: https://github.com/vuejs/core/pull/3399
+import { GlobalComponents } from 'vue'
+
+export {}
+
+/* prettier-ignore */
+declare module 'vue' {
+ export interface GlobalComponents {
+ AppProvider: typeof import('./../components/common/app-provider.vue')['default']
+ BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
+ BooleanTag: typeof import('./../components/custom/boolean-tag.vue')['default']
+ ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
+ CountTo: typeof import('./../components/custom/count-to.vue')['default']
+ DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
+ DataTable: typeof import('./../components/common/data-table.vue')['default']
+ DeptTree: typeof import('./../components/custom/dept-tree.vue')['default']
+ DeptTreeSelect: typeof import('./../components/custom/dept-tree-select.vue')['default']
+ DictCheckbox: typeof import('./../components/custom/dict-checkbox.vue')['default']
+ DictRadio: typeof import('./../components/custom/dict-radio.vue')['default']
+ DictSelect: typeof import('./../components/custom/dict-select.vue')['default']
+ DictTag: typeof import('./../components/custom/dict-tag.vue')['default']
+ ExceptionBase: typeof import('./../components/common/exception-base.vue')['default']
+ FileUpload: typeof import('./../components/custom/file-upload.vue')['default']
+ FormTip: typeof import('./../components/custom/form-tip.vue')['default']
+ FullScreen: typeof import('./../components/common/full-screen.vue')['default']
+ IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
+ IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
+ IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
+ IconEpCopyDocument: typeof import('~icons/ep/copy-document')['default']
+ IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
+ IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
+ IconHugeiconsConfiguration01: typeof import('~icons/hugeicons/configuration01')['default']
+ IconIcRoundBarChart: typeof import('~icons/ic/round-bar-chart')['default']
+ IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
+ IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
+ IconIcRoundTableView: typeof import('~icons/ic/round-table-view')['default']
+ IconLocalBanner: typeof import('~icons/local/banner')['default']
+ IconLocalLogo: typeof import('~icons/local/logo')['default']
+ IconMaterialSymbolsAdd: typeof import('~icons/material-symbols/add')['default']
+ IconMaterialSymbolsAddRounded: typeof import('~icons/material-symbols/add-rounded')['default']
+ IconMaterialSymbolsDeleteOutline: typeof import('~icons/material-symbols/delete-outline')['default']
+ IconMaterialSymbolsDownloadRounded: typeof import('~icons/material-symbols/download-rounded')['default']
+ IconMaterialSymbolsDriveFileRenameOutlineOutline: typeof import('~icons/material-symbols/drive-file-rename-outline-outline')['default']
+ IconMaterialSymbolsImageOutline: typeof import('~icons/material-symbols/image-outline')['default']
+ IconMaterialSymbolsRefreshRounded: typeof import('~icons/material-symbols/refresh-rounded')['default']
+ IconMaterialSymbolsSyncOutline: typeof import('~icons/material-symbols/sync-outline')['default']
+ IconMaterialSymbolsUploadRounded: typeof import('~icons/material-symbols/upload-rounded')['default']
+ IconMaterialSymbolsWarningOutlineRounded: typeof import('~icons/material-symbols/warning-outline-rounded')['default']
+ IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
+ IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
+ IconMdiDrag: typeof import('~icons/mdi/drag')['default']
+ IconMdiGithub: typeof import('~icons/mdi/github')['default']
+ IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
+ IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
+ IconQuillCollapse: typeof import('~icons/quill/collapse')['default']
+ IconQuillExpand: typeof import('~icons/quill/expand')['default']
+ IconSimpleIconsGitee: typeof import('~icons/simple-icons/gitee')['default']
+ IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default']
+ IconUilSearch: typeof import('~icons/uil/search')['default']
+ JsonPreview: typeof import('./../components/custom/json-preview.vue')['default']
+ LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
+ LookForward: typeof import('./../components/custom/look-forward.vue')['default']
+ MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
+ MenuTree: typeof import('./../components/custom/menu-tree.vue')['default']
+ MenuTreeSelect: typeof import('./../components/custom/menu-tree-select.vue')['default']
+ NA: typeof import('naive-ui')['NA']
+ NAlert: typeof import('naive-ui')['NAlert']
+ NAvatar: typeof import('naive-ui')['NAvatar']
+ NBadge: typeof import('naive-ui')['NBadge']
+ NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
+ NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
+ NButton: typeof import('naive-ui')['NButton']
+ NCard: typeof import('naive-ui')['NCard']
+ NCheckbox: typeof import('naive-ui')['NCheckbox']
+ NCode: typeof import('naive-ui')['NCode']
+ NCollapse: typeof import('naive-ui')['NCollapse']
+ NCollapseItem: typeof import('naive-ui')['NCollapseItem']
+ NColorPicker: typeof import('naive-ui')['NColorPicker']
+ NDataTable: typeof import('naive-ui')['NDataTable']
+ NDatePicker: typeof import('naive-ui')['NDatePicker']
+ NDescriptions: typeof import('naive-ui')['NDescriptions']
+ NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
+ NDialogProvider: typeof import('naive-ui')['NDialogProvider']
+ NDivider: typeof import('naive-ui')['NDivider']
+ NDrawer: typeof import('naive-ui')['NDrawer']
+ NDrawerContent: typeof import('naive-ui')['NDrawerContent']
+ NDropdown: typeof import('naive-ui')['NDropdown']
+ NDynamicInput: typeof import('naive-ui')['NDynamicInput']
+ NEllipsis: typeof import('naive-ui')['NEllipsis']
+ NEmpty: typeof import('naive-ui')['NEmpty']
+ NForm: typeof import('naive-ui')['NForm']
+ NFormItem: typeof import('naive-ui')['NFormItem']
+ NFormItemGi: typeof import('naive-ui')['NFormItemGi']
+ NGi: typeof import('naive-ui')['NGi']
+ NGrid: typeof import('naive-ui')['NGrid']
+ NGridItem: typeof import('naive-ui')['NGridItem']
+ NInput: typeof import('naive-ui')['NInput']
+ NInputGroup: typeof import('naive-ui')['NInputGroup']
+ NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
+ NInputNumber: typeof import('naive-ui')['NInputNumber']
+ NLayout: typeof import('naive-ui')['NLayout']
+ NLayoutContent: typeof import('naive-ui')['NLayoutContent']
+ NLayoutSider: typeof import('naive-ui')['NLayoutSider']
+ NList: typeof import('naive-ui')['NList']
+ NListItem: typeof import('naive-ui')['NListItem']
+ NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
+ NMenu: typeof import('naive-ui')['NMenu']
+ NMessageProvider: typeof import('naive-ui')['NMessageProvider']
+ NModal: typeof import('naive-ui')['NModal']
+ NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
+ NP: typeof import('naive-ui')['NP']
+ NPopconfirm: typeof import('naive-ui')['NPopconfirm']
+ NPopover: typeof import('naive-ui')['NPopover']
+ NRadio: typeof import('naive-ui')['NRadio']
+ NRadioButton: typeof import('naive-ui')['NRadioButton']
+ NRadioGroup: typeof import('naive-ui')['NRadioGroup']
+ NScrollbar: typeof import('naive-ui')['NScrollbar']
+ NSelect: typeof import('naive-ui')['NSelect']
+ NSpace: typeof import('naive-ui')['NSpace']
+ NSpin: typeof import('naive-ui')['NSpin']
+ NStatistic: typeof import('naive-ui')['NStatistic']
+ NSwitch: typeof import('naive-ui')['NSwitch']
+ NTab: typeof import('naive-ui')['NTab']
+ NTabPane: typeof import('naive-ui')['NTabPane']
+ NTabs: typeof import('naive-ui')['NTabs']
+ NTag: typeof import('naive-ui')['NTag']
+ NText: typeof import('naive-ui')['NText']
+ NThing: typeof import('naive-ui')['NThing']
+ NTooltip: typeof import('naive-ui')['NTooltip']
+ NTree: typeof import('naive-ui')['NTree']
+ NTreeSelect: typeof import('naive-ui')['NTreeSelect']
+ NUpload: typeof import('naive-ui')['NUpload']
+ NUploadDragger: typeof import('naive-ui')['NUploadDragger']
+ NWatermark: typeof import('naive-ui')['NWatermark']
+ OssUpload: typeof import('./../components/custom/oss-upload.vue')['default']
+ PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
+ PostSelect: typeof import('./../components/custom/post-select.vue')['default']
+ ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
+ RoleSelect: typeof import('./../components/custom/role-select.vue')['default']
+ RouterLink: typeof import('vue-router')['RouterLink']
+ RouterView: typeof import('vue-router')['RouterView']
+ SoybeanAvatar: typeof import('./../components/custom/soybean-avatar.vue')['default']
+ StatusSwitch: typeof import('./../components/custom/status-switch.vue')['default']
+ SvgIcon: typeof import('./../components/custom/svg-icon.vue')['default']
+ SystemLogo: typeof import('./../components/common/system-logo.vue')['default']
+ TableColumnSetting: typeof import('./../components/advanced/table-column-setting.vue')['default']
+ TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default']
+ TableRowCheckAlert: typeof import('./../components/advanced/table-row-check-alert.vue')['default']
+ TableSiderLayout: typeof import('./../components/advanced/table-sider-layout.vue')['default']
+ TenantSelect: typeof import('./../components/custom/tenant-select.vue')['default']
+ ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
+ UmoDocEditor: typeof import('./../components/custom/umo-doc-editor.vue')['default']
+ UserSelect: typeof import('./../components/custom/user-select.vue')['default']
+ WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
+ }
+}
+
+// For TSX support
+declare global {
+ const AppProvider: typeof import('./../components/common/app-provider.vue')['default']
+ const BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
+ const BooleanTag: typeof import('./../components/custom/boolean-tag.vue')['default']
+ const ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
+ const CountTo: typeof import('./../components/custom/count-to.vue')['default']
+ const DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
+ const DataTable: typeof import('./../components/common/data-table.vue')['default']
+ const DeptTree: typeof import('./../components/custom/dept-tree.vue')['default']
+ const DeptTreeSelect: typeof import('./../components/custom/dept-tree-select.vue')['default']
+ const DictCheckbox: typeof import('./../components/custom/dict-checkbox.vue')['default']
+ const DictRadio: typeof import('./../components/custom/dict-radio.vue')['default']
+ const DictSelect: typeof import('./../components/custom/dict-select.vue')['default']
+ const DictTag: typeof import('./../components/custom/dict-tag.vue')['default']
+ const ExceptionBase: typeof import('./../components/common/exception-base.vue')['default']
+ const FileUpload: typeof import('./../components/custom/file-upload.vue')['default']
+ const FormTip: typeof import('./../components/custom/form-tip.vue')['default']
+ const FullScreen: typeof import('./../components/common/full-screen.vue')['default']
+ const IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
+ const IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
+ const IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
+ const IconEpCopyDocument: typeof import('~icons/ep/copy-document')['default']
+ const IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
+ const IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
+ const IconHugeiconsConfiguration01: typeof import('~icons/hugeicons/configuration01')['default']
+ const IconIcRoundBarChart: typeof import('~icons/ic/round-bar-chart')['default']
+ const IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
+ const IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
+ const IconIcRoundTableView: typeof import('~icons/ic/round-table-view')['default']
+ const IconLocalBanner: typeof import('~icons/local/banner')['default']
+ const IconLocalLogo: typeof import('~icons/local/logo')['default']
+ const IconMaterialSymbolsAdd: typeof import('~icons/material-symbols/add')['default']
+ const IconMaterialSymbolsAddRounded: typeof import('~icons/material-symbols/add-rounded')['default']
+ const IconMaterialSymbolsDeleteOutline: typeof import('~icons/material-symbols/delete-outline')['default']
+ const IconMaterialSymbolsDownloadRounded: typeof import('~icons/material-symbols/download-rounded')['default']
+ const IconMaterialSymbolsDriveFileRenameOutlineOutline: typeof import('~icons/material-symbols/drive-file-rename-outline-outline')['default']
+ const IconMaterialSymbolsImageOutline: typeof import('~icons/material-symbols/image-outline')['default']
+ const IconMaterialSymbolsRefreshRounded: typeof import('~icons/material-symbols/refresh-rounded')['default']
+ const IconMaterialSymbolsSyncOutline: typeof import('~icons/material-symbols/sync-outline')['default']
+ const IconMaterialSymbolsUploadRounded: typeof import('~icons/material-symbols/upload-rounded')['default']
+ const IconMaterialSymbolsWarningOutlineRounded: typeof import('~icons/material-symbols/warning-outline-rounded')['default']
+ const IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
+ const IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
+ const IconMdiDrag: typeof import('~icons/mdi/drag')['default']
+ const IconMdiGithub: typeof import('~icons/mdi/github')['default']
+ const IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
+ const IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
+ const IconQuillCollapse: typeof import('~icons/quill/collapse')['default']
+ const IconQuillExpand: typeof import('~icons/quill/expand')['default']
+ const IconSimpleIconsGitee: typeof import('~icons/simple-icons/gitee')['default']
+ const IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default']
+ const IconUilSearch: typeof import('~icons/uil/search')['default']
+ const JsonPreview: typeof import('./../components/custom/json-preview.vue')['default']
+ const LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
+ const LookForward: typeof import('./../components/custom/look-forward.vue')['default']
+ const MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
+ const MenuTree: typeof import('./../components/custom/menu-tree.vue')['default']
+ const MenuTreeSelect: typeof import('./../components/custom/menu-tree-select.vue')['default']
+ const NA: typeof import('naive-ui')['NA']
+ const NAlert: typeof import('naive-ui')['NAlert']
+ const NAvatar: typeof import('naive-ui')['NAvatar']
+ const NBadge: typeof import('naive-ui')['NBadge']
+ const NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
+ const NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
+ const NButton: typeof import('naive-ui')['NButton']
+ const NCard: typeof import('naive-ui')['NCard']
+ const NCheckbox: typeof import('naive-ui')['NCheckbox']
+ const NCode: typeof import('naive-ui')['NCode']
+ const NCollapse: typeof import('naive-ui')['NCollapse']
+ const NCollapseItem: typeof import('naive-ui')['NCollapseItem']
+ const NColorPicker: typeof import('naive-ui')['NColorPicker']
+ const NDataTable: typeof import('naive-ui')['NDataTable']
+ const NDatePicker: typeof import('naive-ui')['NDatePicker']
+ const NDescriptions: typeof import('naive-ui')['NDescriptions']
+ const NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
+ const NDialogProvider: typeof import('naive-ui')['NDialogProvider']
+ const NDivider: typeof import('naive-ui')['NDivider']
+ const NDrawer: typeof import('naive-ui')['NDrawer']
+ const NDrawerContent: typeof import('naive-ui')['NDrawerContent']
+ const NDropdown: typeof import('naive-ui')['NDropdown']
+ const NDynamicInput: typeof import('naive-ui')['NDynamicInput']
+ const NEllipsis: typeof import('naive-ui')['NEllipsis']
+ const NEmpty: typeof import('naive-ui')['NEmpty']
+ const NForm: typeof import('naive-ui')['NForm']
+ const NFormItem: typeof import('naive-ui')['NFormItem']
+ const NFormItemGi: typeof import('naive-ui')['NFormItemGi']
+ const NGi: typeof import('naive-ui')['NGi']
+ const NGrid: typeof import('naive-ui')['NGrid']
+ const NGridItem: typeof import('naive-ui')['NGridItem']
+ const NInput: typeof import('naive-ui')['NInput']
+ const NInputGroup: typeof import('naive-ui')['NInputGroup']
+ const NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
+ const NInputNumber: typeof import('naive-ui')['NInputNumber']
+ const NLayout: typeof import('naive-ui')['NLayout']
+ const NLayoutContent: typeof import('naive-ui')['NLayoutContent']
+ const NLayoutSider: typeof import('naive-ui')['NLayoutSider']
+ const NList: typeof import('naive-ui')['NList']
+ const NListItem: typeof import('naive-ui')['NListItem']
+ const NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
+ const NMenu: typeof import('naive-ui')['NMenu']
+ const NMessageProvider: typeof import('naive-ui')['NMessageProvider']
+ const NModal: typeof import('naive-ui')['NModal']
+ const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
+ const NP: typeof import('naive-ui')['NP']
+ const NPopconfirm: typeof import('naive-ui')['NPopconfirm']
+ const NPopover: typeof import('naive-ui')['NPopover']
+ const NRadio: typeof import('naive-ui')['NRadio']
+ const NRadioButton: typeof import('naive-ui')['NRadioButton']
+ const NRadioGroup: typeof import('naive-ui')['NRadioGroup']
+ const NScrollbar: typeof import('naive-ui')['NScrollbar']
+ const NSelect: typeof import('naive-ui')['NSelect']
+ const NSpace: typeof import('naive-ui')['NSpace']
+ const NSpin: typeof import('naive-ui')['NSpin']
+ const NStatistic: typeof import('naive-ui')['NStatistic']
+ const NSwitch: typeof import('naive-ui')['NSwitch']
+ const NTab: typeof import('naive-ui')['NTab']
+ const NTabPane: typeof import('naive-ui')['NTabPane']
+ const NTabs: typeof import('naive-ui')['NTabs']
+ const NTag: typeof import('naive-ui')['NTag']
+ const NText: typeof import('naive-ui')['NText']
+ const NThing: typeof import('naive-ui')['NThing']
+ const NTooltip: typeof import('naive-ui')['NTooltip']
+ const NTree: typeof import('naive-ui')['NTree']
+ const NTreeSelect: typeof import('naive-ui')['NTreeSelect']
+ const NUpload: typeof import('naive-ui')['NUpload']
+ const NUploadDragger: typeof import('naive-ui')['NUploadDragger']
+ const NWatermark: typeof import('naive-ui')['NWatermark']
+ const OssUpload: typeof import('./../components/custom/oss-upload.vue')['default']
+ const PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
+ const PostSelect: typeof import('./../components/custom/post-select.vue')['default']
+ const ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
+ const RoleSelect: typeof import('./../components/custom/role-select.vue')['default']
+ const RouterLink: typeof import('vue-router')['RouterLink']
+ const RouterView: typeof import('vue-router')['RouterView']
+ const SoybeanAvatar: typeof import('./../components/custom/soybean-avatar.vue')['default']
+ const StatusSwitch: typeof import('./../components/custom/status-switch.vue')['default']
+ const SvgIcon: typeof import('./../components/custom/svg-icon.vue')['default']
+ const SystemLogo: typeof import('./../components/common/system-logo.vue')['default']
+ const TableColumnSetting: typeof import('./../components/advanced/table-column-setting.vue')['default']
+ const TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default']
+ const TableRowCheckAlert: typeof import('./../components/advanced/table-row-check-alert.vue')['default']
+ const TableSiderLayout: typeof import('./../components/advanced/table-sider-layout.vue')['default']
+ const TenantSelect: typeof import('./../components/custom/tenant-select.vue')['default']
+ const ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
+ const UmoDocEditor: typeof import('./../components/custom/umo-doc-editor.vue')['default']
+ const UserSelect: typeof import('./../components/custom/user-select.vue')['default']
+ const WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
+}
\ No newline at end of file
diff --git a/ruoyi-plus-soybean/src/typings/elegant-router.d.ts b/ruoyi-plus-soybean/src/typings/elegant-router.d.ts
new file mode 100755
index 0000000..8ee31f6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/elegant-router.d.ts
@@ -0,0 +1,319 @@
+/* eslint-disable */
+/* prettier-ignore */
+// Generated by elegant-router
+// Read more: https://github.com/soybeanjs/elegant-router
+
+declare module "@elegant-router/types" {
+ type ElegantConstRoute = import('@elegant-router/vue').ElegantConstRoute;
+
+ /**
+ * route layout
+ */
+ export type RouteLayout = "base" | "blank";
+
+ /**
+ * route map
+ */
+ export type RouteMap = {
+ "root": "/";
+ "not-found": "/:pathMatch(.*)*";
+ "exception": "/exception";
+ "exception_403": "/exception/403";
+ "exception_404": "/exception/404";
+ "exception_500": "/exception/500";
+ "403": "/403";
+ "404": "/404";
+ "500": "/500";
+ "about": "/about";
+ "analy": "/analy";
+ "analy_hoister": "/analy/hoister";
+ "analy_output-analy": "/analy/output-analy";
+ "analy_packer": "/analy/packer";
+ "analy_roller": "/analy/roller";
+ "demo": "/demo";
+ "demo_demo": "/demo/demo";
+ "demo_tree": "/demo/tree";
+ "home": "/home";
+ "iframe-page": "/iframe-page/:url";
+ "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
+ "monitor": "/monitor";
+ "monitor_cache": "/monitor/cache";
+ "monitor_logininfor": "/monitor/logininfor";
+ "monitor_online": "/monitor/online";
+ "monitor_operlog": "/monitor/operlog";
+ "social-callback": "/social-callback";
+ "system": "/system";
+ "system_client": "/system/client";
+ "system_config": "/system/config";
+ "system_dept": "/system/dept";
+ "system_dict": "/system/dict";
+ "system_menu": "/system/menu";
+ "system_notice": "/system/notice";
+ "system_oss": "/system/oss";
+ "system_oss-config": "/system/oss-config";
+ "system_post": "/system/post";
+ "system_role": "/system/role";
+ "system_tenant": "/system/tenant";
+ "system_tenant-package": "/system/tenant-package";
+ "system_user": "/system/user";
+ "tool": "/tool";
+ "tool_gen": "/tool/gen";
+ "user-center": "/user-center";
+ };
+
+ /**
+ * route key
+ */
+ export type RouteKey = keyof RouteMap;
+
+ /**
+ * route path
+ */
+ export type RoutePath = RouteMap[RouteKey];
+
+ /**
+ * custom route key
+ */
+ export type CustomRouteKey = Extract<
+ RouteKey,
+ | "root"
+ | "not-found"
+ | "exception"
+ | "exception_403"
+ | "exception_404"
+ | "exception_500"
+ >;
+
+ /**
+ * the generated route key
+ */
+ export type GeneratedRouteKey = Exclude<RouteKey, CustomRouteKey>;
+
+ /**
+ * the first level route key, which contain the layout of the route
+ */
+ export type FirstLevelRouteKey = Extract<
+ RouteKey,
+ | "403"
+ | "404"
+ | "500"
+ | "about"
+ | "analy"
+ | "demo"
+ | "home"
+ | "iframe-page"
+ | "login"
+ | "monitor"
+ | "social-callback"
+ | "system"
+ | "tool"
+ | "user-center"
+ >;
+
+ /**
+ * the custom first level route key
+ */
+ export type CustomFirstLevelRouteKey = Extract<
+ CustomRouteKey,
+ | "root"
+ | "not-found"
+ | "exception"
+ >;
+
+ /**
+ * the last level route key, which has the page file
+ */
+ export type LastLevelRouteKey = Extract<
+ RouteKey,
+ | "403"
+ | "404"
+ | "500"
+ | "iframe-page"
+ | "login"
+ | "social-callback"
+ | "user-center"
+ | "about"
+ | "analy_hoister"
+ | "analy_output-analy"
+ | "analy_packer"
+ | "analy_roller"
+ | "demo_demo"
+ | "demo_tree"
+ | "home"
+ | "monitor_cache"
+ | "monitor_logininfor"
+ | "monitor_online"
+ | "monitor_operlog"
+ | "system_client"
+ | "system_config"
+ | "system_dept"
+ | "system_dict"
+ | "system_menu"
+ | "system_notice"
+ | "system_oss-config"
+ | "system_oss"
+ | "system_post"
+ | "system_role"
+ | "system_tenant-package"
+ | "system_tenant"
+ | "system_user"
+ | "tool_gen"
+ >;
+
+ /**
+ * the custom last level route key
+ */
+ export type CustomLastLevelRouteKey = Extract<
+ CustomRouteKey,
+ | "root"
+ | "not-found"
+ | "exception_403"
+ | "exception_404"
+ | "exception_500"
+ >;
+
+ /**
+ * the single level route key
+ */
+ export type SingleLevelRouteKey = FirstLevelRouteKey & LastLevelRouteKey;
+
+ /**
+ * the custom single level route key
+ */
+ export type CustomSingleLevelRouteKey = CustomFirstLevelRouteKey & CustomLastLevelRouteKey;
+
+ /**
+ * the first level route key, but not the single level
+ */
+ export type FirstLevelRouteNotSingleKey = Exclude<FirstLevelRouteKey, SingleLevelRouteKey>;
+
+ /**
+ * the custom first level route key, but not the single level
+ */
+ export type CustomFirstLevelRouteNotSingleKey = Exclude<CustomFirstLevelRouteKey, CustomSingleLevelRouteKey>;
+
+ /**
+ * the center level route key
+ */
+ export type CenterLevelRouteKey = Exclude<GeneratedRouteKey, FirstLevelRouteKey | LastLevelRouteKey>;
+
+ /**
+ * the custom center level route key
+ */
+ export type CustomCenterLevelRouteKey = Exclude<CustomRouteKey, CustomFirstLevelRouteKey | CustomLastLevelRouteKey>;
+
+ /**
+ * the center level route key
+ */
+ type GetChildRouteKey<K extends RouteKey, T extends RouteKey = RouteKey> = T extends `${K}_${infer R}`
+ ? R extends `${string}_${string}`
+ ? never
+ : T
+ : never;
+
+ /**
+ * the single level route
+ */
+ type SingleLevelRoute<K extends SingleLevelRouteKey = SingleLevelRouteKey> = K extends string
+ ? Omit<ElegantConstRoute, 'children'> & {
+ name: K;
+ path: RouteMap[K];
+ component: `layout.${RouteLayout}$view.${K}`;
+ }
+ : never;
+
+ /**
+ * the last level route
+ */
+ type LastLevelRoute<K extends GeneratedRouteKey> = K extends LastLevelRouteKey
+ ? Omit<ElegantConstRoute, 'children'> & {
+ name: K;
+ path: RouteMap[K];
+ component: `view.${K}`;
+ }
+ : never;
+
+ /**
+ * the center level route
+ */
+ type CenterLevelRoute<K extends GeneratedRouteKey> = K extends CenterLevelRouteKey
+ ? Omit<ElegantConstRoute, 'component'> & {
+ name: K;
+ path: RouteMap[K];
+ children: (CenterLevelRoute<GetChildRouteKey<K>> | LastLevelRoute<GetChildRouteKey<K>>)[];
+ }
+ : never;
+
+ /**
+ * the multi level route
+ */
+ type MultiLevelRoute<K extends FirstLevelRouteNotSingleKey = FirstLevelRouteNotSingleKey> = K extends string
+ ? ElegantConstRoute & {
+ name: K;
+ path: RouteMap[K];
+ component: `layout.${RouteLayout}`;
+ children: (CenterLevelRoute<GetChildRouteKey<K>> | LastLevelRoute<GetChildRouteKey<K>>)[];
+ }
+ : never;
+
+ /**
+ * the custom first level route
+ */
+ type CustomSingleLevelRoute<K extends CustomFirstLevelRouteKey = CustomFirstLevelRouteKey> = K extends string
+ ? Omit<ElegantConstRoute, 'children'> & {
+ name: K;
+ path: RouteMap[K];
+ component?: `layout.${RouteLayout}$view.${LastLevelRouteKey}`;
+ }
+ : never;
+
+ /**
+ * the custom last level route
+ */
+ type CustomLastLevelRoute<K extends CustomRouteKey> = K extends CustomLastLevelRouteKey
+ ? Omit<ElegantConstRoute, 'children'> & {
+ name: K;
+ path: RouteMap[K];
+ component?: `view.${LastLevelRouteKey}`;
+ }
+ : never;
+
+ /**
+ * the custom center level route
+ */
+ type CustomCenterLevelRoute<K extends CustomRouteKey> = K extends CustomCenterLevelRouteKey
+ ? Omit<ElegantConstRoute, 'component'> & {
+ name: K;
+ path: RouteMap[K];
+ children: (CustomCenterLevelRoute<GetChildRouteKey<K>> | CustomLastLevelRoute<GetChildRouteKey<K>>)[];
+ }
+ : never;
+
+ /**
+ * the custom multi level route
+ */
+ type CustomMultiLevelRoute<K extends CustomFirstLevelRouteNotSingleKey = CustomFirstLevelRouteNotSingleKey> =
+ K extends string
+ ? ElegantConstRoute & {
+ name: K;
+ path: RouteMap[K];
+ component: `layout.${RouteLayout}`;
+ children: (CustomCenterLevelRoute<GetChildRouteKey<K>> | CustomLastLevelRoute<GetChildRouteKey<K>>)[];
+ }
+ : never;
+
+ /**
+ * the custom route
+ */
+ type CustomRoute = CustomSingleLevelRoute | CustomMultiLevelRoute;
+
+ /**
+ * the generated route
+ */
+ type GeneratedRoute = SingleLevelRoute | MultiLevelRoute;
+
+ /**
+ * the elegant route
+ */
+ type ElegantRoute = GeneratedRoute | CustomRoute;
+}
diff --git a/ruoyi-plus-soybean/src/typings/global.d.ts b/ruoyi-plus-soybean/src/typings/global.d.ts
new file mode 100755
index 0000000..13f041d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/global.d.ts
@@ -0,0 +1,26 @@
+export {};
+
+declare global {
+ export interface Window {
+ /** NProgress instance */
+ NProgress?: import('nprogress').NProgress;
+ /** Loading bar instance */
+ $loadingBar?: import('naive-ui').LoadingBarProviderInst;
+ /** Dialog instance */
+ $dialog?: import('naive-ui').DialogProviderInst;
+ /** Message instance */
+ $message?: import('naive-ui').MessageProviderInst;
+ /** Notification instance */
+ $notification?: import('naive-ui').NotificationProviderInst;
+ /** Content loading */
+ $loading?: {
+ loading: import('vue').Ref<boolean>;
+ description: import('vue').Ref<string>;
+ startLoading: (description?: string) => void;
+ endLoading: () => void;
+ };
+ }
+
+ /** Build time of the project */
+ export const BUILD_TIME: string;
+}
diff --git a/ruoyi-plus-soybean/src/typings/naive-ui.d.ts b/ruoyi-plus-soybean/src/typings/naive-ui.d.ts
new file mode 100755
index 0000000..30e55c6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/naive-ui.d.ts
@@ -0,0 +1,24 @@
+declare namespace NaiveUI {
+ type ThemeColor = 'default' | 'error' | 'primary' | 'info' | 'success' | 'warning';
+ type Align = 'stretch' | 'baseline' | 'start' | 'end' | 'center' | 'flex-end' | 'flex-start';
+
+ type DataTableBaseColumn<T> = import('naive-ui').DataTableBaseColumn<T>;
+ type DataTableExpandColumn<T> = import('naive-ui').DataTableExpandColumn<T>;
+ type DataTableSelectionColumn<T> = import('naive-ui').DataTableSelectionColumn<T>;
+ type TableColumnGroup<T> = import('naive-ui/es/data-table/src/interface').TableColumnGroup<T>;
+ type TableColumnCheck = import('@sa/hooks').TableColumnCheck;
+
+ type SetTableColumnKey<C, T> = Omit<C, 'key'> & { key: keyof T | (string & {}) };
+
+ type TableColumnWithKey<T> = SetTableColumnKey<DataTableBaseColumn<T>, T> | SetTableColumnKey<TableColumnGroup<T>, T>;
+
+ type TableColumn<T> = TableColumnWithKey<T> | DataTableSelectionColumn<T> | DataTableExpandColumn<T>;
+
+ /**
+ * the type of table operation
+ *
+ * - add: add table item
+ * - edit: edit table item
+ */
+ type TableOperateType = 'add' | 'edit';
+}
diff --git a/ruoyi-plus-soybean/src/typings/router.d.ts b/ruoyi-plus-soybean/src/typings/router.d.ts
new file mode 100755
index 0000000..256e646
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/router.d.ts
@@ -0,0 +1,72 @@
+import 'vue-router';
+
+declare module 'vue-router' {
+ interface RouteMeta {
+ /**
+ * Title of the route
+ *
+ * It can be used in document title
+ */
+ title: string;
+ /**
+ * I18n key of the route
+ *
+ * It's used in i18n, if it is set, the title will be ignored
+ */
+ i18nKey?: App.I18n.I18nKey | null;
+ /**
+ * Roles of the route
+ *
+ * Route can be accessed if the current user has at least one of the roles
+ *
+ * It only works when the route mode is "static", if the route mode is "dynamic", it will be ignored
+ */
+ roles?: string[];
+ /** Whether to cache the route */
+ keepAlive?: boolean | null;
+ /**
+ * Is constant route
+ *
+ * when it is set to true, there will be no login verification and no permission verification to access the route
+ */
+ constant?: boolean | null;
+ /**
+ * Iconify icon
+ *
+ * It can be used in the menu or breadcrumb
+ */
+ icon?: string;
+ /**
+ * Local icon
+ *
+ * In "src/assets/svg-icon", if it is set, the icon will be ignored
+ */
+ localIcon?: string;
+ /** Icon size. width and height are the same. */
+ iconFontSize?: number;
+ /** Router order */
+ order?: number | null;
+ /** The outer link of the route */
+ href?: string | null;
+ /** Whether to hide the route in the menu */
+ hideInMenu?: boolean | null;
+ /**
+ * The menu key will be activated when entering the route
+ *
+ * The route is not in the menu
+ *
+ * @example
+ * the route is "user_detail", if it is set to "user_list", the menu "user_list" will be activated
+ */
+ activeMenu?: import('@elegant-router/types').RouteKey | null;
+ /**
+ * By default, the same route path will use one tab, even with different query, if set true, the route with
+ * different query will use different tabs
+ */
+ multiTab?: boolean | null;
+ /** If set, the route will be fixed in tabs, and the value is the order of fixed tabs */
+ fixedIndexInTab?: number | null;
+ /** if set query parameters, it will be automatically carried when entering the route */
+ query?: { key: string; value: string }[] | null;
+ }
+}
diff --git a/ruoyi-plus-soybean/src/typings/storage.d.ts b/ruoyi-plus-soybean/src/typings/storage.d.ts
new file mode 100755
index 0000000..fe9bbaf
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/storage.d.ts
@@ -0,0 +1,52 @@
+/** The storage namespace */
+declare namespace StorageType {
+ interface Session {
+ /** The theme color */
+ themeColor: string;
+ // /**
+ // * the theme settings
+ // */
+ // themeSettings: App.Theme.ThemeSetting;
+ sessionObj: {
+ url: string;
+ data: any;
+ time: number;
+ };
+ }
+
+ interface Local {
+ /** The i18n language */
+ lang: App.I18n.LangType;
+ /** The token */
+ token: string;
+ /** Fixed sider with mix-menu */
+ mixSiderFixed: CommonType.YesOrNo;
+ /** The refresh token */
+ refreshToken: string;
+ /** The theme color */
+ themeColor: string;
+ /** The dark mode */
+ darkMode: boolean;
+ /** The theme settings */
+ themeSettings: App.Theme.ThemeSetting;
+ /**
+ * The override theme flags
+ *
+ * The value is the build time of the project
+ */
+ overrideThemeFlag: string;
+ /** The global tabs */
+ globalTabs: App.Global.Tab[];
+ /** The backup theme setting before is mobile */
+ backupThemeSettingBeforeIsMobile: {
+ layout: UnionKey.ThemeLayoutMode;
+ siderCollapse: boolean;
+ };
+ /** The last login user id */
+ lastLoginUserId: CommonType.IdType;
+ /** The login form rember */
+ loginRember: string;
+ /** The tenant id */
+ tenantId: CommonType.IdType;
+ }
+}
diff --git a/ruoyi-plus-soybean/src/typings/union-key.d.ts b/ruoyi-plus-soybean/src/typings/union-key.d.ts
new file mode 100755
index 0000000..0cc8411
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/union-key.d.ts
@@ -0,0 +1,165 @@
+/** The union key namespace */
+declare namespace UnionKey {
+ /**
+ * The login module
+ *
+ * - pwd-login: password login
+ * - code-login: phone code login
+ * - register: register
+ * - reset-pwd: reset password
+ * - bind-wechat: bind wechat
+ */
+ type LoginModule = 'pwd-login' | 'code-login' | 'register' | 'reset-pwd' | 'bind-wechat';
+
+ /** Theme scheme */
+ type ThemeScheme = 'light' | 'dark' | 'auto';
+
+ /**
+ * The layout mode
+ *
+ * - vertical: the vertical menu in left
+ * - horizontal: the horizontal menu in top
+ * - vertical-mix: two vertical mixed menus in left
+ * - top-hybrid-sidebar-first: the vertical first level menus in left and horizontal child level menus in top
+ * - top-hybrid-header-first: the horizontal first level menus in top and vertical child level menus in left
+ */
+ type ThemeLayoutMode =
+ | 'vertical'
+ | 'horizontal'
+ | 'vertical-mix'
+ | 'vertical-hybrid-header-first'
+ | 'top-hybrid-sidebar-first'
+ | 'top-hybrid-header-first';
+
+ /**
+ * The scroll mode when content overflow
+ *
+ * - wrapper: the wrapper component's root element overflow
+ * - content: the content component overflow
+ */
+ type ThemeScrollMode = import('@sa/materials').LayoutScrollMode;
+
+ /** Page animate mode */
+ type ThemePageAnimateMode = 'fade' | 'fade-slide' | 'fade-bottom' | 'fade-scale' | 'zoom-fade' | 'zoom-out' | 'none';
+
+ /**
+ * Tab mode
+ *
+ * - chrome: chrome style
+ * - button: button style
+ */
+ type ThemeTabMode = import('@sa/materials').PageTabMode;
+
+ /**
+ * The table size
+ *
+ * - small: small size
+ * - medium: medium size
+ * - large: large size
+ */
+ type ThemeTableSize = 'small' | 'medium' | 'large';
+
+ /** Unocss animate key */
+ type UnoCssAnimateKey =
+ | 'pulse'
+ | 'bounce'
+ | 'spin'
+ | 'ping'
+ | 'bounce-alt'
+ | 'flash'
+ | 'pulse-alt'
+ | 'rubber-band'
+ | 'shake-x'
+ | 'shake-y'
+ | 'head-shake'
+ | 'swing'
+ | 'tada'
+ | 'wobble'
+ | 'jello'
+ | 'heart-beat'
+ | 'hinge'
+ | 'jack-in-the-box'
+ | 'light-speed-in-left'
+ | 'light-speed-in-right'
+ | 'light-speed-out-left'
+ | 'light-speed-out-right'
+ | 'flip'
+ | 'flip-in-x'
+ | 'flip-in-y'
+ | 'flip-out-x'
+ | 'flip-out-y'
+ | 'rotate-in'
+ | 'rotate-in-down-left'
+ | 'rotate-in-down-right'
+ | 'rotate-in-up-left'
+ | 'rotate-in-up-right'
+ | 'rotate-out'
+ | 'rotate-out-down-left'
+ | 'rotate-out-down-right'
+ | 'rotate-out-up-left'
+ | 'rotate-out-up-right'
+ | 'roll-in'
+ | 'roll-out'
+ | 'zoom-in'
+ | 'zoom-in-down'
+ | 'zoom-in-left'
+ | 'zoom-in-right'
+ | 'zoom-in-up'
+ | 'zoom-out'
+ | 'zoom-out-down'
+ | 'zoom-out-left'
+ | 'zoom-out-right'
+ | 'zoom-out-up'
+ | 'bounce-in'
+ | 'bounce-in-down'
+ | 'bounce-in-left'
+ | 'bounce-in-right'
+ | 'bounce-in-up'
+ | 'bounce-out'
+ | 'bounce-out-down'
+ | 'bounce-out-left'
+ | 'bounce-out-right'
+ | 'bounce-out-up'
+ | 'slide-in-down'
+ | 'slide-in-left'
+ | 'slide-in-right'
+ | 'slide-in-up'
+ | 'slide-out-down'
+ | 'slide-out-left'
+ | 'slide-out-right'
+ | 'slide-out-up'
+ | 'fade-in'
+ | 'fade-in-down'
+ | 'fade-in-down-big'
+ | 'fade-in-left'
+ | 'fade-in-left-big'
+ | 'fade-in-right'
+ | 'fade-in-right-big'
+ | 'fade-in-up'
+ | 'fade-in-up-big'
+ | 'fade-in-top-left'
+ | 'fade-in-top-right'
+ | 'fade-in-bottom-left'
+ | 'fade-in-bottom-right'
+ | 'fade-out'
+ | 'fade-out-down'
+ | 'fade-out-down-big'
+ | 'fade-out-left'
+ | 'fade-out-left-big'
+ | 'fade-out-right'
+ | 'fade-out-right-big'
+ | 'fade-out-up'
+ | 'fade-out-up-big'
+ | 'fade-out-top-left'
+ | 'fade-out-top-right'
+ | 'fade-out-bottom-left'
+ | 'fade-out-bottom-right'
+ | 'back-in-up'
+ | 'back-in-down'
+ | 'back-in-right'
+ | 'back-in-left'
+ | 'back-out-up'
+ | 'back-out-down'
+ | 'back-out-right'
+ | 'back-out-left';
+}
diff --git a/ruoyi-plus-soybean/src/typings/vite-env.d.ts b/ruoyi-plus-soybean/src/typings/vite-env.d.ts
new file mode 100755
index 0000000..ea6cc67
--- /dev/null
+++ b/ruoyi-plus-soybean/src/typings/vite-env.d.ts
@@ -0,0 +1,128 @@
+/**
+ * Namespace Env
+ *
+ * It is used to declare the type of the import.meta object
+ */
+declare namespace Env {
+ /** The router history mode */
+ type RouterHistoryMode = 'hash' | 'history' | 'memory';
+
+ /** Interface for import.meta */
+ // eslint-disable-next-line @typescript-eslint/no-shadow
+ interface ImportMeta extends ImportMetaEnv {
+ /** The base url of the application */
+ readonly VITE_BASE_URL: string;
+ /** The title of the application */
+ readonly VITE_APP_TITLE: string;
+ /** The description of the application */
+ readonly VITE_APP_DESC: string;
+ /** The router history mode */
+ readonly VITE_ROUTER_HISTORY_MODE?: RouterHistoryMode;
+ /** The prefix of the iconify icon */
+ readonly VITE_ICON_PREFIX: 'icon';
+ /**
+ * The prefix of the local icon
+ *
+ * This prefix is start with the icon prefix
+ */
+ readonly VITE_ICON_LOCAL_PREFIX: 'icon-local';
+ /** backend service base url */
+ readonly VITE_SERVICE_BASE_URL: string;
+ /**
+ * success code of backend service
+ *
+ * when the code is received, the request is successful
+ */
+ readonly VITE_SERVICE_SUCCESS_CODE: string;
+ /**
+ * logout codes of backend service
+ *
+ * when the code is received, the user will be logged out and redirected to login page
+ *
+ * use "," to separate multiple codes
+ */
+ readonly VITE_SERVICE_LOGOUT_CODES: string;
+ /**
+ * modal logout codes of backend service
+ *
+ * when the code is received, the user will be logged out by displaying a modal
+ *
+ * use "," to separate multiple codes
+ */
+ readonly VITE_SERVICE_MODAL_LOGOUT_CODES: string;
+ /**
+ * token expired codes of backend service
+ *
+ * when the code is received, it will refresh the token and resend the request
+ *
+ * use "," to separate multiple codes
+ */
+ readonly VITE_SERVICE_EXPIRED_TOKEN_CODES: string;
+ /** when the route mode is static, the defined super role */
+ readonly VITE_STATIC_SUPER_ROLE: string;
+ /**
+ * other backend service base url
+ *
+ * the value is a json
+ */
+ readonly VITE_OTHER_SERVICE_BASE_URL: string;
+ /**
+ * Whether to enable the http proxy
+ *
+ * Only valid in the development environment
+ */
+ readonly VITE_HTTP_PROXY?: CommonType.YesOrNo;
+ /**
+ * The auth route mode
+ *
+ * - Static: the auth routes is generated in front-end
+ * - Dynamic: the auth routes is generated in back-end
+ */
+ readonly VITE_AUTH_ROUTE_MODE: 'static' | 'dynamic';
+ /**
+ * The home route key
+ *
+ * It only has effect when the auth route mode is static, if the route mode is dynamic, the home route key is
+ * defined in the back-end
+ */
+ readonly VITE_ROUTE_HOME: import('@elegant-router/types').LastLevelRouteKey;
+ /**
+ * Default menu icon if menu icon is not set
+ *
+ * Iconify icon name
+ */
+ readonly VITE_MENU_ICON: string;
+ /** Whether to build with sourcemap */
+ readonly VITE_SOURCE_MAP?: CommonType.YesOrNo;
+ /**
+ * Iconify api provider url
+ *
+ * If the project is deployed in intranet, you can set the api provider url to the local iconify server
+ *
+ * @link https://docs.iconify.design/api/providers.html
+ */
+ readonly VITE_ICONIFY_URL?: string;
+ /** Used to differentiate storage across different domains */
+ readonly VITE_STORAGE_PREFIX?: string;
+ /** Whether to automatically detect updates after configuring application packaging */
+ readonly VITE_AUTOMATICALLY_DETECT_UPDATE?: CommonType.YesOrNo;
+ /** Whether to show watermark */
+ readonly VITE_WATERMARK?: CommonType.YesOrNo;
+ /** show proxy url log in terminal */
+ readonly VITE_PROXY_LOG?: CommonType.YesOrNo;
+ /** The launch editor */
+ readonly VITE_DEVTOOLS_LAUNCH_EDITOR?: import('vite-plugin-vue-devtools').VitePluginVueDevToolsOptions['launchEditor'];
+ readonly VITE_APP_CLIENT_ID?: string;
+ readonly VITE_REMEMBER_ME_AES_KEY?: string;
+ readonly VITE_APP_ENCRYPT?: CommonType.YesOrNo;
+ readonly VITE_HEADER_FLAG?: string;
+ readonly VITE_APP_RSA_PUBLIC_KEY?: string;
+ readonly VITE_APP_RSA_PRIVATE_KEY?: string;
+ readonly VITE_APP_WEBSOCKET: CommonType.YesOrNo;
+ readonly VITE_APP_SSE: CommonType.YesOrNo;
+ }
+}
+
+interface ImportMeta {
+ readonly env: Env.ImportMeta;
+}
diff --git a/ruoyi-plus-soybean/src/utils/agent.ts b/ruoyi-plus-soybean/src/utils/agent.ts
new file mode 100755
index 0000000..a8416b2
--- /dev/null
+++ b/ruoyi-plus-soybean/src/utils/agent.ts
@@ -0,0 +1,7 @@
+export function isPC() {
+ const agents = ['Android', 'iPhone', 'webOS', 'BlackBerry', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod'];
+
+ const isMobile = agents.some(agent => window.navigator.userAgent.includes(agent));
+
+ return !isMobile;
+}
diff --git a/ruoyi-plus-soybean/src/utils/common.ts b/ruoyi-plus-soybean/src/utils/common.ts
new file mode 100755
index 0000000..3dd4923
--- /dev/null
+++ b/ruoyi-plus-soybean/src/utils/common.ts
@@ -0,0 +1,210 @@
+import { AcceptType } from '@/enum/business';
+import { $t } from '@/locales';
+/**
+ * Transform record to option
+ *
+ * @example
+ * ```ts
+ * const record = {
+ * key1: 'label1',
+ * key2: 'label2'
+ * };
+ * const options = transformRecordToOption(record);
+ * // [
+ * // { value: 'key1', label: 'label1' },
+ * // { value: 'key2', label: 'label2' }
+ * // ]
+ * ```;
+ *
+ * @param record
+ */
+export function transformRecordToOption<T extends Record<string, string>>(record: T) {
+ return Object.entries(record).map(([value, label]) => ({
+ value,
+ label
+ })) as CommonType.Option<keyof T, T[keyof T]>[];
+}
+
+export function transformRecordToNumberOption<T extends Record<string, string>>(record: T) {
+ return Object.entries(record).map(([value, label]) => ({
+ value,
+ label
+ })) as CommonType.Option<keyof T>[];
+}
+
+/**
+ * Translate options
+ *
+ * @param options
+ */
+export function translateOptions(options: CommonType.Option<string, App.I18n.I18nKey>[]) {
+ return options.map(option => ({
+ ...option,
+ label: $t(option.label)
+ }));
+}
+
+/**
+ * Toggle html class
+ *
+ * @param className
+ */
+export function toggleHtmlClass(className: string) {
+ function add() {
+ document.documentElement.classList.add(className);
+ }
+
+ function remove() {
+ document.documentElement.classList.remove(className);
+ }
+
+ return {
+ add,
+ remove
+ };
+}
+
+/* 椹煎嘲杞崲涓嬪垝绾� */
+export function humpToLine(str: string, line: string = '-') {
+ let temp = str.replace(/[A-Z]/g, match => {
+ return `${line}${match.toLowerCase()}`;
+ });
+ // 濡傛灉棣栧瓧姣嶆槸澶у啓锛屾墽琛宺eplace鏃朵細澶氫竴涓猒锛岃繖閲岄渶瑕佸幓鎺�
+ if (temp.slice(0, 1) === line) {
+ temp = temp.slice(1);
+ }
+ return temp;
+}
+
+/** 鍒ゆ柇鏄惁涓虹┖ */
+export function isNotNull(value: any) {
+ return value !== undefined && value !== null && value !== '';
+}
+
+/** 鍒ゆ柇鏄惁涓虹┖ */
+export function isNull(value: any) {
+ return value === undefined || value === null || value === '';
+}
+
+/** 鍒ゆ柇鏄惁涓哄浘鐗囩被鍨� */
+export function isImage(suffix: string) {
+ return AcceptType.Image.split(',').includes(suffix.toLowerCase());
+}
+
+/**
+ * 鏋勯�犳爲鍨嬬粨鏋勬暟鎹�
+ *
+ * @param {T[]} data 鏁版嵁婧�
+ * @param {TreeConfig} config 閰嶇疆閫夐」
+ * @returns {T[]} 鏍戝舰缁撴瀯鏁版嵁
+ */
+export const handleTree = <T>(data: T[], config: CommonType.TreeConfig<T> = {}): { tree: T[]; flatData: T[] } => {
+ if (!data?.length) {
+ return {
+ tree: [],
+ flatData: []
+ };
+ }
+
+ const {
+ idField = 'id',
+ parentIdField = 'parentId',
+ childrenField = 'children',
+ // 娣诲姞杩囨护鍑芥暟锛岄粯璁や负涓嶈繃婊�
+ filterFn = () => true
+ } = config;
+
+ // filter flat data
+ const flatData = data.filter(filterFn) || [];
+
+ // 浣跨敤 Map 鏇夸唬鏅�氬璞★紝鎻愰珮鎬ц兘
+ const childrenMap = new Map<T[keyof T], T[]>();
+ const nodeMap = new Map<T[keyof T], T>();
+ const tree: T[] = [];
+
+ // 绗竴閬嶉亶鍘嗭細鏋勫缓鑺傜偣鏄犲皠
+ for (const item of flatData) {
+ const id = item[idField as keyof T];
+ const parentId = item[parentIdField as keyof T];
+
+ nodeMap.set(id, item);
+
+ if (!childrenMap.has(parentId)) {
+ childrenMap.set(parentId, []);
+ }
+ childrenMap.get(parentId)!.push(item);
+ }
+
+ // 绗簩閬嶉亶鍘嗭細鎵惧嚭鏍硅妭鐐�
+ for (const item of flatData) {
+ const parentId = item[parentIdField as keyof T];
+ if (!nodeMap.has(parentId)) {
+ tree.push(item);
+ }
+ }
+
+ // 閫掑綊鏋勫缓鏍戝舰缁撴瀯
+ const buildTree = (node: T) => {
+ const id = node[idField as keyof T];
+ const children = childrenMap.get(id);
+
+ if (children?.length) {
+ // 浣跨敤绫诲瀷鏂█纭繚绫诲瀷瀹夊叏
+ (node as any)[childrenField] = children;
+ for (const child of children) {
+ buildTree(child);
+ }
+ } else {
+ // 濡傛灉娌℃湁瀛愯妭鐐癸紝璁剧疆涓� undefined
+ (node as any)[childrenField] = undefined;
+ }
+ };
+
+ // 浠庢牴鑺傜偣寮�濮嬫瀯寤烘爲
+ for (const root of tree) {
+ buildTree(root);
+ }
+
+ return {
+ tree: tree || [],
+ flatData
+ };
+};
+
+/**
+ * 灏嗗璞¤浆鎹负 URLSearchParams
+ *
+ * @param obj
+ */
+export function transformToURLSearchParams(obj: Record<string, any>, excludeKeys: string[] = []) {
+ const searchParams = new URLSearchParams();
+ if (!isNotNull(obj)) {
+ return searchParams;
+ }
+ Object.entries(obj).forEach(([key, value]) => {
+ if (excludeKeys.includes(key)) {
+ return;
+ }
+ if (typeof value === 'object') {
+ transformToURLSearchParams(value).forEach((v, k) => {
+ searchParams.append(`${key}[${k}]`, v);
+ });
+ return;
+ }
+ if (!isNotNull(value)) {
+ return;
+ }
+ searchParams.append(key, value);
+ });
+ return searchParams;
+}
+
+/** 鍒ゆ柇涓や釜鏁扮粍鏄惁鐩哥瓑 */
+export function arraysEqualSet(arr1: Array<any>, arr2: Array<any>) {
+ return (
+ arr1.length === arr2.length &&
+ new Set(arr1).size === arr1.length &&
+ new Set(arr2).size === arr2.length &&
+ [...arr1].sort().join() === [...arr2].sort().join()
+ );
+}
diff --git a/ruoyi-plus-soybean/src/utils/copy.ts b/ruoyi-plus-soybean/src/utils/copy.ts
new file mode 100755
index 0000000..757aecb
--- /dev/null
+++ b/ruoyi-plus-soybean/src/utils/copy.ts
@@ -0,0 +1,26 @@
+import { useClipboard } from '@vueuse/core';
+
+const { copy, isSupported } = useClipboard();
+
+export async function handleCopy(source?: string) {
+ if (!isSupported) {
+ window.$message?.error('鎮ㄧ殑娴忚鍣ㄤ笉鏀寔 Clipboard API');
+ return;
+ }
+
+ if (!source) {
+ return;
+ }
+
+ if (navigator.clipboard && window.isSecureContext) {
+ await copy(source);
+ } else {
+ const range = document.createRange();
+ range.selectNode(document.getElementById('tokenDetailInput')!);
+ const selection = window.getSelection();
+ if (selection?.rangeCount) selection.removeAllRanges();
+ selection?.addRange(range);
+ document.execCommand('copy');
+ }
+ window.$message?.success('澶嶅埗鎴愬姛');
+}
diff --git a/ruoyi-plus-soybean/src/utils/crypto.ts b/ruoyi-plus-soybean/src/utils/crypto.ts
new file mode 100755
index 0000000..5c6534f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/utils/crypto.ts
@@ -0,0 +1,70 @@
+import CryptoJS from 'crypto-js';
+
+/**
+ * 闅忔満鐢熸垚32浣嶇殑瀛楃涓�
+ *
+ * @returns {string}
+ */
+const generateRandomString = () => {
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ let result = '';
+ const charactersLength = characters.length;
+ // eslint-disable-next-line no-plusplus
+ for (let i = 0; i < 32; i++) {
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
+ }
+ return result;
+};
+
+/**
+ * 闅忔満鐢熸垚aes 瀵嗛挜
+ *
+ * @returns {string}
+ */
+export const generateAesKey = () => {
+ return CryptoJS.enc.Utf8.parse(generateRandomString());
+};
+
+/**
+ * 鍔犲瘑base64
+ *
+ * @returns {string}
+ */
+export const encryptBase64 = (str: CryptoJS.lib.WordArray) => {
+ return CryptoJS.enc.Base64.stringify(str);
+};
+
+/** 瑙e瘑base64 */
+export const decryptBase64 = (str: string) => {
+ return CryptoJS.enc.Base64.parse(str);
+};
+
+/**
+ * 浣跨敤瀵嗛挜瀵规暟鎹繘琛屽姞瀵�
+ *
+ * @param message
+ * @param aesKey
+ * @returns {string}
+ */
+export const encryptWithAes = (message: string, aesKey: CryptoJS.lib.WordArray) => {
+ const encrypted = CryptoJS.AES.encrypt(message, aesKey, {
+ mode: CryptoJS.mode.ECB,
+ padding: CryptoJS.pad.Pkcs7
+ });
+ return encrypted.toString();
+};
+
+/**
+ * 浣跨敤瀵嗛挜瀵规暟鎹繘琛岃В瀵�
+ *
+ * @param message
+ * @param aesKey
+ * @returns {string}
+ */
+export const decryptWithAes = (message: string, aesKey: CryptoJS.lib.WordArray) => {
+ const decrypted = CryptoJS.AES.decrypt(message, aesKey, {
+ mode: CryptoJS.mode.ECB,
+ padding: CryptoJS.pad.Pkcs7
+ });
+ return decrypted.toString(CryptoJS.enc.Utf8);
+};
diff --git a/ruoyi-plus-soybean/src/utils/icon-tag-format.ts b/ruoyi-plus-soybean/src/utils/icon-tag-format.ts
new file mode 100755
index 0000000..5dd27e0
--- /dev/null
+++ b/ruoyi-plus-soybean/src/utils/icon-tag-format.ts
@@ -0,0 +1,62 @@
+/**
+ * 鑾峰彇璇锋眰鏂规硶鏍囩绫诲瀷
+ *
+ * @param method 璇锋眰鏂规硶
+ * @returns 鏍囩绫诲瀷
+ */
+export function getRequestMethodTagType(method: string): NaiveUI.ThemeColor {
+ const methodUpper = method.toUpperCase();
+ const colors: { [key: string]: NaiveUI.ThemeColor } = {
+ DELETE: 'error',
+ GET: 'success',
+ POST: 'primary',
+ PUT: 'warning'
+ };
+
+ return colors[methodUpper] ?? 'default';
+}
+
+const browserOptions = [
+ { icon: 'logos:chrome', value: 'chrome' },
+ { icon: 'logos:microsoft-edge', value: 'edge' },
+ { icon: 'logos:firefox', value: 'firefox' },
+ { icon: 'logos:opera', value: 'opera' },
+ { icon: 'logos:safari', value: 'safari' },
+ { icon: 'ic:baseline-wechat', value: 'micromessenger' },
+ { icon: 'ic:baseline-wechat', value: 'windowswechat' },
+ { icon: 'arcticons:quark-browser', value: 'quark' },
+ { icon: 'ic:baseline-wechat', value: 'wxwork' },
+ { icon: 'simple-icons:tencentqq', value: 'qq' },
+ { icon: 'arcticons:dingtalk', value: 'dingtalk' },
+ { icon: 'arcticons:uc-browser', value: 'uc' },
+ { icon: 'ri:baidu-fill', value: 'baidu' }
+];
+
+const osOptions = [
+ { icon: 'devicon:windows8', value: 'windows' },
+ { icon: 'cbi:imac', value: 'osx' },
+ { icon: 'devicon:linux', value: 'linux' },
+ { icon: 'logos:android-icon', value: 'android' },
+ { icon: 'file-icons:apple', value: 'ios' }
+];
+/**
+ * 鑾峰彇娴忚鍣ㄥ浘鏍�
+ *
+ * @param browser 娴忚鍣�
+ * @returns 娴忚鍣ㄥ浘鏍�
+ */
+export function getBrowserIcon(browser: string): string {
+ const icon = browserOptions.find(item => browser.toLocaleLowerCase().includes(item.value));
+ return icon?.icon ?? 'stash:browser-light';
+}
+
+/**
+ * 鑾峰彇鎿嶄綔绯荤粺鍥炬爣
+ *
+ * @param os 鎿嶄綔绯荤粺
+ * @returns 鎿嶄綔绯荤粺鍥炬爣
+ */
+export function getOsIcon(os: string): string {
+ const icon = osOptions.find(item => os.toLocaleLowerCase().includes(item.value));
+ return icon?.icon || 'mingcute:device-fill';
+}
diff --git a/ruoyi-plus-soybean/src/utils/icon.ts b/ruoyi-plus-soybean/src/utils/icon.ts
new file mode 100755
index 0000000..b8c2120
--- /dev/null
+++ b/ruoyi-plus-soybean/src/utils/icon.ts
@@ -0,0 +1,19 @@
+export function getLocalIcons() {
+ const svgIcons = import.meta.glob('/src/assets/svg-icon/*.svg');
+
+ const keys = Object.keys(svgIcons)
+ .map(item => item.split('/').at(-1)?.replace('.svg', '') || '')
+ .filter(Boolean);
+
+ return keys;
+}
+
+export function getLocalMenuIcons() {
+ const svgIcons = import.meta.glob('/src/assets/svg-icon/menu/*.svg');
+
+ const keys = Object.keys(svgIcons)
+ .map(item => item.split('/').at(-1)?.replace('.svg', '') || '')
+ .filter(Boolean);
+
+ return keys;
+}
diff --git a/ruoyi-plus-soybean/src/utils/jsencrypt.ts b/ruoyi-plus-soybean/src/utils/jsencrypt.ts
new file mode 100755
index 0000000..da8c3e5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/utils/jsencrypt.ts
@@ -0,0 +1,21 @@
+import JSEncrypt from 'jsencrypt';
+// 瀵嗛挜瀵圭敓鎴� http://web.chacuo.net/netrsakeypair
+
+const publicKey = import.meta.env.VITE_APP_RSA_PUBLIC_KEY;
+
+// 鍓嶇涓嶅缓璁瓨鏀剧閽� 涓嶅缓璁В瀵嗘暟鎹� 鍥犱负閮芥槸閫忔槑鐨勬剰涔変笉澶�
+const privateKey = import.meta.env.VITE_APP_RSA_PRIVATE_KEY;
+
+// 鍔犲瘑
+export const encrypt = (txt: string) => {
+ const encryptor = new JSEncrypt();
+ encryptor.setPublicKey(publicKey!); // 璁剧疆鍏挜
+ return encryptor.encrypt(txt); // 瀵规暟鎹繘琛屽姞瀵�
+};
+
+// 瑙e瘑
+export const decrypt = (txt: string) => {
+ const encryptor = new JSEncrypt();
+ encryptor.setPrivateKey(privateKey!); // 璁剧疆绉侀挜
+ return encryptor.decrypt(txt); // 瀵规暟鎹繘琛岃В瀵�
+};
diff --git a/ruoyi-plus-soybean/src/utils/service.ts b/ruoyi-plus-soybean/src/utils/service.ts
new file mode 100755
index 0000000..9007be7
--- /dev/null
+++ b/ruoyi-plus-soybean/src/utils/service.ts
@@ -0,0 +1,75 @@
+import json5 from 'json5';
+
+/**
+ * Create service config by current env
+ *
+ * @param env The current env
+ */
+export function createServiceConfig(env: Env.ImportMeta) {
+ const { VITE_SERVICE_BASE_URL, VITE_OTHER_SERVICE_BASE_URL, VITE_APP_BASE_API, VITE_APP_WEBSOCKET } = env;
+
+ let other = {} as Record<App.Service.OtherBaseURLKey, string>;
+ try {
+ if (VITE_OTHER_SERVICE_BASE_URL) {
+ other = json5.parse(VITE_OTHER_SERVICE_BASE_URL);
+ }
+ } catch {
+ // eslint-disable-next-line no-console
+ console.error('VITE_OTHER_SERVICE_BASE_URL is not a valid json5 string');
+ }
+
+ const httpConfig: App.Service.SimpleServiceConfig = {
+ baseURL: VITE_SERVICE_BASE_URL,
+ other
+ };
+
+ const otherHttpKeys = Object.keys(httpConfig.other) as App.Service.OtherBaseURLKey[];
+
+ const otherConfig: App.Service.OtherServiceConfigItem[] = otherHttpKeys.map(key => {
+ return {
+ key,
+ ws: false,
+ baseURL: httpConfig.other[key],
+ proxyPattern: createProxyPattern(key)
+ };
+ });
+
+ const config: App.Service.ServiceConfig = {
+ baseURL: httpConfig.baseURL,
+ ws: VITE_APP_WEBSOCKET === 'Y',
+ proxyPattern: VITE_APP_BASE_API,
+ other: otherConfig
+ };
+
+ return config;
+}
+
+/**
+ * get backend service base url
+ *
+ * @param env - the current env
+ * @param isProxy - if use proxy
+ */
+export function getServiceBaseURL(env: Env.ImportMeta, isProxy: boolean = env.DEV && env.VITE_HTTP_PROXY === 'Y') {
+ const { baseURL, other, proxyPattern } = createServiceConfig(env);
+
+ const otherBaseURL = {} as Record<App.Service.OtherBaseURLKey, string>;
+
+ other.forEach(item => {
+ otherBaseURL[item.key] = isProxy ? item.proxyPattern : item.baseURL;
+ });
+
+ return {
+ baseURL: isProxy ? proxyPattern : (baseURL || '') + proxyPattern,
+ otherBaseURL
+ };
+}
+
+/**
+ * Get proxy pattern of backend service base url
+ *
+ * @param key If not set, will use the default key
+ */
+function createProxyPattern(key: App.Service.OtherBaseURLKey) {
+ return `/proxy-${key}`;
+}
diff --git a/ruoyi-plus-soybean/src/utils/sse.ts b/ruoyi-plus-soybean/src/utils/sse.ts
new file mode 100755
index 0000000..8627550
--- /dev/null
+++ b/ruoyi-plus-soybean/src/utils/sse.ts
@@ -0,0 +1,58 @@
+import { watch } from 'vue';
+import { useEventSource } from '@vueuse/core';
+import useNoticeStore from '@/store/modules/notice';
+import { $t } from '@/locales';
+import { localStg } from './storage';
+
+/**
+ * 鍒濆鍖� SSE
+ *
+ * @param url - SSE 鍦板潃
+ */
+export const initSSE = (url: string) => {
+ const token = localStg.get('token');
+ if (import.meta.env.VITE_APP_SSE === 'N' || !token) {
+ return;
+ }
+ const sseUrl = `${url}?Authorization=Bearer ${token}&clientid=${import.meta.env.VITE_APP_CLIENT_ID}`;
+ const { data, error } = useEventSource(sseUrl, [], {
+ autoReconnect: {
+ retries: 5,
+ delay: 5000,
+ onFailed() {
+ // eslint-disable-next-line no-console
+ console.warn('Failed to connect to SSE after 5 attempts.');
+ }
+ }
+ });
+
+ watch(error, () => {
+ if (!error.value || error.value?.isTrusted) {
+ return;
+ }
+ // eslint-disable-next-line no-console
+ console.error('SSE connection error:\n', error.value);
+ error.value = null;
+ });
+
+ watch(data, () => {
+ if (!data.value) return;
+ useNoticeStore().addNotice({
+ message: data.value,
+ read: false,
+ time: new Date().toLocaleString()
+ });
+ let content = data.value;
+ const noticeType = content.match(/\[dict\.(.*?)\]/)?.[1];
+ if (noticeType) {
+ content = content.replace(`dict.${noticeType}`, $t(`dict.${noticeType}` as App.I18n.I18nKey));
+ }
+ window.$notification?.create({
+ title: '娑堟伅',
+ content,
+ type: 'success',
+ duration: 3000
+ });
+ data.value = null;
+ });
+};
diff --git a/ruoyi-plus-soybean/src/utils/storage.ts b/ruoyi-plus-soybean/src/utils/storage.ts
new file mode 100755
index 0000000..2ab44c2
--- /dev/null
+++ b/ruoyi-plus-soybean/src/utils/storage.ts
@@ -0,0 +1,9 @@
+import { createLocalforage, createStorage } from '@sa/utils';
+
+const storagePrefix = import.meta.env.VITE_STORAGE_PREFIX || '';
+
+export const localStg = createStorage<StorageType.Local>('local', storagePrefix);
+
+export const sessionStg = createStorage<StorageType.Session>('session', storagePrefix);
+
+export const localforage = createLocalforage<StorageType.Local>('local');
diff --git a/ruoyi-plus-soybean/src/utils/websocket.ts b/ruoyi-plus-soybean/src/utils/websocket.ts
new file mode 100755
index 0000000..c1b1ed6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/utils/websocket.ts
@@ -0,0 +1,60 @@
+import { useWebSocket } from '@vueuse/core';
+import useNoticeStore from '@/store/modules/notice';
+import { localStg } from './storage';
+
+/**
+ * 鍒濆鍖� WebSocket
+ *
+ * @param url - WebSocket 鍦板潃
+ */
+export const initWebSocket = (url: string) => {
+ const token = localStg.get('token');
+ if (import.meta.env.VITE_APP_WEBSOCKET === 'N' || !token) {
+ return;
+ }
+ const socketUrl = `${url}?Authorization=Bearer ${token}&clientid=${import.meta.env.VITE_APP_CLIENT_ID}`;
+ useWebSocket(socketUrl, {
+ autoReconnect: {
+ // 閲嶈繛鏈�澶ф鏁�
+ retries: 3,
+ // 閲嶈繛闂撮殧
+ delay: 1000,
+ onFailed() {
+ // eslint-disable-next-line no-console
+ console.warn('WebSocket 閲嶈繛澶辫触');
+ }
+ },
+ heartbeat: {
+ message: JSON.stringify({ type: 'ping' }),
+ // 鍙戦�佸績璺崇殑闂撮殧
+ interval: 10000,
+ // 鎺ユ敹鍒板績璺硆esponse鐨勮秴鏃舵椂闂�
+ pongTimeout: 2000
+ },
+ onConnected() {
+ // eslint-disable-next-line no-console
+ console.log('WebSocket 宸茬粡杩炴帴');
+ },
+ onDisconnected() {
+ // eslint-disable-next-line no-console
+ console.warn('WebSocket 宸茬粡鏂紑杩炴帴');
+ },
+ onMessage: (_, e) => {
+ if (e.data.indexOf('ping') > 0) {
+ return;
+ }
+ useNoticeStore().addNotice({
+ message: e.data,
+ read: false,
+ time: new Date().toLocaleString()
+ });
+
+ window.$notification?.create({
+ title: '娑堟伅',
+ content: e.data,
+ type: 'success',
+ duration: 3000
+ });
+ }
+ });
+};
diff --git a/ruoyi-plus-soybean/src/views/_builtin/403/index.vue b/ruoyi-plus-soybean/src/views/_builtin/403/index.vue
new file mode 100755
index 0000000..3c59ddf
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/403/index.vue
@@ -0,0 +1,7 @@
+<script setup lang="ts"></script>
+
+<template>
+ <ExceptionBase type="403" />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/_builtin/404/index.vue b/ruoyi-plus-soybean/src/views/_builtin/404/index.vue
new file mode 100755
index 0000000..4b4bf5b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/404/index.vue
@@ -0,0 +1,7 @@
+<script setup lang="ts"></script>
+
+<template>
+ <ExceptionBase type="404" />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/_builtin/500/index.vue b/ruoyi-plus-soybean/src/views/_builtin/500/index.vue
new file mode 100755
index 0000000..0028cf2
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/500/index.vue
@@ -0,0 +1,7 @@
+<script setup lang="ts"></script>
+
+<template>
+ <ExceptionBase type="500" />
+</template>
+
+<style scoped></style>
diff --git "a/ruoyi-plus-soybean/src/views/_builtin/iframe-page/\133url\135.vue" "b/ruoyi-plus-soybean/src/views/_builtin/iframe-page/\133url\135.vue"
new file mode 100755
index 0000000..33c4d51
--- /dev/null
+++ "b/ruoyi-plus-soybean/src/views/_builtin/iframe-page/\133url\135.vue"
@@ -0,0 +1,15 @@
+<script setup lang="ts">
+interface Props {
+ url: string;
+}
+
+defineProps<Props>();
+</script>
+
+<template>
+ <div class="h-full">
+ <iframe id="iframePage" class="size-full" :src="url"></iframe>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/_builtin/login/index.vue b/ruoyi-plus-soybean/src/views/_builtin/login/index.vue
new file mode 100755
index 0000000..e14d24c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/login/index.vue
@@ -0,0 +1,107 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import type { Component } from 'vue';
+import { loginModuleRecord } from '@/constants/app';
+import { useAppStore } from '@/store/modules/app';
+import { useThemeStore } from '@/store/modules/theme';
+import loginBackground from '@/assets/svg-icon/login-background.svg';
+import { $t } from '@/locales';
+import PwdLogin from './modules/pwd-login.vue';
+import CodeLogin from './modules/code-login.vue';
+import Register from './modules/register.vue';
+import ResetPwd from './modules/reset-pwd.vue';
+import BindWechat from './modules/bind-wechat.vue';
+
+interface Props {
+ /** The login module */
+ module?: UnionKey.LoginModule;
+}
+
+const props = defineProps<Props>();
+
+const appStore = useAppStore();
+const themeStore = useThemeStore();
+
+interface LoginModule {
+ label: App.I18n.I18nKey;
+ component: Component;
+}
+
+const moduleMap: Record<UnionKey.LoginModule, LoginModule> = {
+ 'pwd-login': { label: loginModuleRecord['pwd-login'], component: PwdLogin },
+ 'code-login': { label: loginModuleRecord['code-login'], component: CodeLogin },
+ register: { label: loginModuleRecord.register, component: Register },
+ 'reset-pwd': { label: loginModuleRecord['reset-pwd'], component: ResetPwd },
+ 'bind-wechat': { label: loginModuleRecord['bind-wechat'], component: BindWechat }
+};
+
+const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
+</script>
+
+<template>
+ <div class="scroll box-border size-full flex">
+ <div class="relative box-border hidden h-full w-65vw overflow-hidden bg-primary-50 xl:block dark:bg-primary-900">
+ <div class="relative z-100 flex items-center pl-30px pt-30px">
+ <SystemLogo class="fill-primary text-32px" />
+ <h3 class="ml-10px text-20px font-400">{{ $t('system.title') }}</h3>
+ </div>
+ <div class="absolute inset-x-0 inset-b-10.5% inset-t-0 z-10 m-auto w-40%">
+ <img class="size-full" :src="loginBackground" />
+ </div>
+ <div class="absolute bottom-80px w-full text-center">
+ <h1 class="text-24px font-400">{{ $t('page.login.common.title') }}</h1>
+ <p class="mt-8px text-14px color-gray-500">{{ $t('page.login.common.subTitle') }}</p>
+ </div>
+ <WaveBg />
+ </div>
+ <div class="relative h-full flex-1 xl:m-auto sm:!w-full">
+ <header class="flex-y-center justify-between px-30px pt-30px xl:justify-end">
+ <div class="relative z-100 flex items-center xl:hidden">
+ <SystemLogo class="fill-primary text-32px" />
+ <h3 class="ml-10px text-20px font-400">{{ $t('system.title') }}</h3>
+ </div>
+ <div class="flex items-center justify-end">
+ <ThemeSchemaSwitch
+ :theme-schema="themeStore.themeScheme"
+ :show-tooltip="false"
+ class="text-20px lt-sm:text-18px"
+ @switch="themeStore.toggleThemeScheme"
+ />
+ <LangSwitch
+ v-if="themeStore.header.multilingual.visible"
+ :lang="appStore.locale"
+ :lang-options="appStore.localeOptions"
+ :show-tooltip="false"
+ class="text-20px lt-sm:text-18px"
+ @change-lang="appStore.changeLocale"
+ />
+ </div>
+ </header>
+ <main
+ class="m-auto mt-10% h-630px max-w-450px w-full rounded-5px bg-cover px-24px xl:absolute xl:inset-0 lg:mt-15% xl:mt-auto"
+ >
+ <Transition :name="themeStore.page.animateMode" mode="out-in" appear>
+ <component :is="activeModule.component" />
+ </Transition>
+ </main>
+ </div>
+ </div>
+</template>
+
+<style scoped>
+.scroll {
+ overflow: auto;
+}
+
+.scroll::-webkit-scrollbar {
+ display: none;
+}
+
+.scroll {
+ -ms-overflow-style: none;
+}
+
+.scroll {
+ scrollbar-width: none;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/_builtin/login/modules/bind-wechat.vue b/ruoyi-plus-soybean/src/views/_builtin/login/modules/bind-wechat.vue
new file mode 100755
index 0000000..cb417bf
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/login/modules/bind-wechat.vue
@@ -0,0 +1,11 @@
+<script setup lang="ts">
+defineOptions({
+ name: 'BindWechat'
+});
+</script>
+
+<template>
+ <div></div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/_builtin/login/modules/code-login.vue b/ruoyi-plus-soybean/src/views/_builtin/login/modules/code-login.vue
new file mode 100755
index 0000000..4ab5fc4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/login/modules/code-login.vue
@@ -0,0 +1,87 @@
+<script setup lang="ts">
+import { computed, reactive } from 'vue';
+import { useRouterPush } from '@/hooks/common/router';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { useCaptcha } from '@/hooks/business/captcha';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'CodeLogin'
+});
+
+const { toggleLoginModule } = useRouterPush();
+const { formRef, validate } = useNaiveForm();
+const { label, isCounting, loading, getCaptcha } = useCaptcha();
+
+interface FormModel {
+ phone: string;
+ code: string;
+}
+
+const model: FormModel = reactive({
+ phone: '',
+ code: ''
+});
+
+const rules = computed<Record<keyof FormModel, App.Global.FormRule[]>>(() => {
+ const { formRules } = useFormRules();
+
+ return {
+ phone: formRules.phone,
+ code: formRules.code
+ };
+});
+
+async function handleSubmit() {
+ await validate();
+ // request
+ window.$message?.success($t('page.login.common.validateSuccess'));
+}
+</script>
+
+<template>
+ <div class="mb-5px text-32px text-black font-600 sm:text-30px dark:text-white">
+ {{ $t('page.login.codeLogin.title') }}
+ </div>
+ <div class="pb-18px text-16px text-#858585">璇疯緭鍏ユ偍鐨勬墜鏈哄彿锛屾垜浠皢鍙戦�侀獙璇佺爜鍒版偍鐨勬墜鏈�</div>
+ <NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
+ <NFormItem path="phone">
+ <NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
+ </NFormItem>
+ <NFormItem path="code">
+ <div class="w-full flex-y-center gap-16px">
+ <NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
+ <NButton size="large" :disabled="isCounting" :loading="loading" @click="getCaptcha(model.phone)">
+ {{ label }}
+ </NButton>
+ </div>
+ </NFormItem>
+ <NSpace vertical :size="20" class="w-full">
+ <NButton type="primary" size="large" block @click="handleSubmit">
+ {{ $t('page.login.codeLogin.title') }}
+ </NButton>
+ <NButton size="large" block @click="toggleLoginModule('pwd-login')">
+ {{ $t('page.login.common.back') }}
+ </NButton>
+ </NSpace>
+ </NForm>
+</template>
+
+<style scoped>
+:deep(.n-base-selection),
+:deep(.n-input) {
+ --n-height: 42px !important;
+ --n-font-size: 16px !important;
+ --n-border-radius: 8px !important;
+}
+
+:deep(.n-base-selection-label) {
+ padding: 0 6px !important;
+}
+
+:deep(.n-button) {
+ --n-height: 42px !important;
+ --n-font-size: 18px !important;
+ --n-border-radius: 8px !important;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/_builtin/login/modules/pwd-login.vue b/ruoyi-plus-soybean/src/views/_builtin/login/modules/pwd-login.vue
new file mode 100755
index 0000000..0a60c64
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/login/modules/pwd-login.vue
@@ -0,0 +1,258 @@
+<script setup lang="ts">
+import { computed, reactive, ref } from 'vue';
+import type { SelectOption } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import CryptoJS from 'crypto-js';
+import { fetchCaptchaCode, fetchTenantList } from '@/service/api';
+import { fetchSocialAuthBinding } from '@/service/api/system';
+import { useAuthStore } from '@/store/modules/auth';
+import { useRouterPush } from '@/hooks/common/router';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { localStg } from '@/utils/storage';
+import { decryptWithAes, encryptWithAes } from '@/utils/crypto';
+import { $t } from '@/locales';
+
+const aesKey = CryptoJS.enc.Utf8.parse(import.meta.env.VITE_REMEMBER_ME_AES_KEY || 'pC4aO6cD2uU7hA0bK6iD4vE1mV8sU8xG');
+
+defineOptions({
+ name: 'PwdLogin'
+});
+
+const authStore = useAuthStore();
+const { toggleLoginModule } = useRouterPush();
+const { formRef, validate } = useNaiveForm();
+const { loading: codeLoading, startLoading: startCodeLoading, endLoading: endCodeLoading } = useLoading();
+const { loading: tenantLoading, startLoading: startTenantLoading, endLoading: endTenantLoading } = useLoading();
+
+const codeUrl = ref<string>();
+const captchaEnabled = ref<boolean>(false);
+const registerEnabled = ref<boolean>(false);
+const remberMe = ref<boolean>(false);
+
+const tenantEnabled = ref<boolean>(false);
+
+const tenantOption = ref<SelectOption[]>([]);
+
+const model: Api.Auth.PwdLoginForm = reactive({
+ tenantId: '000000',
+ username: 'admin',
+ password: 'admin123'
+});
+
+type RuleKey = Extract<keyof Api.Auth.PwdLoginForm, 'username' | 'password' | 'code' | 'tenantId'>;
+
+const rules = computed<Record<RuleKey, App.Global.FormRule[]>>(() => {
+ // inside computed to make locale reactive, if not apply i18n, you can define it without computed
+ const { formRules, createRequiredRule } = useFormRules();
+
+ const loginRules: Record<RuleKey, App.Global.FormRule[]> = {
+ username: [...formRules.userName, { required: true }],
+ password: [createRequiredRule($t('form.pwd.required'))],
+ code: captchaEnabled.value ? [createRequiredRule($t('form.code.required'))] : [],
+ tenantId: tenantEnabled.value ? formRules.tenantId : []
+ };
+
+ return loginRules;
+});
+
+async function handleFetchTenantList() {
+ startTenantLoading();
+ const { data, error } = await fetchTenantList();
+ if (error) return;
+ tenantEnabled.value = data.tenantEnabled;
+ if (data.tenantEnabled) {
+ tenantOption.value = data.voList.map(tenant => {
+ return {
+ label: tenant.companyName,
+ value: tenant.tenantId
+ };
+ });
+ }
+ endTenantLoading();
+}
+
+handleFetchTenantList();
+
+async function handleSubmit() {
+ await validate();
+ // 鍕鹃�変簡闇�瑕佽浣忓瘑鐮佽缃湪 localStorage 涓缃浣忕敤鎴峰悕鍜屽瘑鐮�
+ if (remberMe.value) {
+ const { tenantId, username, password } = model;
+ localStg.set('loginRember', encryptWithAes(JSON.stringify({ tenantId, username, password }), aesKey));
+ } else {
+ // 鍚﹀垯绉婚櫎
+ localStg.remove('loginRember');
+ }
+ try {
+ await authStore.login(model);
+ } catch {
+ handleFetchCaptchaCode();
+ }
+}
+
+async function handleFetchCaptchaCode() {
+ startCodeLoading();
+ const { data, error } = await fetchCaptchaCode();
+ if (!error) {
+ captchaEnabled.value = data.captchaEnabled;
+ if (data.captchaEnabled) {
+ model.uuid = data.uuid;
+ codeUrl.value = `data:image/gif;base64,${data.img}`;
+ }
+ }
+ endCodeLoading();
+}
+
+handleFetchCaptchaCode();
+
+function handleLoginRember() {
+ const loginRember = localStg.get('loginRember');
+ if (!loginRember) return;
+ try {
+ remberMe.value = true;
+ Object.assign(model, JSON.parse(decryptWithAes(loginRember, aesKey)));
+ } catch {}
+}
+
+handleLoginRember();
+
+// async function handleRegister() {
+// const { data, error } = await fetchGetConfigDetail('sys.account.registerUser');
+// if (error) return;
+// registerEnabled.value = data.configValue === 'true';
+// }
+
+// handleRegister();
+
+async function handleSocialLogin(type: Api.System.SocialSource) {
+ const { data, error } = await fetchSocialAuthBinding(type, model.tenantId);
+ if (error) return;
+ window.location.href = data;
+}
+</script>
+
+<template>
+ <div>
+ <div class="mb-5px text-32px text-black font-600 dark:text-white">鐧诲綍鍒版偍鐨勮处鎴�</div>
+ <div class="pb-18px text-16px text-#858585">娆㈣繋鍥炴潵锛佽杈撳叆鎮ㄧ殑璐︽埛淇℃伅</div>
+ <NForm
+ ref="formRef"
+ :model="model"
+ :rules="rules"
+ size="large"
+ :show-label="false"
+ @keyup.enter="() => !authStore.loginLoading && handleSubmit()"
+ >
+ <NFormItem v-if="tenantEnabled" path="tenantId">
+ <NSelect
+ v-model:value="model.tenantId"
+ placeholder="璇烽�夋嫨绉熸埛"
+ :options="tenantOption"
+ :loading="tenantLoading"
+ />
+ </NFormItem>
+ <NFormItem path="username">
+ <NInput v-model:value="model.username" :placeholder="$t('page.login.common.userNamePlaceholder')" />
+ </NFormItem>
+ <NFormItem path="password">
+ <NInput
+ v-model:value="model.password"
+ type="password"
+ show-password-on="click"
+ :placeholder="$t('page.login.common.passwordPlaceholder')"
+ />
+ </NFormItem>
+ <NFormItem v-if="captchaEnabled" path="code">
+ <div class="w-full flex-y-center gap-16px">
+ <NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
+ <NSpin :show="codeLoading" :size="28" class="h-42px">
+ <NButton :focusable="false" class="login-code h-42px w-136px" @click="handleFetchCaptchaCode">
+ <img v-if="codeUrl" :src="codeUrl" />
+ <NEmpty v-else :show-icon="false" description="鏆傛棤楠岃瘉鐮�" />
+ </NButton>
+ </NSpin>
+ </div>
+ </NFormItem>
+ <NSpace vertical :size="12" class="mb-8px">
+ <div class="mx-6px mb-8px flex-y-center justify-between">
+ <NCheckbox v-model:checked="remberMe" size="large">{{ $t('page.login.pwdLogin.rememberMe') }}</NCheckbox>
+ <NA type="primary" class="text-18px" @click="toggleLoginModule('reset-pwd')">
+ {{ $t('page.login.pwdLogin.forgetPassword') }}
+ </NA>
+ </div>
+ <NButton type="primary" size="large" block :loading="authStore.loginLoading" @click="handleSubmit">
+ {{ $t('common.login') }}
+ </NButton>
+ <NButton v-if="registerEnabled" size="large" block @click="toggleLoginModule('register')">
+ {{ $t('page.login.common.register') }}
+ </NButton>
+ </NSpace>
+ </NForm>
+
+ <NDivider>
+ <div class="color-#858585">{{ $t('page.login.pwdLogin.otherAccountLogin') }}</div>
+ </NDivider>
+
+ <div class="w-full flex-y-center gap-16px">
+ <NButton class="flex-1" @click="handleSocialLogin('gitee')">
+ <template #icon>
+ <icon-simple-icons-gitee class="color-#c71d23" />
+ </template>
+ <span class="ml-6px">Gitee</span>
+ </NButton>
+ <NButton class="flex-1" @click="handleSocialLogin('github')">
+ <template #icon>
+ <icon-mdi-github class="color-#010409" />
+ </template>
+ <span class="ml-6px">GitHub</span>
+ </NButton>
+ </div>
+
+ <div class="mt-24px w-full text-center text-18px text-#858585">
+ 鎮ㄨ繕娌℃湁璐︽埛锛�
+ <NA type="primary" class="text-18px" @click="toggleLoginModule('register')">
+ {{ $t('page.login.common.register') }}
+ </NA>
+ </div>
+ </div>
+</template>
+
+<style scoped>
+.login-code {
+ &.n-button {
+ --n-padding: 0 8px !important;
+ background-color: #c0c0c0;
+ }
+
+ img {
+ height: 42px;
+ }
+}
+
+:deep(.n-base-selection),
+:deep(.n-input) {
+ --n-height: 42px !important;
+ --n-font-size: 16px !important;
+ --n-border-radius: 8px !important;
+}
+
+:deep(.n-base-selection-label) {
+ padding: 0 6px !important;
+}
+
+:deep(.n-checkbox) {
+ --n-size: 18px !important;
+ --n-font-size: 16px !important;
+}
+
+:deep(.n-button) {
+ --n-height: 42px !important;
+ --n-font-size: 18px !important;
+ --n-border-radius: 8px !important;
+}
+
+:deep(.n-divider) {
+ --n-font-size: 16px !important;
+ --n-font-weight: 400 !important;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/_builtin/login/modules/register.vue b/ruoyi-plus-soybean/src/views/_builtin/login/modules/register.vue
new file mode 100755
index 0000000..c82f90c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/login/modules/register.vue
@@ -0,0 +1,194 @@
+<script setup lang="ts">
+import { computed, reactive, ref } from 'vue';
+import type { SelectOption } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { fetchCaptchaCode, fetchRegister, fetchTenantList } from '@/service/api';
+import { useRouterPush } from '@/hooks/common/router';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'Register'
+});
+
+const { toggleLoginModule } = useRouterPush();
+const { formRef, validate } = useNaiveForm();
+const { loading: codeLoading, startLoading: startCodeLoading, endLoading: endCodeLoading } = useLoading();
+const { loading: registerLoading, startLoading: startRegisterLoading, endLoading: endRegisterLoading } = useLoading();
+
+const codeUrl = ref<string>();
+const captchaEnabled = ref<boolean>(false);
+const tenantEnabled = ref<boolean>(false);
+const tenantOption = ref<SelectOption[]>([]);
+
+const model: Api.Auth.RegisterForm = reactive({
+ tenantId: '000000',
+ username: '',
+ code: '',
+ password: '',
+ confirmPassword: '',
+ userType: 'sys_user'
+});
+
+type RuleKey = Extract<keyof Api.Auth.RegisterForm, 'username' | 'password' | 'confirmPassword' | 'code' | 'tenantId'>;
+
+const rules = computed<Record<RuleKey, App.Global.FormRule[]>>(() => {
+ const { formRules, createConfirmPwdRule, createRequiredRule } = useFormRules();
+
+ return {
+ tenantId: tenantEnabled.value ? formRules.tenantId : [],
+ username: [...formRules.userName, { required: true }],
+ password: [...formRules.pwd, { required: true }],
+ confirmPassword: createConfirmPwdRule(model.password!),
+ code: captchaEnabled.value ? [createRequiredRule($t('form.code.required'))] : []
+ };
+});
+
+async function handleSubmit() {
+ try {
+ await validate();
+ startRegisterLoading();
+ const { error } = await fetchRegister({
+ tenantId: model.tenantId,
+ username: model.username,
+ password: model.password,
+ code: model.code,
+ uuid: model.uuid,
+ grantType: 'password',
+ userType: model.userType,
+ clientId: import.meta.env.VITE_APP_CLIENT_ID
+ });
+ if (error) {
+ handleFetchCaptchaCode();
+ return;
+ }
+ window.$message?.success('娉ㄥ唽鎴愬姛');
+ // 娉ㄥ唽鎴愬姛鍚庤烦杞埌鐧诲綍椤�
+ toggleLoginModule('pwd-login');
+ } catch {
+ handleFetchCaptchaCode();
+ } finally {
+ endRegisterLoading();
+ }
+}
+
+async function handleFetchTenantList() {
+ const { data, error } = await fetchTenantList();
+ if (error) return;
+ tenantEnabled.value = data.tenantEnabled;
+ tenantOption.value = data.voList.map(tenant => {
+ return {
+ label: tenant.companyName,
+ value: tenant.tenantId
+ };
+ });
+}
+
+handleFetchTenantList();
+
+async function handleFetchCaptchaCode() {
+ startCodeLoading();
+ const { data, error } = await fetchCaptchaCode();
+ if (!error) {
+ captchaEnabled.value = data.captchaEnabled;
+ if (data.captchaEnabled) {
+ model.uuid = data.uuid;
+ codeUrl.value = `data:image/gif;base64,${data.img}`;
+ }
+ }
+ endCodeLoading();
+}
+
+handleFetchCaptchaCode();
+</script>
+
+<template>
+ <div>
+ <div class="mb-5px text-32px text-black font-600 sm:text-30px dark:text-white">娉ㄥ唽鏂拌处鎴�</div>
+ <div class="pb-18px text-16px text-#858585">娆㈣繋娉ㄥ唽锛佽杈撳叆鎮ㄧ殑璐︽埛淇℃伅</div>
+ <NForm
+ ref="formRef"
+ :model="model"
+ :rules="rules"
+ size="large"
+ :show-label="false"
+ @keyup.enter="() => !registerLoading && handleSubmit()"
+ >
+ <NFormItem v-if="tenantEnabled" path="tenantId">
+ <NSelect v-model:value="model.tenantId" :options="tenantOption" :enabled="tenantEnabled" />
+ </NFormItem>
+ <NFormItem path="username">
+ <NInput v-model:value="model.username" :placeholder="$t('page.login.common.userNamePlaceholder')" />
+ </NFormItem>
+ <NFormItem path="password">
+ <NInput
+ v-model:value="model.password"
+ type="password"
+ show-password-on="click"
+ :placeholder="$t('page.login.common.passwordPlaceholder')"
+ />
+ </NFormItem>
+ <NFormItem path="confirmPassword">
+ <NInput
+ v-model:value="model.confirmPassword"
+ type="password"
+ show-password-on="click"
+ :placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
+ />
+ </NFormItem>
+ <NFormItem v-if="captchaEnabled" path="code">
+ <div class="w-full flex-y-center gap-16px">
+ <NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
+ <NSpin :show="codeLoading" :size="28" class="h-52px">
+ <NButton :focusable="false" class="login-code h-52px w-136px" @click="handleFetchCaptchaCode">
+ <img v-if="codeUrl" :src="codeUrl" />
+ <NEmpty v-else :show-icon="false" description="鏆傛棤楠岃瘉鐮�" />
+ </NButton>
+ </NSpin>
+ </div>
+ </NFormItem>
+ <NSpace vertical :size="18" class="w-full">
+ <NButton type="primary" size="large" block :loading="registerLoading" @click="handleSubmit">
+ {{ $t('page.login.common.register') }}
+ </NButton>
+ </NSpace>
+ </NForm>
+
+ <div class="mt-24px w-full text-center text-18px text-#858585">
+ 鎮ㄥ凡鏈夎处鎴凤紵
+ <NA type="primary" class="text-18px" @click="toggleLoginModule('pwd-login')">
+ {{ $t('common.login') }}
+ </NA>
+ </div>
+ </div>
+</template>
+
+<style scoped lang="scss">
+.login-code {
+ &.n-button {
+ --n-padding: 0 8px !important;
+ background-color: #c0c0c0;
+ }
+
+ img {
+ height: 40px;
+ }
+}
+
+:deep(.n-base-selection),
+:deep(.n-input) {
+ --n-height: 42px !important;
+ --n-font-size: 16px !important;
+ --n-border-radius: 8px !important;
+}
+
+:deep(.n-base-selection-label) {
+ padding: 0 6px !important;
+}
+
+:deep(.n-button) {
+ --n-height: 42px !important;
+ --n-font-size: 18px !important;
+ --n-border-radius: 8px !important;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/_builtin/login/modules/reset-pwd.vue b/ruoyi-plus-soybean/src/views/_builtin/login/modules/reset-pwd.vue
new file mode 100755
index 0000000..8c8151f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/login/modules/reset-pwd.vue
@@ -0,0 +1,105 @@
+<script setup lang="ts">
+import { computed, reactive } from 'vue';
+import { useRouterPush } from '@/hooks/common/router';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'ResetPwd'
+});
+
+const { toggleLoginModule } = useRouterPush();
+const { formRef, validate } = useNaiveForm();
+
+interface FormModel {
+ phone: string;
+ code: string;
+ password: string;
+ confirmPassword: string;
+}
+
+const model: FormModel = reactive({
+ phone: '',
+ code: '',
+ password: '',
+ confirmPassword: ''
+});
+
+type RuleRecord = Partial<Record<keyof FormModel, App.Global.FormRule[]>>;
+
+const rules = computed<RuleRecord>(() => {
+ const { formRules, createConfirmPwdRule } = useFormRules();
+
+ return {
+ phone: formRules.phone,
+ password: formRules.pwd,
+ confirmPassword: createConfirmPwdRule(model.password)
+ };
+});
+
+async function handleSubmit() {
+ await validate();
+ // request to reset password
+ window.$message?.success($t('page.login.common.validateSuccess'));
+}
+</script>
+
+<template>
+ <div>
+ <div class="mb-5px text-32px text-black font-600 sm:text-30px dark:text-white">
+ {{ $t('page.login.resetPwd.title') }}
+ </div>
+ <div class="pb-18px text-16px text-#858585">璇疯緭鍏ユ偍鐨勬墜鏈哄彿锛屾垜浠皢鍙戦�侀獙璇佺爜鍒版偍鐨勬墜鏈�</div>
+ <NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
+ <NFormItem path="phone">
+ <NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
+ </NFormItem>
+ <NFormItem path="code">
+ <NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
+ </NFormItem>
+ <NFormItem path="password">
+ <NInput
+ v-model:value="model.password"
+ type="password"
+ show-password-on="click"
+ :placeholder="$t('page.login.common.passwordPlaceholder')"
+ />
+ </NFormItem>
+ <NFormItem path="confirmPassword">
+ <NInput
+ v-model:value="model.confirmPassword"
+ type="password"
+ show-password-on="click"
+ :placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
+ />
+ </NFormItem>
+ <NSpace vertical :size="20" class="w-full">
+ <NButton type="primary" size="large" block @click="handleSubmit">
+ {{ $t('page.login.resetPwd.title') }}
+ </NButton>
+ <NButton size="large" block @click="toggleLoginModule('pwd-login')">
+ {{ $t('page.login.common.back') }}
+ </NButton>
+ </NSpace>
+ </NForm>
+ </div>
+</template>
+
+<style scoped>
+:deep(.n-base-selection),
+:deep(.n-input) {
+ --n-height: 42px !important;
+ --n-font-size: 16px !important;
+ --n-border-radius: 8px !important;
+}
+
+:deep(.n-base-selection-label) {
+ padding: 0 6px !important;
+}
+
+:deep(.n-button) {
+ --n-height: 42px !important;
+ --n-font-size: 18px !important;
+ --n-border-radius: 8px !important;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/_builtin/social-callback/index.vue b/ruoyi-plus-soybean/src/views/_builtin/social-callback/index.vue
new file mode 100755
index 0000000..69c0191
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/social-callback/index.vue
@@ -0,0 +1,125 @@
+<script setup lang="ts">
+import { onMounted, ref } from 'vue';
+import { useRoute } from 'vue-router';
+import { getRgb } from '@sa/color';
+import { DARK_CLASS } from '@/constants/app';
+import { fetchSocialLoginCallback } from '@/service/api';
+import { useAuthStore } from '@/store/modules/auth';
+import { useRouterPush } from '@/hooks/common/router';
+import { localStg } from '@/utils/storage';
+import { toggleHtmlClass } from '@/utils/common';
+
+const route = useRoute();
+const authStore = useAuthStore();
+const { routerPushByKey } = useRouterPush();
+
+/**
+ * 鎺ユ敹Route浼犻�掔殑鍙傛暟
+ *
+ * @param {Object} route.query.
+ */
+const code = route.query.code as string;
+const state = route.query.state as string;
+const source = route.query.source as string;
+const stateJson = state ? JSON.parse(atob(state)) : {};
+const tenantId = (stateJson.tenantId as string) ?? '000000';
+const domain = (stateJson.domain as string) ?? window.location.host;
+const msg = ref('姝e湪鐧诲綍锛岃绋嶅悗......');
+
+const processResponse = async () => {
+ window.$message?.success('鐧诲綍鎴愬姛');
+ msg.value = '鐧诲綍鎴愬姛锛�2s 鍚庡嵆灏嗚烦杞嚦棣栭〉';
+ setTimeout(() => {
+ msg.value = '鐧诲綍鎴愬姛锛�1s 鍚庡嵆灏嗚烦杞嚦棣栭〉';
+ }, 1000);
+ setTimeout(() => {
+ routerPushByKey(import.meta.env.VITE_ROUTE_HOME || 'home');
+ }, 1000);
+};
+
+const handleError = () => {
+ msg.value = '鐧诲綍澶辫触锛�2s 鍚庡嵆灏嗚烦杞嚦鐧诲綍椤�';
+ setTimeout(() => {
+ msg.value = '鐧诲綍澶辫触锛�1s 鍚庡嵆灏嗚烦杞嚦鐧诲綍椤�';
+ }, 1000);
+ setTimeout(() => {
+ routerPushByKey('login');
+ }, 1000);
+};
+
+const callbackByCode = async (data: Api.Auth.SocialLoginForm) => {
+ const { error } = await fetchSocialLoginCallback({
+ ...data,
+ clientId: import.meta.env.VITE_APP_CLIENT_ID,
+ grantType: 'social'
+ });
+ if (error) {
+ handleError();
+ return;
+ }
+ await processResponse();
+};
+
+const loginByCode = async (data: Api.Auth.SocialLoginForm) => {
+ try {
+ await authStore.logout();
+ await authStore.login(data);
+ await processResponse();
+ } catch {
+ handleError();
+ }
+};
+
+const init = async () => {
+ // 濡傛灉鍩熷悕涓嶇浉绛� 鍒欓噸瀹氬悜澶勭悊
+ const host = window.location.host;
+ if (domain !== host) {
+ const urlFull = new URL(window.location.href);
+ urlFull.host = domain;
+ window.location.href = urlFull.toString();
+ return;
+ }
+
+ const data: Api.Auth.SocialLoginForm = {
+ socialCode: code,
+ socialState: state,
+ tenantId,
+ source,
+ grantType: 'social'
+ };
+
+ if (!authStore.isLogin) {
+ await loginByCode(data);
+ } else {
+ await callbackByCode(data);
+ }
+};
+
+onMounted(async () => {
+ await init();
+});
+
+const themeColor = localStg.get('themeColor') || '#2080f0';
+const darkMode = localStg.get('darkMode') || false;
+const { r, g, b } = getRgb(themeColor);
+
+if (darkMode) {
+ toggleHtmlClass(DARK_CLASS).add();
+}
+
+const primaryColor = `--primary-color: ${r} ${g} ${b}`;
+</script>
+
+<template>
+ <div class="fixed-center flex-col bg-layout" :style="primaryColor">
+ <div class="my-52px h-120px w-120px">
+ <!-- From Uiverse.io by SchawnnahJ -->
+ <div class="loader"></div>
+ </div>
+ <h2 class="text-30px text-primary-400 font-500">{{ msg }}</h2>
+ </div>
+</template>
+
+<style lang="scss" scoped>
+@use '@/styles/scss/loading.scss';
+</style>
diff --git a/ruoyi-plus-soybean/src/views/_builtin/user-center/index.vue b/ruoyi-plus-soybean/src/views/_builtin/user-center/index.vue
new file mode 100755
index 0000000..f759562
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/user-center/index.vue
@@ -0,0 +1,244 @@
+<script setup lang="ts">
+import { reactive } from 'vue';
+import { NButton } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { fetchUpdateUserPassword, fetchUpdateUserProfile } from '@/service/api/system';
+import { useAuthStore } from '@/store/modules/auth';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import OnlineTable from './modules/online-table.vue';
+import SocialCard from './modules/social-card.vue';
+import UserAvatar from './modules/user-avatar.vue';
+
+defineOptions({
+ name: 'UserCenter'
+});
+
+const authStore = useAuthStore();
+const { userInfo } = authStore;
+
+const { loading: btnLoading, startLoading: startBtnLoading, endLoading: endBtnLoading } = useLoading();
+
+const {
+ formRef: profileFormRef,
+ validate: profileValidate,
+ restoreValidation: profileRestoreValidation
+} = useNaiveForm();
+const {
+ formRef: passwordFormRef,
+ validate: passwordValidate,
+ restoreValidation: passwordRestoreValidation
+} = useNaiveForm();
+const { createRequiredRule, patternRules } = useFormRules();
+
+type ProfileModel = Api.System.UserProfileOperateParams;
+type PasswordModel = Api.System.UserPasswordOperateParams & { confirmPassword: string };
+
+const profileModel: ProfileModel = reactive(createDefaultProfileModel());
+const passwordModel: PasswordModel = reactive(createDefaultPasswordModel());
+
+function createDefaultProfileModel(): ProfileModel {
+ return {
+ nickName: userInfo.user?.nickName || '',
+ email: userInfo.user?.email || '',
+ phonenumber: userInfo.user?.phonenumber || '',
+ sex: userInfo.user?.sex || '0'
+ };
+}
+
+function createDefaultPasswordModel(): PasswordModel {
+ return {
+ oldPassword: '',
+ confirmPassword: '',
+ newPassword: ''
+ };
+}
+
+type ProfileRuleKey = Extract<keyof ProfileModel, 'nickName' | 'email' | 'phonenumber' | 'sex'>;
+type PasswordRuleKey = Extract<keyof PasswordModel, 'oldPassword' | 'newPassword' | 'confirmPassword'>;
+
+const profileRules: Record<ProfileRuleKey, App.Global.FormRule> = {
+ nickName: createRequiredRule('鏄电О涓嶈兘涓虹┖'),
+ email: { ...patternRules.email, required: true },
+ phonenumber: { ...patternRules.phone, required: true },
+ sex: createRequiredRule('鎬у埆涓嶈兘涓虹┖')
+};
+
+const passwordRules: Record<PasswordRuleKey, App.Global.FormRule> = {
+ oldPassword: createRequiredRule('鏃у瘑鐮佷笉鑳戒负绌�'),
+ confirmPassword: createRequiredRule('纭瀵嗙爜涓嶈兘涓虹┖'),
+ newPassword: createRequiredRule('鏂板瘑鐮佷笉鑳戒负绌�')
+};
+
+async function updateProfile() {
+ await profileValidate();
+ startBtnLoading();
+ const { error } = await fetchUpdateUserProfile(profileModel);
+ if (!error) {
+ window.$message?.success('鏇存柊鎴愬姛');
+ // 鏇存柊鏈湴鐢ㄦ埛淇℃伅
+ if (userInfo.user) {
+ Object.assign(userInfo.user, profileModel);
+ profileRestoreValidation();
+ }
+ }
+ endBtnLoading();
+}
+
+async function updatePassword() {
+ await passwordValidate();
+ if (passwordModel.newPassword !== passwordModel.confirmPassword) {
+ window.$message?.error('涓ゆ杈撳叆鐨勫瘑鐮佷笉涓�鑷�');
+ return;
+ }
+ startBtnLoading();
+ const { oldPassword, newPassword } = passwordModel;
+ const { error } = await fetchUpdateUserPassword({ oldPassword, newPassword });
+ if (!error) {
+ window.$message?.success('瀵嗙爜淇敼鎴愬姛');
+ // 娓呯┖琛ㄥ崟
+ Object.assign(passwordModel, createDefaultPasswordModel());
+ passwordRestoreValidation();
+ }
+ endBtnLoading();
+}
+</script>
+
+<template>
+ <div class="flex gap-16px">
+ <!-- 涓汉淇℃伅鍗$墖 -->
+ <NCard title="涓汉淇℃伅" class="w-360px shadow-sm">
+ <div class="flex-x-center flex-wrap gap-24px">
+ <div class="flex-center flex-col gap-16px">
+ <div class="relative">
+ <UserAvatar />
+ </div>
+ <div class="text-18px font-medium">{{ userInfo.user?.nickName }}</div>
+ <div class="text-14px text-gray-500">{{ userInfo.user?.userName }}</div>
+ </div>
+ <NDescriptions :column="1" label-placement="left" label-width="120px">
+ <NDescriptionsItem label="鎵嬫満鍙风爜">
+ <div class="text-14px">{{ userInfo.user?.phonenumber }}</div>
+ </NDescriptionsItem>
+ <NDescriptionsItem label="鐢ㄦ埛閭">
+ <div class="text-14px">{{ userInfo.user?.email }}</div>
+ </NDescriptionsItem>
+ <NDescriptionsItem label="鎵�灞為儴闂�">
+ <div class="text-14px">{{ userInfo.user?.deptName }}</div>
+ </NDescriptionsItem>
+ <NDescriptionsItem label="鎵�灞炶鑹�">
+ <NSpace>
+ <NTag v-for="role in userInfo.user?.roles" :key="role.roleId" type="primary" size="small">
+ {{ role.roleName }}
+ </NTag>
+ </NSpace>
+ </NDescriptionsItem>
+ <NDescriptionsItem label="鍒涘缓鏃ユ湡">
+ <div class="text-14px">{{ userInfo.user?.createTime }}</div>
+ </NDescriptionsItem>
+ </NDescriptions>
+ </div>
+ </NCard>
+
+ <!-- 鍩烘湰璧勬枡鍗$墖 -->
+ <NCard title="鍩烘湰璧勬枡" class="w-full overflow-x-auto shadow-sm">
+ <NTabs type="line" animated class="h-full" s>
+ <NTabPane name="userInfo" tab="鍩烘湰璧勬枡">
+ <NForm
+ ref="profileFormRef"
+ :model="profileModel"
+ :rules="profileRules"
+ label-placement="left"
+ label-width="100px"
+ class="mt-16px max-w-520px"
+ >
+ <NFormItem label="鏄电О" path="nickName">
+ <NInput v-model:value="profileModel.nickName" placeholder="璇疯緭鍏ユ樀绉�" />
+ </NFormItem>
+ <NFormItem label="閭" path="email">
+ <NInput v-model:value="profileModel.email" placeholder="璇疯緭鍏ラ偖绠�" />
+ </NFormItem>
+ <NFormItem label="鎵嬫満鍙�" path="phonenumber">
+ <NInput v-model:value="profileModel.phonenumber" placeholder="璇疯緭鍏ユ墜鏈哄彿" />
+ </NFormItem>
+ <NFormItem label="鎬у埆" path="sex">
+ <NRadioGroup v-model:value="profileModel.sex">
+ <NRadio value="0">鐢�</NRadio>
+ <NRadio value="1">濂�</NRadio>
+ </NRadioGroup>
+ </NFormItem>
+ <NFormItem class="flex items-center justify-end">
+ <NButton class="ml-20px w-80px" type="primary" :loading="btnLoading" @click="updateProfile">
+ <template #icon>
+ <SvgIcon icon="ic:outline-save" class="size-24px" />
+ </template>
+ 淇濆瓨
+ </NButton>
+ </NFormItem>
+ </NForm>
+ </NTabPane>
+ <NTabPane name="updatePwd" tab="淇敼瀵嗙爜">
+ <NForm
+ ref="passwordFormRef"
+ :model="passwordModel"
+ :rules="passwordRules"
+ label-placement="left"
+ label-width="100px"
+ class="mt-16px max-w-520px"
+ >
+ <NFormItem label="鏃у瘑鐮�" path="oldPassword">
+ <NInput
+ v-model:value="passwordModel.oldPassword"
+ type="password"
+ placeholder="璇疯緭鍏ユ棫瀵嗙爜"
+ show-password-on="click"
+ />
+ </NFormItem>
+ <NFormItem label="鏂板瘑鐮�" path="newPassword">
+ <NInput
+ v-model:value="passwordModel.newPassword"
+ type="password"
+ placeholder="璇疯緭鍏ユ柊瀵嗙爜"
+ show-password-on="click"
+ />
+ </NFormItem>
+ <NFormItem label="纭瀵嗙爜" path="confirmPassword">
+ <NInput
+ v-model:value="passwordModel.confirmPassword"
+ type="password"
+ placeholder="璇峰啀娆¤緭鍏ユ柊瀵嗙爜"
+ show-password-on="click"
+ />
+ </NFormItem>
+ <NFormItem class="flex items-center justify-end">
+ <NButton class="ml-20px w-120px" type="primary" :loading="btnLoading" @click="updatePassword">
+ <template #icon>
+ <SvgIcon icon="ic:outline-key" class="size-24px" />
+ </template>
+ 淇敼瀵嗙爜
+ </NButton>
+ </NFormItem>
+ </NForm>
+ </NTabPane>
+ <NTabPane name="social" tab="绗笁鏂瑰簲鐢�">
+ <SocialCard />
+ </NTabPane>
+ <NTabPane name="online" tab="鍦ㄧ嚎璁惧">
+ <div class="h-full">
+ <OnlineTable />
+ </div>
+ </NTabPane>
+ </NTabs>
+ </NCard>
+ </div>
+</template>
+
+<style scoped>
+.shadow-sm {
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+}
+
+:deep(.n-tabs-pane-wrapper),
+:deep(.n-tab-pane) {
+ height: 100% !important;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/_builtin/user-center/modules/online-table.vue b/ruoyi-plus-soybean/src/views/_builtin/user-center/modules/online-table.vue
new file mode 100755
index 0000000..d960750
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/user-center/modules/online-table.vue
@@ -0,0 +1,125 @@
+<script setup lang="tsx">
+import { NTime } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { fetchGetOnlineDeviceList, fetchKickOutCurrentDevice } from '@/service/api/monitor';
+import { useAppStore } from '@/store/modules/app';
+import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import { getBrowserIcon, getOsIcon } from '@/utils/icon-tag-format';
+import { $t } from '@/locales';
+import DictTag from '@/components/custom/dict-tag.vue';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import SvgIcon from '@/components/custom/svg-icon.vue';
+
+defineOptions({
+ name: 'OnlineTable'
+});
+
+useDict('sys_device_type');
+
+const appStore = useAppStore();
+const { loading: btnLoading, startLoading: startBtnLoading, endLoading: endBtnLoading } = useLoading(false);
+
+const { columns, data, getData, loading, scrollX } = useNaivePaginatedTable({
+ api: () => fetchGetOnlineDeviceList(),
+ transform: response => defaultTransform(response),
+ columns: () => [
+ {
+ title: '璁惧绫诲瀷',
+ key: 'deviceType',
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ return <DictTag size="small" value={row.deviceType} dict-code="sys_device_type" />;
+ }
+ },
+ { title: 'IP鍦板潃', key: 'ipaddr', align: 'center', minWidth: 120 },
+ { title: '鐧诲綍鍦扮偣', key: 'loginLocation', align: 'center', minWidth: 120 },
+ {
+ title: '娴忚鍣�',
+ key: 'browser',
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ return (
+ <div class="flex items-center justify-center gap-2">
+ <SvgIcon icon={getBrowserIcon(row.browser)} />
+ {row.browser}
+ </div>
+ );
+ }
+ },
+ {
+ title: '鎿嶄綔绯荤粺',
+ key: 'os',
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ const osName = row.os?.split(' or ')[0] ?? '';
+ return (
+ <div class="flex items-center justify-center gap-2">
+ <SvgIcon icon={getOsIcon(osName)} />
+ {osName}
+ </div>
+ );
+ }
+ },
+ {
+ title: '鐧诲綍鏃堕棿',
+ key: 'loginTime',
+ align: 'center',
+ minWidth: 180,
+ render: row => <NTime time={row.loginTime} format="yyyy-MM-dd HH:mm:ss" />
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ minWidth: 80,
+ render: row => {
+ return (
+ <div class="flex-center gap-8px">
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ loading={btnLoading.value}
+ class="text-18px"
+ tooltipContent="寮哄埗涓嬬嚎"
+ popconfirmContent="纭畾寮哄埗涓嬬嚎鍚楋紵"
+ onPositiveClick={() => forceLogout(row.tokenId)}
+ />
+ </div>
+ );
+ }
+ }
+ ]
+});
+
+/** 寮哄埗涓嬬嚎 */
+async function forceLogout(tokenId: string) {
+ startBtnLoading();
+ const { error } = await fetchKickOutCurrentDevice(tokenId);
+ if (!error) {
+ window.$message?.success('寮哄埗涓嬬嚎鎴愬姛');
+ await getData();
+ }
+ endBtnLoading();
+}
+</script>
+
+<template>
+ <NDataTable
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.noticeId"
+ class="h-full"
+ />
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/_builtin/user-center/modules/social-card.vue b/ruoyi-plus-soybean/src/views/_builtin/user-center/modules/social-card.vue
new file mode 100755
index 0000000..a8999de
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/user-center/modules/social-card.vue
@@ -0,0 +1,114 @@
+<script lang="ts" setup>
+import { ref } from 'vue';
+import { useLoading } from '@sa/hooks';
+import { fetchSocialAuthBinding, fetchSocialAuthUnbinding, fetchSocialList } from '@/service/api/system';
+import { useAuthStore } from '@/store/modules/auth';
+
+defineOptions({
+ name: 'SocialCard'
+});
+
+const authStore = useAuthStore();
+const { userInfo } = authStore;
+
+const socialList = ref<Api.System.Social[]>([]);
+const { loading, startLoading, endLoading } = useLoading();
+const { loading: btnLoading, startLoading: startBtnLoading, endLoading: endBtnLoading } = useLoading();
+
+/** 鑾峰彇SSO璐︽埛鍒楄〃 */
+async function getSsoUserList() {
+ startLoading();
+ const { data, error } = await fetchSocialList();
+ if (!error) {
+ socialList.value = data || [];
+ }
+ endLoading();
+}
+
+/** 缁戝畾SSO璐︽埛 */
+async function bindSsoAccount(type: Api.System.SocialSource) {
+ const { data, error } = await fetchSocialAuthBinding(type, userInfo.user?.tenantId);
+ if (!error) {
+ window.location.href = data;
+ }
+}
+
+/** 瑙g粦SSO璐︽埛 */
+async function unbindSsoAccount(socialId: CommonType.IdType) {
+ startBtnLoading();
+ const { error } = await fetchSocialAuthUnbinding(socialId);
+ if (!error) {
+ window.$message?.success('璐︽埛瑙g粦鎴愬姛');
+ await getSsoUserList();
+ }
+ endBtnLoading();
+}
+
+const socialSources: {
+ key: Api.System.SocialSource;
+ icon?: string;
+ localIcon?: string;
+ color: string;
+ name: string;
+}[] = [
+ { key: 'wechat_open', icon: 'ic:outline-wechat', color: '#44b549', name: '寰俊' },
+ { key: 'topiam', localIcon: 'topiam', color: '', name: 'TopIAM' },
+ { key: 'maxkey', localIcon: 'maxkey', color: '', name: 'MaxKey' },
+ { key: 'gitee', icon: 'simple-icons:gitee', color: '#c71d23', name: 'Gitee' },
+ { key: 'github', icon: 'mdi:github', color: '#010409', name: 'GitHub' }
+];
+
+getSsoUserList();
+
+function getSocial(key: string) {
+ return socialList.value.find(s => s.source.toLowerCase() === key);
+}
+</script>
+
+<template>
+ <NSpin :show="loading" class="mt-16px">
+ <div class="grid grid-cols-1 gap-16px 2xl:grid-cols-3 xl:grid-cols-2">
+ <div v-for="source in socialSources" :key="source.key" class="relative">
+ <NCard class="h-full transition-all duration-300 hover:shadow-md" :bordered="true">
+ <template v-if="getSocial(source.key)">
+ <div class="flex flex-col items-center gap-16px">
+ <NAvatar round size="large" :src="getSocial(source.key)?.avatar" class="size-80px" />
+ <div class="text-center">
+ <div class="text-16px font-medium">
+ {{ getSocial(source.key)?.nickName }}
+ </div>
+ <div class="mt-4px text-12px text-gray-500">缁戝畾鏃堕棿锛歿{ getSocial(source.key)?.createTime }}</div>
+ </div>
+ <NButton
+ type="error"
+ size="small"
+ :loading="btnLoading"
+ @click="unbindSsoAccount(getSocial(source.key)?.id || '')"
+ >
+ 瑙g粦
+ </NButton>
+ </div>
+ </template>
+ <template v-else>
+ <div class="h-full flex flex-col items-center justify-center gap-16px">
+ <SvgIcon
+ :local-icon="source.localIcon"
+ :icon="source.icon"
+ class="size-48px"
+ :style="{ color: source.color }"
+ />
+ <div class="text-16px font-medium">{{ source.name }}</div>
+ <NButton type="primary" size="small" @click="bindSsoAccount(source.key)">缁戝畾</NButton>
+ </div>
+ </template>
+ </NCard>
+ </div>
+ </div>
+ </NSpin>
+</template>
+
+<style scoped>
+.border-primary {
+ border-color: var(--primary-color);
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/_builtin/user-center/modules/user-avatar.vue b/ruoyi-plus-soybean/src/views/_builtin/user-center/modules/user-avatar.vue
new file mode 100755
index 0000000..45d0357
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/_builtin/user-center/modules/user-avatar.vue
@@ -0,0 +1,211 @@
+<script setup lang="ts">
+import { reactive, ref } from 'vue';
+import type { UploadFileInfo } from 'naive-ui';
+import { NButton, NModal, NUpload } from 'naive-ui';
+import { Cropper } from 'vue-advanced-cropper';
+import { useBoolean, useLoading } from '@sa/hooks';
+import { fetchUpdateUserAvatar } from '@/service/api/system';
+import { useAuthStore } from '@/store/modules/auth';
+import defaultAvatar from '@/assets/imgs/soybean.jpg';
+import 'vue-advanced-cropper/dist/style.css';
+
+interface CropperOptions {
+ img: string;
+ fileName: string;
+ stencilProps: {
+ aspectRatio: number;
+ };
+}
+
+interface CropperRef {
+ getResult: () => {
+ canvas: HTMLCanvasElement;
+ };
+}
+
+const authStore = useAuthStore();
+
+// 浣跨敤 useBoolean 绠$悊妯℃�佹鏄剧ず鐘舵��
+const { bool: showModal, setTrue: showDrawer, setFalse: hideDrawer } = useBoolean();
+// 浣跨敤 useLoading 绠$悊鍔犺浇鐘舵��
+const { loading, startLoading, endLoading } = useLoading();
+
+const imageUrl = ref(authStore.userInfo.user?.avatar || defaultAvatar);
+const cropperRef = ref<CropperRef | null>(null);
+
+// 鍥剧墖瑁佸壀鏁版嵁
+const options = reactive<CropperOptions>({
+ img: imageUrl.value,
+ fileName: '',
+ stencilProps: {
+ aspectRatio: 1
+ }
+});
+
+/** 缂栬緫澶村儚 */
+function handleEdit() {
+ options.img = imageUrl.value;
+ showDrawer();
+}
+
+/** 澶勭悊鏂囦欢閫夋嫨 */
+async function handleFileSelect(data: { file: UploadFileInfo }) {
+ const file = data.file.file;
+ if (!file) return false;
+
+ if (!file.type.includes('image/')) {
+ window.$message?.error('璇蜂笂浼犲浘鐗囩被鍨嬫枃浠讹紙JPG銆丳NG绛夛級');
+ return false;
+ }
+
+ const reader = new FileReader();
+ reader.onload = () => {
+ options.img = reader.result as string;
+ options.fileName = file.name;
+ };
+ reader.readAsDataURL(file);
+
+ return false;
+}
+
+/** 澶勭悊瑁佸壀 */
+async function handleCrop() {
+ if (!cropperRef.value) return;
+
+ startLoading();
+ try {
+ const { canvas } = cropperRef.value.getResult();
+
+ // 灏� canvas 杞崲涓� blob
+ canvas.toBlob(async (blob: Blob | null) => {
+ if (!blob) return;
+
+ const formData = new FormData();
+ formData.append('avatarfile', blob, options.fileName || 'avatar.png');
+
+ const { error } = await fetchUpdateUserAvatar(formData);
+ if (!error) {
+ window.$message?.success('澶村儚鏇存柊鎴愬姛锛�');
+ imageUrl.value = URL.createObjectURL(blob);
+ authStore.userInfo.user!.avatar = imageUrl.value;
+ hideDrawer();
+ }
+ }, 'image/png');
+ } finally {
+ endLoading();
+ }
+}
+
+/** 鍏抽棴瀵硅瘽妗� */
+function handleClose() {
+ hideDrawer();
+ options.img = imageUrl.value;
+}
+</script>
+
+<template>
+ <div class="cursor-pointer" @click="handleEdit">
+ <div class="relative h-120px w-120px overflow-hidden rounded-full">
+ <img :src="imageUrl" alt="user-avatar" class="h-full w-full object-cover" />
+ <div
+ class="absolute inset-0 flex-center bg-black/50 text-white opacity-0 transition-opacity duration-300 hover:opacity-100"
+ >
+ <SvgIcon icon="ep:plus" class="text-24px" />
+ </div>
+ </div>
+
+ <NModal v-model:show="showModal" preset="card" title="淇敼澶村儚" class="w-400px" @close="handleClose">
+ <div class="flex-col-center gap-20px py-20px">
+ <div class="h-300px w-full">
+ <Cropper
+ ref="cropperRef"
+ class="h-full bg-gray-100"
+ :src="options.img"
+ :stencil-props="options.stencilProps"
+ />
+ </div>
+ <div class="flex gap-12px">
+ <NUpload accept=".jpg,.jpeg,.png,.gif" :max="1" :show-file-list="false" @before-upload="handleFileSelect">
+ <NButton class="min-w-100px">閫夋嫨鍥剧墖</NButton>
+ </NUpload>
+ <NButton type="primary" class="min-w-100px" :loading="loading" @click="handleCrop">纭瑁佸壀</NButton>
+ </div>
+ </div>
+ </NModal>
+ </div>
+</template>
+
+<style lang="scss" scoped>
+.avatar-wrapper {
+ display: inline-block;
+ cursor: pointer;
+}
+
+.avatar-container {
+ position: relative;
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ overflow: hidden;
+
+ &:hover .avatar-overlay {
+ opacity: 1;
+ }
+}
+
+.avatar-image {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.avatar-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.5);
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ color: #fff;
+}
+
+.upload-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+ padding: 20px 0;
+}
+
+.cropper-container {
+ width: 100%;
+ height: 300px;
+}
+
+.cropper {
+ height: 100%;
+ background: #f8f8f8;
+}
+
+.preview-image {
+ width: 200px;
+ height: 200px;
+ border-radius: 50%;
+ object-fit: cover;
+ border: 1px solid #eee;
+}
+
+.button-group {
+ display: flex;
+ gap: 12px;
+}
+
+.upload-button {
+ min-width: 100px;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/about/index.vue b/ruoyi-plus-soybean/src/views/about/index.vue
new file mode 100755
index 0000000..333127e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/about/index.vue
@@ -0,0 +1,110 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { useAppStore } from '@/store/modules/app';
+import { $t } from '@/locales';
+import pkg from '~/package.json';
+
+const appStore = useAppStore();
+
+const column = computed(() => (appStore.isMobile ? 1 : 2));
+
+interface PkgJson {
+ name: string;
+ version: string;
+ dependencies: PkgVersionInfo[];
+ devDependencies: PkgVersionInfo[];
+}
+
+interface PkgVersionInfo {
+ name: string;
+ version: string;
+}
+
+const { name, version, dependencies, devDependencies } = pkg;
+
+function transformVersionData(tuple: [string, string]): PkgVersionInfo {
+ const [$name, $version] = tuple;
+ return {
+ name: $name,
+ version: $version
+ };
+}
+
+const pkgJson: PkgJson = {
+ name,
+ version,
+ dependencies: Object.entries(dependencies).map(item => transformVersionData(item)),
+ devDependencies: Object.entries(devDependencies).map(item => transformVersionData(item))
+};
+
+const latestBuildTime = BUILD_TIME;
+
+function toLink(url: string) {
+ window.open(url, '_blank');
+}
+</script>
+
+<template>
+ <NSpace vertical :size="16">
+ <NCard :title="$t('page.about.title')" :bordered="false" size="small" segmented class="card-wrapper">
+ <template #header-extra>
+ <div class="flex gap-3px">
+ <ButtonIcon
+ icon="simple-icons:github"
+ :tooltip-content="`GitHub ${$t('page.about.projectInfo.repositoryLink')}`"
+ @click="toLink('https://github.com/m-xlsea/ruoyi-plus-soybean')"
+ />
+ <ButtonIcon
+ class="color-#da203e"
+ icon="simple-icons:gitcode"
+ :tooltip-content="`GitCode ${$t('page.about.projectInfo.repositoryLink')}`"
+ @click="toLink('https://gitcode.com/xlsea/ruoyi-plus-soybean')"
+ />
+ <ButtonIcon
+ class="color-#c71d23"
+ icon="simple-icons:gitee"
+ :tooltip-content="`Gitee ${$t('page.about.projectInfo.repositoryLink')}`"
+ @click="toLink('https://gitee.com/xlsea/ruoyi-plus-soybean')"
+ />
+ </div>
+ </template>
+ <p>{{ $t('page.about.introduction') }}</p>
+ </NCard>
+ <NCard :title="$t('page.about.projectInfo.title')" :bordered="false" size="small" segmented class="card-wrapper">
+ <NDescriptions label-placement="left" bordered size="small" :column="column">
+ <NDescriptionsItem :label="$t('page.about.projectInfo.version')">
+ <NTag type="primary">{{ pkgJson.version }}</NTag>
+ </NDescriptionsItem>
+ <NDescriptionsItem :label="$t('page.about.projectInfo.latestBuildTime')">
+ <NTag type="primary">{{ latestBuildTime }}</NTag>
+ </NDescriptionsItem>
+ <NDescriptionsItem :label="$t('page.about.projectInfo.documentLink')">
+ <a class="text-primary" :href="pkg.homepage" target="_blank" rel="noopener noreferrer">
+ {{ $t('page.about.projectInfo.documentLink') }}
+ </a>
+ </NDescriptionsItem>
+ <NDescriptionsItem :label="$t('page.about.projectInfo.previewLink')">
+ <a class="text-primary" :href="pkg.website" target="_blank" rel="noopener noreferrer">
+ {{ $t('page.about.projectInfo.previewLink') }}
+ </a>
+ </NDescriptionsItem>
+ </NDescriptions>
+ </NCard>
+ <NCard :title="$t('page.about.prdDep')" :bordered="false" size="small" segmented class="card-wrapper">
+ <NDescriptions label-placement="left" bordered size="small" :column="column">
+ <NDescriptionsItem v-for="item in pkgJson.dependencies" :key="item.name" :label="item.name">
+ {{ item.version }}
+ </NDescriptionsItem>
+ </NDescriptions>
+ </NCard>
+ <NCard :title="$t('page.about.devDep')" :bordered="false" size="small" segmented class="card-wrapper">
+ <NDescriptions label-placement="left" bordered size="small" :column="column">
+ <NDescriptionsItem v-for="item in pkgJson.devDependencies" :key="item.name" :label="item.name">
+ {{ item.version }}
+ </NDescriptionsItem>
+ </NDescriptions>
+ </NCard>
+ </NSpace>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/analy/hoister/index.vue b/ruoyi-plus-soybean/src/views/analy/hoister/index.vue
new file mode 100755
index 0000000..9e83f57
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/analy/hoister/index.vue
@@ -0,0 +1,413 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDeleteHoisterData, fetchGetHoisterDataList } from '@/service/api/analy/hoister-data';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import HoisterDataOperateDrawer from './modules/hoister-data-operate-drawer.vue';
+import HoisterDataSearch from './modules/hoister-data-search.vue';
+
+defineOptions({
+ name: 'HoisterDataList'
+});
+
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.Qa.HoisterDataSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ time: null,
+ shift: null,
+ equNo: 601,
+ params: {
+ beginTime: `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate()).padStart(2, '0')} 00:00:00`,
+ endTime: `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate()).padStart(2, '0')} 23:59:59`
+ }
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetHoisterDataList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'time',
+ title: '鏃堕棿',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'key',
+ title: 'key',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'online',
+ title: '缃戠粶鐘舵�� 0寮傚父锛�1姝e父',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'qty',
+ title: '浜ч噺',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tState1',
+ title: '1#鎻愬崌鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tState2',
+ title: '2#鎻愬崌鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tState3',
+ title: '3#鎻愬崌鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tState4',
+ title: '4#鎻愬崌鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tState5',
+ title: '5#鎻愬崌鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tState6',
+ title: '6#鎻愬崌鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tState7',
+ title: '7#鎻愬崌鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tState8',
+ title: '8#鎻愬崌鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tState9',
+ title: '9#鎻愬崌鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tState10',
+ title: '10#鎻愬崌鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tState11',
+ title: '11#鎻愬崌鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tState12',
+ title: '12#鎻愬崌鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'pState1',
+ title: '1#鎺掑寘鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'pState2',
+ title: '2#鎺掑寘鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'pState3',
+ title: '3#鎺掑寘鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'pState4',
+ title: '4#鎺掑寘鏈虹姸鎬�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tQty1',
+ title: '1#鎻愬崌鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tQty2',
+ title: '2#鎻愬崌鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tQty3',
+ title: '3#鎻愬崌鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tQty4',
+ title: '4#鎻愬崌鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tQty5',
+ title: '5#鎻愬崌鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tQty6',
+ title: '6#鎻愬崌鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tQty7',
+ title: '7#鎻愬崌鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tQty8',
+ title: '8#鎻愬崌鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tQty9',
+ title: '9#鎻愬崌鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tQty10',
+ title: '10#鎻愬崌鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tQty11',
+ title: '11#鎻愬崌鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tQty12',
+ title: '12#鎻愬崌鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'pQty1',
+ title: '1#鎺掑寘鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'pQty2',
+ title: '2#鎺掑寘鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'pQty3',
+ title: '3#鎺掑寘鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'pQty4',
+ title: '4#鎺掑寘鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'shift',
+ title: '鐝',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'equNo',
+ title: '璁惧',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'remark',
+ title: '澶囨敞',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('qa:hoisterData:edit') || !hasAuth('qa:hoisterData:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('qa:hoisterData:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.time)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('qa:hoisterData:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.time)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+});
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'time', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteHoisterData(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(time: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteHoisterData([time]);
+ if (error) return;
+ onDeleted();
+}
+
+function edit(time: CommonType.IdType) {
+ handleEdit(time);
+}
+
+function handleExport() {
+ download('/qa/hoisterData/export', searchParams.value, `鎻愬崌鏈哄垎鏋恄${new Date().getTime()}.xlsx`);
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <HoisterDataSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard title="鎻愬崌鏈哄垎鏋愬垪琛�" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('qa:hoisterData:add')"
+ :show-delete="hasAuth('qa:hoisterData:remove')"
+ :show-export="hasAuth('qa:hoisterData:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ />
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.time"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <HoisterDataOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getDataByPage"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/analy/hoister/modules/hoister-data-operate-drawer.vue b/ruoyi-plus-soybean/src/views/analy/hoister/modules/hoister-data-operate-drawer.vue
new file mode 100755
index 0000000..0864f86
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/analy/hoister/modules/hoister-data-operate-drawer.vue
@@ -0,0 +1,285 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '~/packages/utils';
+import { fetchCreateHoisterData, fetchUpdateHoisterData } from '@/service/api/analy/hoister-data';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'HoisterDataOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.Qa.HoisterData | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: '鏂板鎻愬崌鏈哄垎鏋�',
+ edit: '缂栬緫鎻愬崌鏈哄垎鏋�'
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.Qa.HoisterDataOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ time: null,
+ key: '',
+ online: null,
+ qty: null,
+ tState1: null,
+ tState2: null,
+ tState3: null,
+ tState4: null,
+ tState5: null,
+ tState6: null,
+ tState7: null,
+ tState8: null,
+ tState9: null,
+ tState10: null,
+ tState11: null,
+ tState12: null,
+ pState1: null,
+ pState2: null,
+ pState3: null,
+ pState4: null,
+ tQty1: null,
+ tQty2: null,
+ tQty3: null,
+ tQty4: null,
+ tQty5: null,
+ tQty6: null,
+ tQty7: null,
+ tQty8: null,
+ tQty9: null,
+ tQty10: null,
+ tQty11: null,
+ tQty12: null,
+ pQty1: null,
+ pQty2: null,
+ pQty3: null,
+ pQty4: null,
+ shift: null,
+ equNo: null,
+ remark: ''
+ };
+}
+
+type RuleKey = Extract<
+ keyof Model,
+ | 'time'
+ | 'key'
+>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ time: createRequiredRule('鏃堕棿涓嶈兘涓虹┖'),
+ key: createRequiredRule('key涓嶈兘涓虹┖'),
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { time, key, online, qty, tState1, tState2, tState3, tState4, tState5, tState6, tState7, tState8, tState9, tState10, tState11, tState12, pState1, pState2, pState3, pState4, tQty1, tQty2, tQty3, tQty4, tQty5, tQty6, tQty7, tQty8, tQty9, tQty10, tQty11, tQty12, pQty1, pQty2, pQty3, pQty4, shift, equNo, remark } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateHoisterData({ time, key, online, qty, tState1, tState2, tState3, tState4, tState5, tState6, tState7, tState8, tState9, tState10, tState11, tState12, pState1, pState2, pState3, pState4, tQty1, tQty2, tQty3, tQty4, tQty5, tQty6, tQty7, tQty8, tQty9, tQty10, tQty11, tQty12, pQty1, pQty2, pQty3, pQty4, shift, equNo, remark });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateHoisterData({ time, key, online, qty, tState1, tState2, tState3, tState4, tState5, tState6, tState7, tState8, tState9, tState10, tState11, tState12, pState1, pState2, pState3, pState4, tQty1, tQty2, tQty3, tQty4, tQty5, tQty6, tQty7, tQty8, tQty9, tQty10, tQty11, tQty12, pQty1, pQty2, pQty3, pQty4, shift, equNo, remark });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ getTreeList();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem label="鏃堕棿" path="time">
+ <NDatePicker
+ v-model:formatted-value="model.time"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ />
+ </NFormItem>
+ <NFormItem label="key" path="key">
+ <NInput
+ v-model:value="model.key"
+ :rows="3"
+ type="textarea"
+ placeholder="璇疯緭鍏ey"
+ />
+ </NFormItem>
+ <NFormItem label="缃戠粶鐘舵�� 0寮傚父锛�1姝e父" path="online">
+ <NInput v-model:value="model.online" placeholder="璇疯緭鍏ョ綉缁滅姸鎬� 0寮傚父锛�1姝e父" />
+ </NFormItem>
+ <NFormItem label="浜ч噺" path="qty">
+ <NInput v-model:value="model.qty" placeholder="璇疯緭鍏ヤ骇閲�" />
+ </NFormItem>
+ <NFormItem label="1#鎻愬崌鏈虹姸鎬�" path="tState1">
+ <NInput v-model:value="model.tState1" placeholder="璇疯緭鍏�1#鎻愬崌鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="2#鎻愬崌鏈虹姸鎬�" path="tState2">
+ <NInput v-model:value="model.tState2" placeholder="璇疯緭鍏�2#鎻愬崌鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="3#鎻愬崌鏈虹姸鎬�" path="tState3">
+ <NInput v-model:value="model.tState3" placeholder="璇疯緭鍏�3#鎻愬崌鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="4#鎻愬崌鏈虹姸鎬�" path="tState4">
+ <NInput v-model:value="model.tState4" placeholder="璇疯緭鍏�4#鎻愬崌鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="5#鎻愬崌鏈虹姸鎬�" path="tState5">
+ <NInput v-model:value="model.tState5" placeholder="璇疯緭鍏�5#鎻愬崌鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="6#鎻愬崌鏈虹姸鎬�" path="tState6">
+ <NInput v-model:value="model.tState6" placeholder="璇疯緭鍏�6#鎻愬崌鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="7#鎻愬崌鏈虹姸鎬�" path="tState7">
+ <NInput v-model:value="model.tState7" placeholder="璇疯緭鍏�7#鎻愬崌鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="8#鎻愬崌鏈虹姸鎬�" path="tState8">
+ <NInput v-model:value="model.tState8" placeholder="璇疯緭鍏�8#鎻愬崌鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="9#鎻愬崌鏈虹姸鎬�" path="tState9">
+ <NInput v-model:value="model.tState9" placeholder="璇疯緭鍏�9#鎻愬崌鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="10#鎻愬崌鏈虹姸鎬�" path="tState10">
+ <NInput v-model:value="model.tState10" placeholder="璇疯緭鍏�10#鎻愬崌鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="11#鎻愬崌鏈虹姸鎬�" path="tState11">
+ <NInput v-model:value="model.tState11" placeholder="璇疯緭鍏�11#鎻愬崌鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="12#鎻愬崌鏈虹姸鎬�" path="tState12">
+ <NInput v-model:value="model.tState12" placeholder="璇疯緭鍏�12#鎻愬崌鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="1#鎺掑寘鏈虹姸鎬�" path="pState1">
+ <NInput v-model:value="model.pState1" placeholder="璇疯緭鍏�1#鎺掑寘鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="2#鎺掑寘鏈虹姸鎬�" path="pState2">
+ <NInput v-model:value="model.pState2" placeholder="璇疯緭鍏�2#鎺掑寘鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="3#鎺掑寘鏈虹姸鎬�" path="pState3">
+ <NInput v-model:value="model.pState3" placeholder="璇疯緭鍏�3#鎺掑寘鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="4#鎺掑寘鏈虹姸鎬�" path="pState4">
+ <NInput v-model:value="model.pState4" placeholder="璇疯緭鍏�4#鎺掑寘鏈虹姸鎬�" />
+ </NFormItem>
+ <NFormItem label="1#鎻愬崌鏈轰骇閲�" path="tQty1">
+ <NInput v-model:value="model.tQty1" placeholder="璇疯緭鍏�1#鎻愬崌鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="2#鎻愬崌鏈轰骇閲�" path="tQty2">
+ <NInput v-model:value="model.tQty2" placeholder="璇疯緭鍏�2#鎻愬崌鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="3#鎻愬崌鏈轰骇閲�" path="tQty3">
+ <NInput v-model:value="model.tQty3" placeholder="璇疯緭鍏�3#鎻愬崌鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="4#鎻愬崌鏈轰骇閲�" path="tQty4">
+ <NInput v-model:value="model.tQty4" placeholder="璇疯緭鍏�4#鎻愬崌鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="5#鎻愬崌鏈轰骇閲�" path="tQty5">
+ <NInput v-model:value="model.tQty5" placeholder="璇疯緭鍏�5#鎻愬崌鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="6#鎻愬崌鏈轰骇閲�" path="tQty6">
+ <NInput v-model:value="model.tQty6" placeholder="璇疯緭鍏�6#鎻愬崌鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="7#鎻愬崌鏈轰骇閲�" path="tQty7">
+ <NInput v-model:value="model.tQty7" placeholder="璇疯緭鍏�7#鎻愬崌鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="8#鎻愬崌鏈轰骇閲�" path="tQty8">
+ <NInput v-model:value="model.tQty8" placeholder="璇疯緭鍏�8#鎻愬崌鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="9#鎻愬崌鏈轰骇閲�" path="tQty9">
+ <NInput v-model:value="model.tQty9" placeholder="璇疯緭鍏�9#鎻愬崌鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="10#鎻愬崌鏈轰骇閲�" path="tQty10">
+ <NInput v-model:value="model.tQty10" placeholder="璇疯緭鍏�10#鎻愬崌鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="11#鎻愬崌鏈轰骇閲�" path="tQty11">
+ <NInput v-model:value="model.tQty11" placeholder="璇疯緭鍏�11#鎻愬崌鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="12#鎻愬崌鏈轰骇閲�" path="tQty12">
+ <NInput v-model:value="model.tQty12" placeholder="璇疯緭鍏�12#鎻愬崌鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="1#鎺掑寘鏈轰骇閲�" path="pQty1">
+ <NInput v-model:value="model.pQty1" placeholder="璇疯緭鍏�1#鎺掑寘鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="2#鎺掑寘鏈轰骇閲�" path="pQty2">
+ <NInput v-model:value="model.pQty2" placeholder="璇疯緭鍏�2#鎺掑寘鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="3#鎺掑寘鏈轰骇閲�" path="pQty3">
+ <NInput v-model:value="model.pQty3" placeholder="璇疯緭鍏�3#鎺掑寘鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="4#鎺掑寘鏈轰骇閲�" path="pQty4">
+ <NInput v-model:value="model.pQty4" placeholder="璇疯緭鍏�4#鎺掑寘鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="鐝" path="shift">
+ <NInput v-model:value="model.shift" placeholder="璇疯緭鍏ョ彮娆�" />
+ </NFormItem>
+ <NFormItem label="璁惧" path="equNo">
+ <NInput v-model:value="model.equNo" placeholder="璇疯緭鍏ヨ澶�" />
+ </NFormItem>
+ <NFormItem label="澶囨敞" path="remark">
+ <NInput v-model:value="model.remark" placeholder="璇疯緭鍏ュ娉�" />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/analy/hoister/modules/hoister-data-search.vue b/ruoyi-plus-soybean/src/views/analy/hoister/modules/hoister-data-search.vue
new file mode 100755
index 0000000..db26889
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/analy/hoister/modules/hoister-data-search.vue
@@ -0,0 +1,147 @@
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+import type { FormRules } from 'naive-ui';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'HoisterDataSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Qa.HoisterDataSearchParams>('model', { required: true });
+
+const rules: FormRules = {
+ equNo: {
+ required: true,
+ type: 'number',
+ message: '璇烽�夋嫨璁惧',
+ trigger: ['blur', 'change']
+ },
+ 'params.beginTime': {
+ required: true,
+ message: '璇烽�夋嫨鏃堕棿鍖洪棿',
+ trigger: ['blur', 'change']
+ }
+};
+
+const timeRange = ref<[string, string] | null>(null);
+
+// sync model params to timeRange
+watch(
+ () => model.value.params,
+ (val: Api.Qa.HoisterDataSearchParams['params']) => {
+ if (val?.beginTime && val?.endTime) {
+ timeRange.value = [val.beginTime, val.endTime];
+ } else {
+ timeRange.value = null;
+ }
+ },
+ { immediate: true, deep: true }
+);
+
+const shiftOptions = [
+ { label: '鏃╃彮', value: 1 },
+ { label: '涓彮', value: 2 },
+ { label: '鏅氱彮', value: 3 }
+];
+
+const equNoOptions = [
+ { label: '1#鎻愬崌鏈�', value: 601 }
+];
+
+function onTimeRangeUpdate(value: [string, string] | null) {
+ if (!model.value.params) {
+ model.value.params = {};
+ }
+ if (value && value.length === 2) {
+ [model.value.params.beginTime, model.value.params.endTime] = value;
+ } else {
+ model.value.params.beginTime = undefined;
+ model.value.params.endTime = undefined;
+ }
+}
+
+async function reset() {
+ await restoreValidation();
+ model.value.shift = null;
+ model.value.equNo = 601;
+
+ const now = new Date();
+ const todayStart = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} 00:00:00`;
+ const todayEnd = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} 23:59:59`;
+
+ timeRange.value = [todayStart, todayEnd];
+ onTimeRangeUpdate(timeRange.value);
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="qa-hoister-data-search">
+ <NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:8" label="鏃堕棿鍖洪棿" path="params.beginTime" class="pr-24px">
+ <NDatePicker
+ v-model:formatted-value="timeRange"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ @update:formatted-value="onTimeRangeUpdate"
+ :default-time="['00:00:00', '23:59:59']"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="璁惧" path="equNo" class="pr-24px">
+ <NSelect
+ v-model:value="model.equNo"
+ placeholder="璇烽�夋嫨璁惧鍙�"
+ :options="equNoOptions"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鐝" path="shift" class="pr-24px">
+ <NSelect
+ v-model:value="model.shift"
+ placeholder="璇烽�夋嫨鐝"
+ :options="shiftOptions"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/analy/output-analy/index.vue b/ruoyi-plus-soybean/src/views/analy/output-analy/index.vue
new file mode 100755
index 0000000..7d5d986
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/analy/output-analy/index.vue
@@ -0,0 +1,269 @@
+<script setup lang="tsx">
+import { reactive, ref } from 'vue';
+import { NTag } from 'naive-ui';
+import type { PaginationProps } from 'naive-ui';
+import { fetchGetRollerSampleDataList } from '@/service/api/analy/roller-data';
+import { useAppStore } from '@/store/modules/app';
+import { useDownload } from '@/hooks/business/download';
+import { useNaiveTable } from '@/hooks/common/table';
+import RollerDataSearch from './modules/roller-data-search.vue';
+import RollerDataLineChart from './modules/roller-data-line-chart.vue';
+
+defineOptions({
+ name: 'RollerDataList'
+});
+
+const appStore = useAppStore();
+const { download } = useDownload();
+
+const showTable = ref(false);
+
+const equNoMap: Record<number, string> = {};
+Array.from({ length: 13 }, (_, i) => i + 1)
+ .filter(i => i !== 4)
+ .forEach(i => {
+ equNoMap[100 + i] = `${i}#鍗锋帴鏈虹粍`;
+ });
+
+const shiftMap: Record<number, string> = {
+ 1: '鏃╃彮',
+ 2: '涓彮',
+ 3: '鏅氱彮'
+};
+
+const searchParams = ref<Api.Qa.RollerDataSearchParams>({
+ time: null,
+ key: null,
+ online: null,
+ qty: null,
+ badQty: null,
+ lvbangVal: null,
+ juanyanzhiVal: null,
+ shuisongzhiVal: null,
+ runTime: null,
+ stopTime: null,
+ stopTimes: null,
+ speed: null,
+ runStatus: null,
+ cy: null,
+ cyCs: null,
+ cyOnline: null,
+ recQty1: null,
+ recQty2: null,
+ params: {
+ beginTime: `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate()).padStart(2, '0')} 00:00:00`,
+ endTime: `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate()).padStart(2, '0')} 23:59:59`
+ },
+ shift: null,
+ equNo: 101
+});
+
+const pagination = reactive<PaginationProps>({
+ page: 1,
+ pageSize: 20,
+ showSizePicker: true,
+ pageSizes: [20, 50, 100, 500],
+ prefix: ({ itemCount }) => `鍏� ${itemCount} 鏉,
+ onChange: (page: number) => {
+ pagination.page = page;
+ },
+ onUpdatePageSize: (pageSize: number) => {
+ pagination.pageSize = pageSize;
+ pagination.page = 1;
+ }
+});
+
+const { columns, columnChecks, data, getData, loading, scrollX } = useNaiveTable({
+ api: () => fetchGetRollerSampleDataList(searchParams.value),
+ transform: (response: any) => response.data || [],
+ columns: () => ([
+ {
+ key: 'index',
+ title: '搴忓彿',
+ align: 'center',
+ width: 64,
+ render: (_: Api.Qa.RollerData, index: number) => index + 1
+ },
+ {
+ key: 'time',
+ title: '鏃堕棿',
+ align: 'center',
+ minWidth: 160
+ },
+ {
+ key: 'equNo',
+ title: '璁惧',
+ align: 'center',
+ minWidth: 120,
+ render: (row: Api.Qa.RollerData) => equNoMap[row.equNo] || row.equNo
+ },
+ {
+ key: 'shift',
+ title: '鐝',
+ align: 'center',
+ minWidth: 80,
+ render: (row: Api.Qa.RollerData) => <NTag type="info">{shiftMap[row.shift] || row.shift}</NTag>
+ },
+ {
+ key: 'qty',
+ title: '鍗锋帴鏈轰骇閲�(鍗冩敮)',
+ align: 'center',
+ minWidth: 100,
+ render: (row: Api.Qa.RollerData) => Number(row.qty ?? 0).toFixed(1)
+ },
+ {
+ key: 'qtyBox',
+ title: '鍗锋帴鏈轰骇閲�(绠�)',
+ align: 'center',
+ minWidth: 120,
+ render: (row: Api.Qa.RollerData) => {
+ const v = Number(row.qty ?? 0) / 50;
+ return v.toFixed(1);
+ }
+ },
+ {
+ key: 'tsQtyTiao',
+ title: '鎻愬崌鏈轰骇閲�(鏉�)',
+ align: 'center',
+ minWidth: 120,
+ render: (row: Api.Qa.RollerData) => {
+ if (row.tsQty === null || row.tsQty === undefined) return '-';
+ return Number(row.tsQty).toFixed(1);
+ }
+ },
+ {
+ key: 'tsQtyBox',
+ title: '鎻愬崌鏈轰骇閲�(绠�)',
+ align: 'center',
+ minWidth: 120,
+ render: (row: Api.Qa.RollerData) => {
+ if (row.tsQty === null || row.tsQty === undefined) return '-';
+ const v = Number(row.tsQty) / 250;
+ return v.toFixed(1);
+ }
+ },
+ {
+ key: 'packerQty',
+ title: '鍖呰鏈轰骇閲�(灏忓寘)',
+ align: 'center',
+ minWidth: 140,
+ render: (row: Api.Qa.RollerData) => {
+ if (row.packerQty === null || row.packerQty === undefined) return '-';
+ return Number(row.packerQty).toFixed(1);
+ }
+ },
+ {
+ key: 'packerQtyBox',
+ title: '鍖呰鏈轰骇閲�(绠�)',
+ align: 'center',
+ minWidth: 120,
+ render: (row: Api.Qa.RollerData) => {
+ if (row.packerQty === null || row.packerQty === undefined) return '-';
+ const v = Number(row.packerQty) / 10 / 250;
+ return v.toFixed(1);
+ }
+ },
+ {
+ key: 'lvbangVal',
+ title: '婊ゆ娑堣��(鏀�)',
+ align: 'center',
+ minWidth: 140,
+ render: (row: Api.Qa.RollerData) => Number(row.lvbangVal ?? 0).toFixed(1)
+ },
+ {
+ key: 'lvbangWan',
+ title: '婊ゆ娑堣��(涓囨敮)',
+ align: 'center',
+ minWidth: 140,
+ render: (row: Api.Qa.RollerData) => {
+ const v = Number(row.lvbangVal ?? 0) / 10000;
+ return v.toFixed(1);
+ }
+ },
+ {
+ key: 'danhao',
+ title: '婊ゆ鍗曡��(涓囨敮/绠�)',
+ align: 'center',
+ minWidth: 180,
+ render: (row: Api.Qa.RollerData) => {
+ const qtyBox = Number(row.qty ?? 0) / 50;
+ if (!qtyBox) return '-';
+ const v = (Number(row.lvbangVal ?? 0) / 10000) / qtyBox;
+ return v.toFixed(1);
+ }
+ }
+ ] as any)
+});
+
+async function handleExport() {
+ await download('/qa/rollerData/export', searchParams.value, `鍗锋帴鏈烘暟鎹甠${new Date().getTime()}.xlsx`);
+}
+</script>
+
+<template>
+ <div class="h-full min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <RollerDataSearch
+ v-model:model="searchParams"
+ @search="getData"
+ />
+ <NCard
+ v-if="!showTable"
+ title="鍗锋帴鏈哄垎鏋愯秼鍔垮浘"
+ :bordered="false"
+ size="small"
+ class="sm:flex-1-hidden card-wrapper flex flex-col"
+ content-style="flex: 1; min-height: 0; overflow: hidden;"
+ >
+ <template #header-extra>
+ <NButton size="small" @click="showTable = true">
+ <template #icon>
+ <icon-ic-round-table-view class="text-icon" />
+ </template>
+ 鏁版嵁璇︽儏
+ </NButton>
+ </template>
+ <RollerDataLineChart :data="(data as any)" class="h-full" />
+ </NCard>
+ <NCard
+ v-else
+ title="鏁版嵁璇︽儏"
+ :bordered="false"
+ size="small"
+ class="sm:flex-1-hidden card-wrapper"
+ >
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :loading="loading"
+ :show-add="false"
+ :show-delete="false"
+ :show-export="true"
+ :show-refresh="false"
+ @export="handleExport"
+ >
+ <template #prefix>
+ <NButton size="small" @click="showTable = false">
+ <template #icon>
+ <icon-ic-round-bar-chart class="text-icon" />
+ </template>
+ 瓒嬪娍鍥�
+ </NButton>
+ </template>
+ </TableHeaderOperation>
+ </template>
+ <NDataTable
+ :columns="columns"
+ :data="(data as any)"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ :pagination="pagination"
+ :row-key="row => row.id"
+ class="sm:h-full"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/analy/output-analy/modules/roller-data-line-chart.vue b/ruoyi-plus-soybean/src/views/analy/output-analy/modules/roller-data-line-chart.vue
new file mode 100755
index 0000000..77271ca
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/analy/output-analy/modules/roller-data-line-chart.vue
@@ -0,0 +1,312 @@
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+import { useEcharts } from '@/hooks/common/echarts';
+
+defineOptions({
+ name: 'RollerDataLineChart'
+});
+
+interface Props {
+ data: Api.Qa.RollerData[];
+}
+
+const props = defineProps<Props>();
+
+const sortedData = ref<Api.Qa.RollerData[]>([]);
+
+const { domRef, updateOptions } = useEcharts(() => ({
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'cross',
+ label: {
+ backgroundColor: '#6a7985'
+ }
+ },
+ formatter: (params: any) => {
+ const lines = Array.isArray(params)
+ ? params.map((p: any) => {
+ const val = Number(p.value);
+ const v = Number.isFinite(val) ? val.toFixed(1) : '-';
+ let unit = '';
+ if (p.seriesName.includes('涓囨敮/绠�')) {
+ unit = '涓囨敮/绠�';
+ } else if (p.seriesName.includes('绠�')) {
+ unit = '绠�';
+ } else {
+ unit = '涓囨敮';
+ }
+ return `${p.marker}${p.seriesName}: ${v}${unit}`;
+ })
+ : [];
+ const name = Array.isArray(params) && params.length ? params[0].name : '';
+
+ let shiftLine = '';
+ if (Array.isArray(params) && params.length > 0) {
+ const dataIndex = params[0].dataIndex;
+ if (sortedData.value[dataIndex]) {
+ const shift = sortedData.value[dataIndex].shift;
+ const shiftMap: Record<number, string> = { 1: '鏃�', 2: '涓�', 3: '鏅�' };
+ const shiftText = shiftMap[shift] || shift;
+ shiftLine = `鐝: ${shiftText}`;
+ }
+ }
+
+ return [name, shiftLine, ...lines].filter(Boolean).join('<br/>');
+ }
+ },
+ legend: {
+ data: ['鍗锋帴鏈轰骇閲�(绠�)', '鎻愬崌鏈轰骇閲�(绠�)', '鍖呰鏈轰骇閲�(绠�)', '婊ゆ娑堣��(涓囨敮)', '婊ゆ鍗曡��(涓囨敮/绠�)'],
+ selected: {
+ '婊ゆ娑堣��(涓囨敮)': false,
+ '婊ゆ鍗曡��(涓囨敮/绠�)': false
+ },
+ top: '0'
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ top: '15%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ boundaryGap: false,
+ data: [] as string[]
+ },
+ yAxis: [
+ {
+ type: 'value',
+ name: '绠便�佷竾鏀�/绠�',
+ min: 0,
+ max: (value: { max: number }) => {
+ return Math.ceil(value.max * 1.1);
+ },
+ axisLabel: {
+ formatter: (val: number) => Number(val).toFixed(1)
+ }
+ },
+ {
+ type: 'value',
+ name: '涓囨敮',
+ min: 0,
+ max: (value: { max: number }) => {
+ return Math.ceil(value.max * 2.5);
+ },
+ splitLine: {
+ show: false
+ },
+ axisLabel: {
+ formatter: (val: number) => Number(val).toFixed(1)
+ }
+ }
+ ],
+ series: [
+ {
+ color: '#8e9dff',
+ name: '鍗锋帴鏈轰骇閲�(绠�)',
+ type: 'line',
+ smooth: true,
+ yAxisIndex: 0,
+ areaStyle: {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ {
+ offset: 0.25,
+ color: '#8e9dff'
+ },
+ {
+ offset: 1,
+ color: '#fff'
+ }
+ ]
+ }
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ data: [] as number[]
+ },
+ {
+ color: '#26deca',
+ name: '鎻愬崌鏈轰骇閲�(绠�)',
+ type: 'line',
+ smooth: true,
+ yAxisIndex: 0,
+ areaStyle: {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ {
+ offset: 0.25,
+ color: '#26deca'
+ },
+ {
+ offset: 1,
+ color: '#fff'
+ }
+ ]
+ }
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ data: [] as number[]
+ },
+ {
+ color: '#65e026',
+ name: '鍖呰鏈轰骇閲�(绠�)',
+ type: 'line',
+ smooth: true,
+ yAxisIndex: 0,
+ areaStyle: {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ {
+ offset: 0.25,
+ color: '#65e026'
+ },
+ {
+ offset: 1,
+ color: '#fff'
+ }
+ ]
+ }
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ data: [] as number[]
+ },
+ {
+ color: '#ff9d8e',
+ name: '婊ゆ娑堣��(涓囨敮)',
+ type: 'line',
+ smooth: true,
+ yAxisIndex: 1,
+ areaStyle: {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ {
+ offset: 0.25,
+ color: '#ff9d8e'
+ },
+ {
+ offset: 1,
+ color: '#fff'
+ }
+ ]
+ }
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ data: [] as number[]
+ },
+ {
+ color: '#ffa600',
+ name: '婊ゆ鍗曡��(涓囨敮/绠�)',
+ type: 'line',
+ smooth: true,
+ yAxisIndex: 0,
+ areaStyle: {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ {
+ offset: 0.25,
+ color: '#ffa600'
+ },
+ {
+ offset: 1,
+ color: '#fff'
+ }
+ ]
+ }
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ data: [] as number[]
+ }
+ ]
+}));
+
+watch(
+ () => props.data,
+ (newData) => {
+ if (!newData) return;
+
+ // Process data
+ // Assuming 'time' is the x-axis and 'qty' is the y-axis
+ // Sort by time just in case
+ sortedData.value = [...newData].sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime());
+
+ const times = sortedData.value.map(item => item.time);
+ const qtys = sortedData.value.map(item => {
+ const v = Number(item.qty ?? 0) / 50;
+ return Math.round(v * 10) / 10;
+ });
+ const tsQtys = sortedData.value.map(item => {
+ const v = Number(item.tsQty ?? 0) / 250;
+ return Math.round(v * 10) / 10;
+ });
+ const packerQtys = sortedData.value.map(item => {
+ // Assuming packerQty is in same unit as tsQty or similar
+ const v = Number(item.packerQty ?? 0)/ 10 / 250;
+ return Math.round(v * 10) / 10;
+ });
+ const lvbangVals = sortedData.value.map(item => {
+ const v = Number(item.lvbangVal ?? 0) / 10000;
+ return Math.round(v * 10) / 10;
+ });
+ const danhaoVals = sortedData.value.map(item => {
+ const qtyBox = Number(item.qty ?? 0) / 50;
+ if (!qtyBox) return 0;
+ const v = (Number(item.lvbangVal ?? 0) / 10000) / qtyBox;
+ return Math.round(v * 10) / 10;
+ });
+
+ updateOptions(opts => {
+ opts.xAxis.data = times;
+ opts.series[0].data = qtys;
+ opts.series[1].data = tsQtys;
+ opts.series[2].data = packerQtys;
+ opts.series[3].data = lvbangVals;
+ opts.series[4].data = danhaoVals;
+ return opts;
+ });
+ },
+ { deep: true, immediate: true }
+);
+</script>
+
+<template>
+ <div ref="domRef" class="h-full w-full overflow-hidden"></div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/analy/output-analy/modules/roller-data-operate-drawer.vue b/ruoyi-plus-soybean/src/views/analy/output-analy/modules/roller-data-operate-drawer.vue
new file mode 100755
index 0000000..2b0ad65
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/analy/output-analy/modules/roller-data-operate-drawer.vue
@@ -0,0 +1,213 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '~/packages/utils';
+import { fetchCreateRollerData, fetchUpdateRollerData } from '@/service/api/analy/roller-data';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'RollerDataOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.Qa.RollerData | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: '鏂板鍗锋帴鏈哄垎鏋�',
+ edit: '缂栬緫鍗锋帴鏈哄垎鏋�'
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.Qa.RollerDataOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ time: null,
+ key: '',
+ online: null,
+ qty: null,
+ badQty: null,
+ lvbangVal: null,
+ juanyanzhiVal: null,
+ shuisongzhiVal: null,
+ runTime: null,
+ stopTime: null,
+ stopTimes: null,
+ speed: null,
+ runStatus: null,
+ cy: null,
+ cyCs: null,
+ cyOnline: '',
+ recQty1: null,
+ recQty2: null,
+ tsQty: null
+ };
+}
+
+type RuleKey = Extract<
+ keyof Model,
+ | 'time'
+ | 'key'
+>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ time: createRequiredRule('鏃堕棿涓嶈兘涓虹┖'),
+ key: createRequiredRule('key涓嶈兘涓虹┖'),
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { time, key, online, qty, badQty, lvbangVal, juanyanzhiVal, shuisongzhiVal, runTime, stopTime, stopTimes, speed, runStatus, cy, cyCs, cyOnline, recQty1, recQty2, tsQty } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateRollerData({ time, key, online, qty, badQty, lvbangVal, juanyanzhiVal, shuisongzhiVal, runTime, stopTime, stopTimes, speed, runStatus, cy, cyCs, cyOnline, recQty1, recQty2, tsQty });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateRollerData({ time, key, online, qty, badQty, lvbangVal, juanyanzhiVal, shuisongzhiVal, runTime, stopTime, stopTimes, speed, runStatus, cy, cyCs, cyOnline, recQty1, recQty2, tsQty });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem label="鏃堕棿" path="time">
+ <NDatePicker
+ v-model:formatted-value="model.time"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ />
+ </NFormItem>
+ <NFormItem label="key" path="key">
+ <NInput
+ v-model:value="model.key"
+ :rows="3"
+ type="textarea"
+ placeholder="璇疯緭鍏ey"
+ />
+ </NFormItem>
+ <NFormItem label="缃戠粶鐘舵��" path="online">
+ <NInputNumber v-model:value="model.online" placeholder="璇疯緭鍏ョ綉缁滅姸鎬�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="浜ч噺" path="qty">
+ <NInputNumber v-model:value="model.qty" placeholder="璇疯緭鍏ヤ骇閲�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鎻愬崌鏈轰骇閲�" path="tsQty">
+ <NInputNumber v-model:value="model.tsQty" placeholder="璇疯緭鍏ユ彁鍗囨満浜ч噺" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鍓旈櫎浜ч噺" path="badQty">
+ <NInputNumber v-model:value="model.badQty" placeholder="璇疯緭鍏ュ墧闄や骇閲�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="婊ゆ娑堣��" path="lvbangVal">
+ <NInputNumber v-model:value="model.lvbangVal" placeholder="璇疯緭鍏ユ护妫掓秷鑰�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鍗风儫绾告秷鑰�" path="juanyanzhiVal">
+ <NInputNumber v-model:value="model.juanyanzhiVal" placeholder="璇疯緭鍏ュ嵎鐑熺焊娑堣��" class="w-full" />
+ </NFormItem>
+ <NFormItem label="姘存澗绾告秷鑰�" path="shuisongzhiVal">
+ <NInputNumber v-model:value="model.shuisongzhiVal" placeholder="璇疯緭鍏ユ按鏉剧焊娑堣��" class="w-full" />
+ </NFormItem>
+ <NFormItem label="杩愯鏃堕棿" path="runTime">
+ <NInputNumber v-model:value="model.runTime" placeholder="璇疯緭鍏ヨ繍琛屾椂闂�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鍋滄満鏃堕棿" path="stopTime">
+ <NInputNumber v-model:value="model.stopTime" placeholder="璇疯緭鍏ュ仠鏈烘椂闂�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鍋滄満娆℃暟" path="stopTimes">
+ <NInputNumber v-model:value="model.stopTimes" placeholder="璇疯緭鍏ュ仠鏈烘鏁�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="杞﹂��" path="speed">
+ <NInputNumber v-model:value="model.speed" placeholder="璇疯緭鍏ヨ溅閫�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="杩愯鐘舵��" path="runStatus">
+ <NRadioGroup v-model:value="model.runStatus">
+ <NSpace>
+ <NRadio :value="0" label="璇烽�夋嫨瀛楀吀鐢熸垚" />
+ </NSpace>
+ </NRadioGroup>
+ </NFormItem>
+ <NFormItem label="鍌ㄧ儫璁惧鍌ㄩ噺" path="cy">
+ <NInputNumber v-model:value="model.cy" placeholder="璇疯緭鍏ュ偍鐑熻澶囧偍閲�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鍌ㄧ儫璁惧杞﹂��" path="cyCs">
+ <NInputNumber v-model:value="model.cyCs" placeholder="璇疯緭鍏ュ偍鐑熻澶囪溅閫�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鍌ㄧ儫璁惧缃戠粶" path="cyOnline">
+ <NInput
+ v-model:value="model.cyOnline"
+ :rows="3"
+ type="textarea"
+ placeholder="璇疯緭鍏ュ偍鐑熻澶囩綉缁�"
+ />
+ </NFormItem>
+ <NFormItem label="鎺ユ敹鏈轰骇閲�" path="recQty1">
+ <NInputNumber v-model:value="model.recQty1" placeholder="璇疯緭鍏ユ帴鏀舵満浜ч噺" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鎺ユ敹鏈轰骇閲�2" path="recQty2">
+ <NInputNumber v-model:value="model.recQty2" placeholder="璇疯緭鍏ユ帴鏀舵満浜ч噺2" class="w-full" />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/analy/output-analy/modules/roller-data-search.vue b/ruoyi-plus-soybean/src/views/analy/output-analy/modules/roller-data-search.vue
new file mode 100755
index 0000000..2686174
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/analy/output-analy/modules/roller-data-search.vue
@@ -0,0 +1,147 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import type { FormRules } from 'naive-ui';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'RollerDataSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Qa.RollerDataSearchParams>('model', { required: true });
+
+const rules: FormRules = {
+ equNo: {
+ required: true,
+ type: 'number',
+ message: '璇烽�夋嫨璁惧',
+ trigger: ['blur', 'change']
+ },
+ 'params.beginTime': {
+ required: true,
+ message: '璇烽�夋嫨鏃堕棿鍖洪棿',
+ trigger: ['blur', 'change']
+ }
+};
+
+const timeRange = ref<[string, string] | null>(null);
+
+// sync model params to timeRange
+watch(
+ () => model.value.params,
+ (val: Api.Qa.RollerDataSearchParams['params']) => {
+ if (val?.beginTime && val?.endTime) {
+ timeRange.value = [val.beginTime, val.endTime];
+ } else {
+ timeRange.value = null;
+ }
+ },
+ { immediate: true, deep: true }
+);
+
+const shiftOptions = [
+ { label: '鏃╃彮', value: 1 },
+ { label: '涓彮', value: 2 },
+ { label: '鏅氱彮', value: 3 }
+];
+
+const equNoOptions = Array.from({ length: 13 }, (_, i) => i + 1)
+ .filter(i => i !== 4)
+ .map(i => ({ label: `${i}#鍗锋帴鏈虹粍`, value: 100 + i }));
+
+function onTimeRangeUpdate(value: [string, string] | null) {
+ if (!model.value.params) {
+ model.value.params = {};
+ }
+ if (value && value.length === 2) {
+ [model.value.params.beginTime, model.value.params.endTime] = value;
+ } else {
+ model.value.params.beginTime = undefined;
+ model.value.params.endTime = undefined;
+ }
+}
+
+async function reset() {
+ await restoreValidation();
+ model.value.shift = null;
+ model.value.equNo = 101;
+
+ const now = new Date();
+ const todayStart = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} 00:00:00`;
+ const todayEnd = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} 23:59:59`;
+
+ timeRange.value = [todayStart, todayEnd];
+ onTimeRangeUpdate(timeRange.value);
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="roller-data-search">
+ <NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:8" label="鏃堕棿鍖洪棿" path="params.beginTime" class="pr-24px">
+ <NDatePicker
+ v-model:formatted-value="timeRange"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ @update:formatted-value="onTimeRangeUpdate"
+ :default-time="['00:00:00', '23:59:59']"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="璁惧" path="equNo" class="pr-24px">
+ <NSelect
+ v-model:value="model.equNo"
+ placeholder="璇烽�夋嫨璁惧鍙�"
+ :options="equNoOptions"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鐝" path="shift" class="pr-24px">
+ <NSelect
+ v-model:value="model.shift"
+ placeholder="璇烽�夋嫨鐝"
+ :options="shiftOptions"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/analy/packer/index.vue b/ruoyi-plus-soybean/src/views/analy/packer/index.vue
new file mode 100755
index 0000000..b030ded
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/analy/packer/index.vue
@@ -0,0 +1,344 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDeletePackerData, fetchGetPackerDataList } from '@/service/api/analy/packer-data';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import PackerDataOperateDrawer from './modules/packer-data-operate-drawer.vue';
+import PackerDataSearch from './modules/packer-data-search.vue';
+
+defineOptions({
+ name: 'PackerDataList'
+});
+
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.Qa.PackerDataSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ time: null,
+ key: null,
+ online: null,
+ qty: null,
+ badQty: null,
+ xiaohemoVal: null,
+ tiaohemoVal: null,
+ xiaohezhiVal: null,
+ tiaohezhiVal: null,
+ neichenzhiVal: null,
+ runTime: null,
+ stopTime: null,
+ stopTimes: null,
+ speed: null,
+ runStatus: null,
+ tsQty: null,
+ mainQty: null,
+ mainBadQty: null,
+ tbjQty: null,
+ tbjGdQty: null,
+ tbjBadQty: null,
+ pbjQty: null,
+ shift: null,
+ equNo: 201,
+ params: {
+ beginTime: `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate()).padStart(2, '0')} 00:00:00`,
+ endTime: `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate()).padStart(2, '0')} 23:59:59`
+ },
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetPackerDataList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'time',
+ title: '鏃堕棿',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'key',
+ title: 'key',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'online',
+ title: '缃戠粶鐘舵��(0寮傚父锛�1姝e父)',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'qty',
+ title: '浜ч噺',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'badQty',
+ title: '鍓旈櫎浜ч噺',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'xiaohemoVal',
+ title: '灏忕洅鑶滄秷鑰�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tiaohemoVal',
+ title: '鏉$洅鑶滄秷鑰�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'xiaohezhiVal',
+ title: '灏忕洅绾告秷鑰�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tiaohezhiVal',
+ title: '鏉$洅绾告秷鑰�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'neichenzhiVal',
+ title: '鍐呰‖绾告秷鑰�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'runTime',
+ title: '杩愯鏃堕棿',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'stopTime',
+ title: '鍋滄満鏃堕棿',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'stopTimes',
+ title: '鍋滄満娆℃暟',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'speed',
+ title: '杞﹂��',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'runStatus',
+ title: '杩愯鐘舵��(-1 鏂綉 0鍋滄 1浣庨�熻繍琛� 2姝e父杩愯)',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tsQty',
+ title: '鎻愬崌鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'mainQty',
+ title: '涓绘満浜ч噺锛堝皬鍖呮満锛�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'mainBadQty',
+ title: '涓绘満鍓旈櫎閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tbjQty',
+ title: '閫忓寘鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tbjGdQty',
+ title: '閫忓寘鏈哄墧闄ゅソ鍖�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tbjBadQty',
+ title: '閫忓寘鏈哄墧闄ゅ潖鍖�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'pbjQty',
+ title: '鎺掑寘鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'shift',
+ title: '鐝',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'equNo',
+ title: '璁惧',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('qa:packerData:edit') || !hasAuth('qa:packerData:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('qa:packerData:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.time)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('qa:packerData:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.time)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+});
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'time', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeletePackerData(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(time: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeletePackerData([time]);
+ if (error) return;
+ onDeleted();
+}
+
+function edit(time: CommonType.IdType) {
+ handleEdit(time);
+}
+
+function handleExport() {
+ download('/qa/packerData/export', searchParams.value, `鍖呰鏈哄垎鏋恄${new Date().getTime()}.xlsx`);
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <PackerDataSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard title="鍖呰鏈哄垎鏋愬垪琛�" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('qa:packerData:add')"
+ :show-delete="hasAuth('qa:packerData:remove')"
+ :show-export="hasAuth('qa:packerData:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ />
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.time"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <PackerDataOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getDataByPage"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/analy/packer/modules/packer-data-operate-drawer.vue b/ruoyi-plus-soybean/src/views/analy/packer/modules/packer-data-operate-drawer.vue
new file mode 100755
index 0000000..3696912
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/analy/packer/modules/packer-data-operate-drawer.vue
@@ -0,0 +1,240 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '~/packages/utils';
+import { fetchCreatePackerData, fetchUpdatePackerData } from '@/service/api/analy/packer-data';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'PackerDataOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.Qa.PackerData | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: '鏂板鍖呰鏈哄垎鏋�',
+ edit: '缂栬緫鍖呰鏈哄垎鏋�'
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.Qa.PackerDataOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ time: null,
+ key: '',
+ online: null,
+ qty: null,
+ badQty: null,
+ xiaohemoVal: null,
+ tiaohemoVal: null,
+ xiaohezhiVal: null,
+ tiaohezhiVal: null,
+ neichenzhiVal: null,
+ runTime: null,
+ stopTime: null,
+ stopTimes: null,
+ speed: null,
+ runStatus: null,
+ tsQty: null,
+ mainQty: null,
+ mainBadQty: null,
+ tbjQty: null,
+ tbjGdQty: null,
+ tbjBadQty: null,
+ pbjQty: null,
+ shift: null,
+ equNo: null
+ };
+}
+
+type RuleKey = Extract<
+ keyof Model,
+ | 'time'
+ | 'shift'
+ | 'equNo'
+>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ time: createRequiredRule('鏃堕棿涓嶈兘涓虹┖'),
+ shift: createRequiredRule('鐝涓嶈兘涓虹┖'),
+ equNo: createRequiredRule('璁惧涓嶈兘涓虹┖')
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { time, key, online, qty, badQty, xiaohemoVal, tiaohemoVal, xiaohezhiVal, tiaohezhiVal, neichenzhiVal, runTime, stopTime, stopTimes, speed, runStatus, tsQty, mainQty, mainBadQty, tbjQty, tbjGdQty, tbjBadQty, pbjQty, shift, equNo } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreatePackerData({ time, key, online, qty, badQty, xiaohemoVal, tiaohemoVal, xiaohezhiVal, tiaohezhiVal, neichenzhiVal, runTime, stopTime, stopTimes, speed, runStatus, tsQty, mainQty, mainBadQty, tbjQty, tbjGdQty, tbjBadQty, pbjQty, shift, equNo });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdatePackerData({ time, key, online, qty, badQty, xiaohemoVal, tiaohemoVal, xiaohezhiVal, tiaohezhiVal, neichenzhiVal, runTime, stopTime, stopTimes, speed, runStatus, tsQty, mainQty, mainBadQty, tbjQty, tbjGdQty, tbjBadQty, pbjQty, shift, equNo });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem label="鏃堕棿" path="time">
+ <NDatePicker
+ v-model:formatted-value="model.time"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ />
+ </NFormItem>
+ <NFormItem label="key" path="key">
+ <NInput
+ v-model:value="model.key"
+ :rows="3"
+ type="textarea"
+ placeholder="璇疯緭鍏ey"
+ />
+ </NFormItem>
+ <NFormItem label="缃戠粶鐘舵��(0寮傚父锛�1姝e父)" path="online">
+ <NInput v-model:value="model.online" placeholder="璇疯緭鍏ョ綉缁滅姸鎬�(0寮傚父锛�1姝e父)" />
+ </NFormItem>
+ <NFormItem label="浜ч噺" path="qty">
+ <NInput v-model:value="model.qty" placeholder="璇疯緭鍏ヤ骇閲�" />
+ </NFormItem>
+ <NFormItem label="鍓旈櫎浜ч噺" path="badQty">
+ <NInput v-model:value="model.badQty" placeholder="璇疯緭鍏ュ墧闄や骇閲�" />
+ </NFormItem>
+ <NFormItem label="灏忕洅鑶滄秷鑰�" path="xiaohemoVal">
+ <NInput v-model:value="model.xiaohemoVal" placeholder="璇疯緭鍏ュ皬鐩掕啘娑堣��" />
+ </NFormItem>
+ <NFormItem label="鏉$洅鑶滄秷鑰�" path="tiaohemoVal">
+ <NInput v-model:value="model.tiaohemoVal" placeholder="璇疯緭鍏ユ潯鐩掕啘娑堣��" />
+ </NFormItem>
+ <NFormItem label="灏忕洅绾告秷鑰�" path="xiaohezhiVal">
+ <NInput v-model:value="model.xiaohezhiVal" placeholder="璇疯緭鍏ュ皬鐩掔焊娑堣��" />
+ </NFormItem>
+ <NFormItem label="鏉$洅绾告秷鑰�" path="tiaohezhiVal">
+ <NInput v-model:value="model.tiaohezhiVal" placeholder="璇疯緭鍏ユ潯鐩掔焊娑堣��" />
+ </NFormItem>
+ <NFormItem label="鍐呰‖绾告秷鑰�" path="neichenzhiVal">
+ <NInput v-model:value="model.neichenzhiVal" placeholder="璇疯緭鍏ュ唴琛焊娑堣��" />
+ </NFormItem>
+ <NFormItem label="杩愯鏃堕棿" path="runTime">
+ <NInput v-model:value="model.runTime" placeholder="璇疯緭鍏ヨ繍琛屾椂闂�" />
+ </NFormItem>
+ <NFormItem label="鍋滄満鏃堕棿" path="stopTime">
+ <NInput v-model:value="model.stopTime" placeholder="璇疯緭鍏ュ仠鏈烘椂闂�" />
+ </NFormItem>
+ <NFormItem label="鍋滄満娆℃暟" path="stopTimes">
+ <NInput v-model:value="model.stopTimes" placeholder="璇疯緭鍏ュ仠鏈烘鏁�" />
+ </NFormItem>
+ <NFormItem label="杞﹂��" path="speed">
+ <NInput v-model:value="model.speed" placeholder="璇疯緭鍏ヨ溅閫�" />
+ </NFormItem>
+ <NFormItem label="杩愯鐘舵��(-1 鏂綉 0鍋滄 1浣庨�熻繍琛� 2姝e父杩愯)" path="runStatus">
+ <NRadioGroup v-model:value="model.runStatus">
+ <NSpace>
+ <NRadio value="0" label="璇烽�夋嫨瀛楀吀鐢熸垚" />
+ </NSpace>
+ </NRadioGroup>
+ </NFormItem>
+ <NFormItem label="鎻愬崌鏈轰骇閲�" path="tsQty">
+ <NInput v-model:value="model.tsQty" placeholder="璇疯緭鍏ユ彁鍗囨満浜ч噺" />
+ </NFormItem>
+ <NFormItem label="涓绘満浜ч噺锛堝皬鍖呮満锛�" path="mainQty">
+ <NInput v-model:value="model.mainQty" placeholder="璇疯緭鍏ヤ富鏈轰骇閲忥紙灏忓寘鏈猴級" />
+ </NFormItem>
+ <NFormItem label="涓绘満鍓旈櫎閲�" path="mainBadQty">
+ <NInput v-model:value="model.mainBadQty" placeholder="璇疯緭鍏ヤ富鏈哄墧闄ら噺" />
+ </NFormItem>
+ <NFormItem label="閫忓寘鏈轰骇閲�" path="tbjQty">
+ <NInput v-model:value="model.tbjQty" placeholder="璇疯緭鍏ラ�忓寘鏈轰骇閲�" />
+ </NFormItem>
+ <NFormItem label="閫忓寘鏈哄墧闄ゅソ鍖�" path="tbjGdQty">
+ <NInput v-model:value="model.tbjGdQty" placeholder="璇疯緭鍏ラ�忓寘鏈哄墧闄ゅソ鍖�" />
+ </NFormItem>
+ <NFormItem label="閫忓寘鏈哄墧闄ゅ潖鍖�" path="tbjBadQty">
+ <NInput v-model:value="model.tbjBadQty" placeholder="璇疯緭鍏ラ�忓寘鏈哄墧闄ゅ潖鍖�" />
+ </NFormItem>
+ <NFormItem label="鎺掑寘鏈轰骇閲�" path="pbjQty">
+ <NInput v-model:value="model.pbjQty" placeholder="璇疯緭鍏ユ帓鍖呮満浜ч噺" />
+ </NFormItem>
+ <NFormItem label="鐝" path="shift">
+ <NSelect
+ v-model:value="model.shift"
+ placeholder="璇烽�夋嫨鐝"
+ :options="[{ value: '0', label: '璇烽�夋嫨瀛楀吀鐢熸垚' }]"
+ clearable
+ />
+ </NFormItem>
+ <NFormItem label="璁惧" path="equNo">
+ <NSelect
+ v-model:value="model.equNo"
+ placeholder="璇烽�夋嫨璁惧"
+ :options="[{ value: '0', label: '璇烽�夋嫨瀛楀吀鐢熸垚' }]"
+ clearable
+ />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/analy/packer/modules/packer-data-search.vue b/ruoyi-plus-soybean/src/views/analy/packer/modules/packer-data-search.vue
new file mode 100755
index 0000000..ac1b06a
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/analy/packer/modules/packer-data-search.vue
@@ -0,0 +1,147 @@
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+import type { FormRules } from 'naive-ui';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'PackerDataSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Qa.PackerDataSearchParams>('model', { required: true });
+
+const rules: FormRules = {
+ equNo: {
+ required: true,
+ type: 'number',
+ message: '璇烽�夋嫨璁惧',
+ trigger: ['blur', 'change']
+ },
+ 'params.beginTime': {
+ required: true,
+ message: '璇烽�夋嫨鏃堕棿鍖洪棿',
+ trigger: ['blur', 'change']
+ }
+};
+
+const timeRange = ref<[string, string] | null>(null);
+
+// sync model params to timeRange
+watch(
+ () => model.value.params,
+ (val: Api.Qa.PackerDataSearchParams['params']) => {
+ if (val?.beginTime && val?.endTime) {
+ timeRange.value = [val.beginTime, val.endTime];
+ } else {
+ timeRange.value = null;
+ }
+ },
+ { immediate: true, deep: true }
+);
+
+const shiftOptions = [
+ { label: '鏃╃彮', value: 1 },
+ { label: '涓彮', value: 2 },
+ { label: '鏅氱彮', value: 3 }
+];
+
+const equNoOptions = Array.from({ length: 13 }, (_, i) => i + 1)
+ .filter(i => i !== 4)
+ .map(i => ({ label: `${i}#鍖呰鏈虹粍`, value: 200 + i }));
+
+function onTimeRangeUpdate(value: [string, string] | null) {
+ if (!model.value.params) {
+ model.value.params = {};
+ }
+ if (value && value.length === 2) {
+ [model.value.params.beginTime, model.value.params.endTime] = value;
+ } else {
+ model.value.params.beginTime = undefined;
+ model.value.params.endTime = undefined;
+ }
+}
+
+async function reset() {
+ await restoreValidation();
+ model.value.shift = null;
+ model.value.equNo = 201;
+
+ const now = new Date();
+ const todayStart = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} 00:00:00`;
+ const todayEnd = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} 23:59:59`;
+
+ timeRange.value = [todayStart, todayEnd];
+ onTimeRangeUpdate(timeRange.value);
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="qa-packer-data-search">
+ <NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:8" label="鏃堕棿鍖洪棿" path="params.beginTime" class="pr-24px">
+ <NDatePicker
+ v-model:formatted-value="timeRange"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ @update:formatted-value="onTimeRangeUpdate"
+ :default-time="['00:00:00', '23:59:59']"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="璁惧" path="equNo" class="pr-24px">
+ <NSelect
+ v-model:value="model.equNo"
+ placeholder="璇烽�夋嫨璁惧鍙�"
+ :options="equNoOptions"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鐝" path="shift" class="pr-24px">
+ <NSelect
+ v-model:value="model.shift"
+ placeholder="璇烽�夋嫨鐝"
+ :options="shiftOptions"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/analy/roller/index.vue b/ruoyi-plus-soybean/src/views/analy/roller/index.vue
new file mode 100755
index 0000000..faf1d54
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/analy/roller/index.vue
@@ -0,0 +1,304 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDeleteRollerData, fetchGetRollerDataList } from '@/service/api/analy/roller-data';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import RollerDataOperateDrawer from './modules/roller-data-operate-drawer.vue';
+import RollerDataSearch from './modules/roller-data-search.vue';
+
+defineOptions({
+ name: 'RollerDataList'
+});
+
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.Qa.RollerDataSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ time: null,
+ key: null,
+ online: null,
+ qty: null,
+ badQty: null,
+ lvbangVal: null,
+ juanyanzhiVal: null,
+ shuisongzhiVal: null,
+ runTime: null,
+ stopTime: null,
+ stopTimes: null,
+ speed: null,
+ runStatus: null,
+ cy: null,
+ cyCs: null,
+ cyOnline: null,
+ recQty1: null,
+ recQty2: null,
+ params: {
+ beginTime: `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate()).padStart(2, '0')} 00:00:00`,
+ endTime: `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate()).padStart(2, '0')} 23:59:59`
+ },
+ shift: null,
+ equNo: 101
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetRollerDataList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'time',
+ title: '鏃堕棿',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'key',
+ title: 'key',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'online',
+ title: '缃戠粶鐘舵��',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'qty',
+ title: '浜ч噺',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'badQty',
+ title: '鍓旈櫎浜ч噺',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'lvbangVal',
+ title: '婊ゆ娑堣��',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'juanyanzhiVal',
+ title: '鍗风儫绾告秷鑰�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'shuisongzhiVal',
+ title: '姘存澗绾告秷鑰�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'runTime',
+ title: '杩愯鏃堕棿',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'stopTime',
+ title: '鍋滄満鏃堕棿',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'stopTimes',
+ title: '鍋滄満娆℃暟',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'speed',
+ title: '杞﹂��',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'runStatus',
+ title: '杩愯鐘舵��',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'cy',
+ title: '鍌ㄧ儫璁惧鍌ㄩ噺',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'cyCs',
+ title: '鍌ㄧ儫璁惧杞﹂��',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'cyOnline',
+ title: '鍌ㄧ儫璁惧缃戠粶',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'recQty1',
+ title: '鎺ユ敹鏈轰骇閲�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'recQty2',
+ title: '鎺ユ敹鏈轰骇閲�2',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('qa:rollerData:edit') || !hasAuth('qa:rollerData:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('qa:rollerData:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.time)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('qa:rollerData:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.time)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+});
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'time', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteRollerData(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(time: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteRollerData([time]);
+ if (error) return;
+ onDeleted();
+}
+
+function edit(time: CommonType.IdType) {
+ handleEdit(time);
+}
+
+function handleExport() {
+ download('/qa/rollerData/export', searchParams.value, `鍗锋帴鏈哄垎鏋恄${new Date().getTime()}.xlsx`);
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <RollerDataSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard title="鍗锋帴鏈哄垎鏋愬垪琛�" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('qa:rollerData:add')"
+ :show-delete="hasAuth('qa:rollerData:remove')"
+ :show-export="hasAuth('qa:rollerData:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ />
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.time"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <RollerDataOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getDataByPage"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/analy/roller/modules/roller-data-operate-drawer.vue b/ruoyi-plus-soybean/src/views/analy/roller/modules/roller-data-operate-drawer.vue
new file mode 100755
index 0000000..3932afb
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/analy/roller/modules/roller-data-operate-drawer.vue
@@ -0,0 +1,209 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '~/packages/utils';
+import { fetchCreateRollerData, fetchUpdateRollerData } from '@/service/api/analy/roller-data';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'RollerDataOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.Qa.RollerData | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: '鏂板鍗锋帴鏈哄垎鏋�',
+ edit: '缂栬緫鍗锋帴鏈哄垎鏋�'
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.Qa.RollerDataOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ time: null,
+ key: '',
+ online: null,
+ qty: null,
+ badQty: null,
+ lvbangVal: null,
+ juanyanzhiVal: null,
+ shuisongzhiVal: null,
+ runTime: null,
+ stopTime: null,
+ stopTimes: null,
+ speed: null,
+ runStatus: null,
+ cy: null,
+ cyCs: null,
+ cyOnline: '',
+ recQty1: null,
+ recQty2: null
+ };
+}
+
+type RuleKey = Extract<
+ keyof Model,
+ | 'time'
+ | 'key'
+>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ time: createRequiredRule('鏃堕棿涓嶈兘涓虹┖'),
+ key: createRequiredRule('key涓嶈兘涓虹┖'),
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { time, key, online, qty, badQty, lvbangVal, juanyanzhiVal, shuisongzhiVal, runTime, stopTime, stopTimes, speed, runStatus, cy, cyCs, cyOnline, recQty1, recQty2 } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateRollerData({ time, key, online, qty, badQty, lvbangVal, juanyanzhiVal, shuisongzhiVal, runTime, stopTime, stopTimes, speed, runStatus, cy, cyCs, cyOnline, recQty1, recQty2 });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateRollerData({ time, key, online, qty, badQty, lvbangVal, juanyanzhiVal, shuisongzhiVal, runTime, stopTime, stopTimes, speed, runStatus, cy, cyCs, cyOnline, recQty1, recQty2 });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem label="鏃堕棿" path="time">
+ <NDatePicker
+ v-model:formatted-value="model.time"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ />
+ </NFormItem>
+ <NFormItem label="key" path="key">
+ <NInput
+ v-model:value="model.key"
+ :rows="3"
+ type="textarea"
+ placeholder="璇疯緭鍏ey"
+ />
+ </NFormItem>
+ <NFormItem label="缃戠粶鐘舵��" path="online">
+ <NInputNumber v-model:value="model.online" placeholder="璇疯緭鍏ョ綉缁滅姸鎬�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="浜ч噺" path="qty">
+ <NInputNumber v-model:value="model.qty" placeholder="璇疯緭鍏ヤ骇閲�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鍓旈櫎浜ч噺" path="badQty">
+ <NInputNumber v-model:value="model.badQty" placeholder="璇疯緭鍏ュ墧闄や骇閲�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="婊ゆ娑堣��" path="lvbangVal">
+ <NInputNumber v-model:value="model.lvbangVal" placeholder="璇疯緭鍏ユ护妫掓秷鑰�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鍗风儫绾告秷鑰�" path="juanyanzhiVal">
+ <NInputNumber v-model:value="model.juanyanzhiVal" placeholder="璇疯緭鍏ュ嵎鐑熺焊娑堣��" class="w-full" />
+ </NFormItem>
+ <NFormItem label="姘存澗绾告秷鑰�" path="shuisongzhiVal">
+ <NInputNumber v-model:value="model.shuisongzhiVal" placeholder="璇疯緭鍏ユ按鏉剧焊娑堣��" class="w-full" />
+ </NFormItem>
+ <NFormItem label="杩愯鏃堕棿" path="runTime">
+ <NInputNumber v-model:value="model.runTime" placeholder="璇疯緭鍏ヨ繍琛屾椂闂�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鍋滄満鏃堕棿" path="stopTime">
+ <NInputNumber v-model:value="model.stopTime" placeholder="璇疯緭鍏ュ仠鏈烘椂闂�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鍋滄満娆℃暟" path="stopTimes">
+ <NInputNumber v-model:value="model.stopTimes" placeholder="璇疯緭鍏ュ仠鏈烘鏁�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="杞﹂��" path="speed">
+ <NInputNumber v-model:value="model.speed" placeholder="璇疯緭鍏ヨ溅閫�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="杩愯鐘舵��" path="runStatus">
+ <NRadioGroup v-model:value="model.runStatus">
+ <NSpace>
+ <NRadio value="0" label="璇烽�夋嫨瀛楀吀鐢熸垚" />
+ </NSpace>
+ </NRadioGroup>
+ </NFormItem>
+ <NFormItem label="鍌ㄧ儫璁惧鍌ㄩ噺" path="cy">
+ <NInputNumber v-model:value="model.cy" placeholder="璇疯緭鍏ュ偍鐑熻澶囧偍閲�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鍌ㄧ儫璁惧杞﹂��" path="cyCs">
+ <NInputNumber v-model:value="model.cyCs" placeholder="璇疯緭鍏ュ偍鐑熻澶囪溅閫�" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鍌ㄧ儫璁惧缃戠粶" path="cyOnline">
+ <NInput
+ v-model:value="model.cyOnline"
+ :rows="3"
+ type="textarea"
+ placeholder="璇疯緭鍏ュ偍鐑熻澶囩綉缁�"
+ />
+ </NFormItem>
+ <NFormItem label="鎺ユ敹鏈轰骇閲�" path="recQty1">
+ <NInputNumber v-model:value="model.recQty1" placeholder="璇疯緭鍏ユ帴鏀舵満浜ч噺" class="w-full" />
+ </NFormItem>
+ <NFormItem label="鎺ユ敹鏈轰骇閲�2" path="recQty2">
+ <NInputNumber v-model:value="model.recQty2" placeholder="璇疯緭鍏ユ帴鏀舵満浜ч噺2" class="w-full" />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/analy/roller/modules/roller-data-search.vue b/ruoyi-plus-soybean/src/views/analy/roller/modules/roller-data-search.vue
new file mode 100755
index 0000000..88b6f8d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/analy/roller/modules/roller-data-search.vue
@@ -0,0 +1,147 @@
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+import type { FormRules } from 'naive-ui';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'RollerDataSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Qa.RollerDataSearchParams>('model', { required: true });
+
+const rules: FormRules = {
+ equNo: {
+ required: true,
+ type: 'number',
+ message: '璇烽�夋嫨璁惧',
+ trigger: ['blur', 'change']
+ },
+ 'params.beginTime': {
+ required: true,
+ message: '璇烽�夋嫨鏃堕棿鍖洪棿',
+ trigger: ['blur', 'change']
+ }
+};
+
+const timeRange = ref<[string, string] | null>(null);
+
+// sync model params to timeRange
+watch(
+ () => model.value.params,
+ (val: Api.Qa.RollerDataSearchParams['params']) => {
+ if (val?.beginTime && val?.endTime) {
+ timeRange.value = [val.beginTime, val.endTime];
+ } else {
+ timeRange.value = null;
+ }
+ },
+ { immediate: true, deep: true }
+);
+
+const shiftOptions = [
+ { label: '鏃╃彮', value: 1 },
+ { label: '涓彮', value: 2 },
+ { label: '鏅氱彮', value: 3 }
+];
+
+const equNoOptions = Array.from({ length: 13 }, (_, i) => i + 1)
+ .filter(i => i !== 4)
+ .map(i => ({ label: `${i}#鍗锋帴鏈虹粍`, value: 100 + i }));
+
+function onTimeRangeUpdate(value: [string, string] | null) {
+ if (!model.value.params) {
+ model.value.params = {};
+ }
+ if (value && value.length === 2) {
+ [model.value.params.beginTime, model.value.params.endTime] = value;
+ } else {
+ model.value.params.beginTime = undefined;
+ model.value.params.endTime = undefined;
+ }
+}
+
+async function reset() {
+ await restoreValidation();
+ model.value.shift = null;
+ model.value.equNo = 101;
+
+ const now = new Date();
+ const todayStart = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} 00:00:00`;
+ const todayEnd = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} 23:59:59`;
+
+ timeRange.value = [todayStart, todayEnd];
+ onTimeRangeUpdate(timeRange.value);
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="roller-data-search">
+ <NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:8" label="鏃堕棿鍖洪棿" path="params.beginTime" class="pr-24px">
+ <NDatePicker
+ v-model:formatted-value="timeRange"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ @update:formatted-value="onTimeRangeUpdate"
+ :default-time="['00:00:00', '23:59:59']"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="璁惧" path="equNo" class="pr-24px">
+ <NSelect
+ v-model:value="model.equNo"
+ placeholder="璇烽�夋嫨璁惧鍙�"
+ :options="equNoOptions"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鐝" path="shift" class="pr-24px">
+ <NSelect
+ v-model:value="model.shift"
+ placeholder="璇烽�夋嫨鐝"
+ :options="shiftOptions"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/demo/demo/index.vue b/ruoyi-plus-soybean/src/views/demo/demo/index.vue
new file mode 100755
index 0000000..c40c69e
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/demo/demo/index.vue
@@ -0,0 +1,225 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import type { DataTableSortState } from 'naive-ui';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDeleteDemo, fetchGetDemoList } from '@/service/api/demo/demo';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import DemoOperateDrawer from './modules/demo-operate-drawer.vue';
+import DemoSearch from './modules/demo-search.vue';
+
+defineOptions({
+ name: 'DemoList'
+});
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.Demo.DemoSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ deptId: null,
+ userId: null,
+ testKey: null,
+ value: null,
+ orderByColumn: null,
+ isAsc: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetDemoList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'deptId',
+ title: '閮ㄩ棬 ID',
+ align: 'center',
+ minWidth: 120,
+ sorter: true
+ },
+ {
+ key: 'userId',
+ title: '鐢ㄦ埛 ID',
+ align: 'center',
+ minWidth: 120,
+ sorter: true
+ },
+ {
+ key: 'orderNum',
+ title: '鎺掑簭鍙�',
+ align: 'center',
+ minWidth: 120,
+ sorter: true
+ },
+ {
+ key: 'testKey',
+ title: 'Key 閿�',
+ align: 'center',
+ minWidth: 120,
+ sorter: true
+ },
+ {
+ key: 'value',
+ title: '鍊�',
+ align: 'center',
+ minWidth: 120,
+ sorter: true
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('demo:demo:edit') || !hasAuth('demo:demo:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('demo:demo:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.id)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('demo:demo:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.id)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'id', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteDemo(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteDemo([id]);
+ if (error) return;
+ onDeleted();
+}
+
+function edit(id: CommonType.IdType) {
+ handleEdit(id);
+}
+
+function handleExport() {
+ download('/demo/demo/export', searchParams.value, `娴嬭瘯鍗曡〃_${new Date().getTime()}.xlsx`);
+}
+
+function handleUpdateSorter(sorters: DataTableSortState) {
+ if (!sorters.order) {
+ searchParams.value.orderByColumn = null;
+ searchParams.value.isAsc = null;
+ } else {
+ searchParams.value.orderByColumn = sorters.columnKey as keyof Api.System.Oss;
+ searchParams.value.isAsc = sorters.order === 'ascend' ? 'asc' : 'desc';
+ }
+ getDataByPage();
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <DemoSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard title="娴嬭瘯鍗曡〃鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('demo:demo:add')"
+ :show-delete="hasAuth('demo:demo:remove')"
+ :show-export="hasAuth('demo:demo:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ />
+ </template>
+ <DataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.id"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ @update:sorter="handleUpdateSorter"
+ />
+ <DemoOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getDataByPage"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/demo/demo/modules/demo-operate-drawer.vue b/ruoyi-plus-soybean/src/views/demo/demo/modules/demo-operate-drawer.vue
new file mode 100755
index 0000000..40ef9c5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/demo/demo/modules/demo-operate-drawer.vue
@@ -0,0 +1,140 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateDemo, fetchUpdateDemo } from '@/service/api/demo/demo';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'DemoOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.Demo.Demo | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: '鏂板娴嬭瘯鍗曡〃',
+ edit: '缂栬緫娴嬭瘯鍗曡〃'
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.Demo.DemoOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ id: null,
+ deptId: null,
+ userId: null,
+ orderNum: 0,
+ testKey: '',
+ value: '',
+ remark: ''
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'id' | 'deptId' | 'userId' | 'orderNum' | 'testKey' | 'value'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ id: createRequiredRule('涓婚敭涓嶈兘涓虹┖'),
+ deptId: createRequiredRule('閮ㄩ棬涓嶈兘涓虹┖'),
+ userId: createRequiredRule('鐢ㄦ埛涓嶈兘涓虹┖'),
+ orderNum: createRequiredRule('鎺掑簭鍙蜂笉鑳戒负绌�'),
+ testKey: createRequiredRule('key 閿笉鑳戒负绌�'),
+ value: createRequiredRule('鍊间笉鑳戒负绌�')
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { id, deptId, userId, orderNum, testKey, value } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateDemo({ deptId, userId, orderNum, testKey, value });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateDemo({ id, deptId, userId, orderNum, testKey, value });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem label="閮ㄩ棬" path="deptId">
+ <DeptTreeSelect v-model:value="model.deptId" placeholder="璇烽�夋嫨閮ㄩ棬" />
+ </NFormItem>
+ <NFormItem label="鐢ㄦ埛" path="userId">
+ <UserSelect v-model:value="model.userId" placeholder="璇烽�夋嫨鐢ㄦ埛" />
+ </NFormItem>
+ <NFormItem label="鎺掑簭鍙�" path="orderNum">
+ <NInputNumber v-model:value="model.orderNum" placeholder="璇疯緭鍏ユ帓搴忓彿" />
+ </NFormItem>
+ <NFormItem label="key 閿�" path="testKey">
+ <NInput v-model:value="model.testKey" placeholder="璇疯緭鍏� key 閿�" />
+ </NFormItem>
+ <NFormItem label="鍊�" path="value">
+ <NInput v-model:value="model.value" placeholder="璇疯緭鍏ュ��" />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/demo/demo/modules/demo-search.vue b/ruoyi-plus-soybean/src/views/demo/demo/modules/demo-search.vue
new file mode 100755
index 0000000..dfd0d9c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/demo/demo/modules/demo-search.vue
@@ -0,0 +1,80 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'DemoSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Demo.DemoSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="demo-demo-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:6" label="閮ㄩ棬 ID" label-width="auto" path="deptId" class="pr-24px">
+ <DeptTreeSelect v-model:value="model.deptId" placeholder="璇烽�夋嫨閮ㄩ棬" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鐢ㄦ埛 ID" label-width="auto" path="userId" class="pr-24px">
+ <UserSelect v-model:value="model.userId" placeholder="璇烽�夋嫨鐢ㄦ埛" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="Key 閿�" label-width="auto" path="testKey" class="pr-24px">
+ <NInput v-model:value="model.testKey" placeholder="璇疯緭鍏ey 閿�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鍊�" label-width="auto" path="value" class="pr-24px">
+ <NInput v-model:value="model.value" placeholder="璇疯緭鍏ュ��" />
+ </NFormItemGi>
+ <NFormItemGi :show-feedback="false" span="24" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/demo/tree/index.vue b/ruoyi-plus-soybean/src/views/demo/tree/index.vue
new file mode 100755
index 0000000..0a4cafd
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/demo/tree/index.vue
@@ -0,0 +1,233 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import { fetchBatchDeleteTree, fetchGetTreeList } from '@/service/api/demo/tree';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { treeTransform, useNaiveTreeTable, useTableOperate } from '@/hooks/common/table';
+import { useDownload } from '@/hooks/business/download';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import TreeOperateDrawer from './modules/tree-operate-drawer.vue';
+import TreeSearch from './modules/tree-search.vue';
+
+defineOptions({
+ name: 'TreeList'
+});
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.Demo.TreeSearchParams>({
+ parentId: null,
+ deptId: null,
+ userId: null,
+ treeName: null,
+ params: {}
+});
+
+const {
+ columns,
+ columnChecks,
+ data,
+ rows,
+ getData,
+ loading,
+ expandedRowKeys,
+ isCollapse,
+ expandAll,
+ collapseAll,
+ scrollX
+} = useNaiveTreeTable({
+ keyField: 'id',
+ api: () => fetchGetTreeList(searchParams.value),
+ transform: response => treeTransform(response),
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'id',
+ title: '涓婚敭',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'parentId',
+ title: '鐖� ID',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'deptId',
+ title: '閮ㄩ棬 ID',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'userId',
+ title: '鐢ㄦ埛 ID',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'treeName',
+ title: '鏍戣妭鐐瑰悕',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 150,
+ render: row => {
+ const addBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:add-2-rounded"
+ tooltipContent={$t('common.add')}
+ onClick={() => addInRow(row)}
+ />
+ );
+ };
+
+ const editBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.id)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.id)}
+ />
+ );
+ };
+
+ const buttons = [];
+ if (hasAuth('demo:tree:add')) buttons.push(addBtn());
+ if (hasAuth('demo:tree:edit')) buttons.push(editBtn());
+ if (hasAuth('demo:tree:remove')) buttons.push(deleteBtn());
+
+ return (
+ <div class="flex-center gap-8px">
+ {buttons.map((btn, index) => (
+ <>
+ {index !== 0 && <NDivider vertical />}
+ {btn}
+ </>
+ ))}
+ </div>
+ );
+ }
+ }
+ ]
+});
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(rows, 'id', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteTree(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteTree([id]);
+ if (error) return;
+ onDeleted();
+}
+
+function edit(id: CommonType.IdType) {
+ handleEdit(id);
+}
+
+function addInRow(row: Api.Demo.Tree) {
+ editingData.value = jsonClone(row);
+ handleAdd();
+}
+
+function handleExport() {
+ download('/demo/tree/export', searchParams.value, `娴嬭瘯鏍戣〃_${new Date().getTime()}.xlsx`);
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <TreeSearch v-model:model="searchParams" :tree-list="data" @search="getData" />
+ <NCard title="娴嬭瘯鏍戣〃鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('demo:tree:add')"
+ :show-delete="hasAuth('demo:tree:remove')"
+ :show-export="false"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ >
+ <template #prefix>
+ <NButton v-if="!isCollapse" :disabled="!data.length" size="small" @click="expandAll">
+ <template #icon>
+ <icon-quill-expand />
+ </template>
+ 鍏ㄩ儴灞曞紑
+ </NButton>
+ <NButton v-if="isCollapse" :disabled="!data.length" size="small" @click="collapseAll">
+ <template #icon>
+ <icon-quill-collapse />
+ </template>
+ 鍏ㄩ儴鏀惰捣
+ </NButton>
+ </template>
+ </TableHeaderOperation>
+ </template>
+ <DataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ v-model:expanded-row-keys="expandedRowKeys"
+ :columns="columns"
+ :data="data"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.id"
+ class="sm:h-full"
+ />
+ <TreeOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ :tree-list="data"
+ @submitted="getData"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/demo/tree/modules/tree-operate-drawer.vue b/ruoyi-plus-soybean/src/views/demo/tree/modules/tree-operate-drawer.vue
new file mode 100755
index 0000000..17939e9
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/demo/tree/modules/tree-operate-drawer.vue
@@ -0,0 +1,166 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateTree, fetchGetTreeList, fetchUpdateTree } from '@/service/api/demo/tree';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { handleTree } from '@/utils/common';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'TreeOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.Demo.Tree | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const treeList = ref<Api.Demo.Tree[]>([]);
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: '鏂板娴嬭瘯鏍戣〃',
+ edit: '缂栬緫娴嬭瘯鏍戣〃'
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.Demo.TreeOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ id: null,
+ parentId: null,
+ deptId: null,
+ userId: null,
+ treeName: ''
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'id' | 'parentId' | 'deptId' | 'userId' | 'treeName'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ id: createRequiredRule('涓婚敭涓嶈兘涓虹┖'),
+ parentId: createRequiredRule('鐖� ID 涓嶈兘涓虹┖'),
+ deptId: createRequiredRule('閮ㄩ棬涓嶈兘涓虹┖'),
+ userId: createRequiredRule('鐢ㄦ埛涓嶈兘涓虹┖'),
+ treeName: createRequiredRule('鏍戣妭鐐瑰悕涓嶈兘涓虹┖')
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+ model.value.parentId = props.rowData?.id || 0;
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { id, parentId, deptId, userId, treeName } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateTree({ parentId, deptId, userId, treeName });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateTree({ id, parentId, deptId, userId, treeName });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+async function getTreeList() {
+ const { data, error } = await fetchGetTreeList();
+ if (error) {
+ return;
+ }
+ const { tree } = handleTree(data);
+ treeList.value = tree;
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ getTreeList();
+ }
+});
+
+const treeOptions = computed(() => {
+ return [
+ {
+ id: 0,
+ treeName: '椤剁骇鑺傜偣',
+ children: treeList.value
+ }
+ ];
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem label="鐖� ID" path="parentId">
+ <NTreeSelect
+ v-model:value="model.parentId"
+ filterable
+ class="h-full"
+ key-field="id"
+ label-field="treeName"
+ :options="treeOptions"
+ :default-expanded-keys="[0]"
+ />
+ </NFormItem>
+ <NFormItem label="閮ㄩ棬" path="deptId">
+ <DeptTreeSelect v-model:value="model.deptId" placeholder="璇烽�夋嫨閮ㄩ棬" />
+ </NFormItem>
+ <NFormItem label="鐢ㄦ埛" path="userId">
+ <UserSelect v-model:value="model.userId" placeholder="璇烽�夋嫨鐢ㄦ埛" />
+ </NFormItem>
+ <NFormItem label="鏍戣妭鐐瑰悕" path="treeName">
+ <NInput v-model:value="model.treeName" placeholder="璇疯緭鍏ュ��" />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/demo/tree/modules/tree-search.vue b/ruoyi-plus-soybean/src/views/demo/tree/modules/tree-search.vue
new file mode 100755
index 0000000..22b4c54
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/demo/tree/modules/tree-search.vue
@@ -0,0 +1,77 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'TreeSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Demo.TreeSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="demo-tree-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:6" label="鏍戣妭鐐瑰悕" label-width="auto" path="treeName" class="pr-24px">
+ <NInput v-model:value="model.treeName" placeholder="璇疯緭鍏ユ爲鑺傜偣鍚�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="閮ㄩ棬" label-width="auto" path="deptId" class="pr-24px">
+ <DeptTreeSelect v-model:value="model.deptId" placeholder="璇烽�夋嫨閮ㄩ棬" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鐢ㄦ埛" label-width="auto" path="userId" class="pr-24px">
+ <UserSelect v-model:value="model.userId" placeholder="璇烽�夋嫨鐢ㄦ埛" />
+ </NFormItemGi>
+ <NFormItemGi :show-feedback="false" span="24" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/home/index.vue b/ruoyi-plus-soybean/src/views/home/index.vue
new file mode 100755
index 0000000..bd69cf5
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/home/index.vue
@@ -0,0 +1,43 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { useAppStore } from '@/store/modules/app';
+import HeaderBanner from './modules/header-banner.vue';
+import CardData from './modules/card-data.vue';
+import LineChart from './modules/line-chart.vue';
+import PieChart from './modules/pie-chart.vue';
+import ProjectNews from './modules/project-news.vue';
+import CreativityBanner from './modules/creativity-banner.vue';
+
+const appStore = useAppStore();
+
+const gap = computed(() => (appStore.isMobile ? 0 : 16));
+</script>
+
+<template>
+ <NSpace vertical :size="16">
+ <HeaderBanner />
+ <CardData />
+ <NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
+ <NGi span="24 s:24 m:14">
+ <NCard :bordered="false" class="card-wrapper">
+ <LineChart />
+ </NCard>
+ </NGi>
+ <NGi span="24 s:24 m:10">
+ <NCard :bordered="false" class="card-wrapper">
+ <PieChart />
+ </NCard>
+ </NGi>
+ </NGrid>
+ <NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
+ <NGi span="24 s:24 m:14">
+ <ProjectNews />
+ </NGi>
+ <NGi span="24 s:24 m:10">
+ <CreativityBanner />
+ </NGi>
+ </NGrid>
+ </NSpace>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/home/modules/card-data.vue b/ruoyi-plus-soybean/src/views/home/modules/card-data.vue
new file mode 100755
index 0000000..f1ff6d6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/home/modules/card-data.vue
@@ -0,0 +1,115 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { createReusableTemplate } from '@vueuse/core';
+import { useThemeStore } from '@/store/modules/theme';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'CardData'
+});
+
+interface CardData {
+ key: string;
+ title: string;
+ value: number;
+ unit: string;
+ color: {
+ start: string;
+ end: string;
+ };
+ icon: string;
+}
+
+const cardData = computed<CardData[]>(() => [
+ {
+ key: 'visitCount',
+ title: $t('page.home.visitCount'),
+ value: 9725,
+ unit: '',
+ color: {
+ start: '#ec4786',
+ end: '#b955a4'
+ },
+ icon: 'ant-design:bar-chart-outlined'
+ },
+ {
+ key: 'turnover',
+ title: $t('page.home.turnover'),
+ value: 1026,
+ unit: '$',
+ color: {
+ start: '#865ec0',
+ end: '#5144b4'
+ },
+ icon: 'ant-design:money-collect-outlined'
+ },
+ {
+ key: 'downloadCount',
+ title: $t('page.home.downloadCount'),
+ value: 970925,
+ unit: '',
+ color: {
+ start: '#56cdf3',
+ end: '#719de3'
+ },
+ icon: 'carbon:document-download'
+ },
+ {
+ key: 'dealCount',
+ title: $t('page.home.dealCount'),
+ value: 9527,
+ unit: '',
+ color: {
+ start: '#fcbc25',
+ end: '#f68057'
+ },
+ icon: 'ant-design:trademark-circle-outlined'
+ }
+]);
+
+interface GradientBgProps {
+ gradientColor: string;
+}
+
+const [DefineGradientBg, GradientBg] = createReusableTemplate<GradientBgProps>();
+
+const themeStore = useThemeStore();
+
+function getGradientColor(color: CardData['color']) {
+ return `linear-gradient(to bottom right, ${color.start}, ${color.end})`;
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <!-- define component start: GradientBg -->
+ <DefineGradientBg v-slot="{ $slots, gradientColor }">
+ <div
+ class="px-16px pb-4px pt-8px text-white"
+ :style="{ backgroundImage: gradientColor, borderRadius: themeStore.themeRadius + 'px' }"
+ >
+ <component :is="$slots.default" />
+ </div>
+ </DefineGradientBg>
+ <!-- define component end: GradientBg -->
+
+ <NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
+ <NGi v-for="item in cardData" :key="item.key">
+ <GradientBg :gradient-color="getGradientColor(item.color)" class="flex-1">
+ <h3 class="text-16px">{{ item.title }}</h3>
+ <div class="flex justify-between pt-12px">
+ <SvgIcon :icon="item.icon" class="text-32px" />
+ <CountTo
+ :prefix="item.unit"
+ :start-value="1"
+ :end-value="item.value"
+ class="text-30px text-white dark:text-dark"
+ />
+ </div>
+ </GradientBg>
+ </NGi>
+ </NGrid>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/home/modules/creativity-banner.vue b/ruoyi-plus-soybean/src/views/home/modules/creativity-banner.vue
new file mode 100755
index 0000000..c461e46
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/home/modules/creativity-banner.vue
@@ -0,0 +1,17 @@
+<script setup lang="ts">
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'CreativityBanner'
+});
+</script>
+
+<template>
+ <NCard :title="$t('page.home.creativity')" :bordered="false" size="small" class="h-full card-wrapper">
+ <div class="h-full flex-center">
+ <icon-local-banner class="text-400px text-primary sm:text-320px" />
+ </div>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/home/modules/header-banner.vue b/ruoyi-plus-soybean/src/views/home/modules/header-banner.vue
new file mode 100755
index 0000000..f784f89
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/home/modules/header-banner.vue
@@ -0,0 +1,71 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { useAppStore } from '@/store/modules/app';
+import { useAuthStore } from '@/store/modules/auth';
+import defaultAvatar from '@/assets/imgs/soybean.jpg';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'HeaderBanner'
+});
+
+const appStore = useAppStore();
+const authStore = useAuthStore();
+
+const gap = computed(() => (appStore.isMobile ? 0 : 16));
+
+interface StatisticData {
+ id: number;
+ label: string;
+ value: string;
+}
+
+const statisticData = computed<StatisticData[]>(() => [
+ {
+ id: 0,
+ label: $t('page.home.projectCount'),
+ value: '25'
+ },
+ {
+ id: 1,
+ label: $t('page.home.todo'),
+ value: '4/16'
+ },
+ {
+ id: 2,
+ label: $t('page.home.message'),
+ value: '12'
+ }
+]);
+</script>
+
+<template>
+ <NCard :bordered="false" class="card-wrapper">
+ <NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
+ <NGi span="24 s:24 m:18">
+ <div class="flex-y-center">
+ <div class="size-72px shrink-0 overflow-hidden rd-1/2">
+ <img :src="authStore.userInfo.user?.avatar || defaultAvatar" class="size-full" />
+ </div>
+ <div class="pl-12px">
+ <h3 class="text-18px font-semibold">
+ {{
+ $t('page.home.greeting', {
+ userName: authStore.userInfo.user?.nickName || authStore.userInfo.user?.userName
+ })
+ }}
+ </h3>
+ <p class="text-#999 leading-30px">{{ $t('page.home.weatherDesc') }}</p>
+ </div>
+ </div>
+ </NGi>
+ <NGi span="24 s:24 m:6">
+ <NSpace :size="24" justify="end">
+ <NStatistic v-for="item in statisticData" :key="item.id" class="whitespace-nowrap" v-bind="item" />
+ </NSpace>
+ </NGi>
+ </NGrid>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/home/modules/line-chart.vue b/ruoyi-plus-soybean/src/views/home/modules/line-chart.vue
new file mode 100755
index 0000000..5e0b079
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/home/modules/line-chart.vue
@@ -0,0 +1,152 @@
+<script setup lang="ts">
+import { watch } from 'vue';
+import { useAppStore } from '@/store/modules/app';
+import { useEcharts } from '@/hooks/common/echarts';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'LineChart'
+});
+
+const appStore = useAppStore();
+
+const { domRef, updateOptions } = useEcharts(() => ({
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'cross',
+ label: {
+ backgroundColor: '#6a7985'
+ }
+ }
+ },
+ legend: {
+ data: [$t('page.home.downloadCount'), $t('page.home.registerCount')],
+ top: '0'
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ top: '15%'
+ },
+ xAxis: {
+ type: 'category',
+ boundaryGap: false,
+ data: [] as string[]
+ },
+ yAxis: {
+ type: 'value'
+ },
+ series: [
+ {
+ color: '#8e9dff',
+ name: $t('page.home.downloadCount'),
+ type: 'line',
+ smooth: true,
+ stack: 'Total',
+ areaStyle: {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ {
+ offset: 0.25,
+ color: '#8e9dff'
+ },
+ {
+ offset: 1,
+ color: '#fff'
+ }
+ ]
+ }
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ data: [] as number[]
+ },
+ {
+ color: '#26deca',
+ name: $t('page.home.registerCount'),
+ type: 'line',
+ smooth: true,
+ stack: 'Total',
+ areaStyle: {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ {
+ offset: 0.25,
+ color: '#26deca'
+ },
+ {
+ offset: 1,
+ color: '#fff'
+ }
+ ]
+ }
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ data: []
+ }
+ ]
+}));
+
+async function mockData() {
+ await new Promise(resolve => {
+ setTimeout(resolve, 1000);
+ });
+
+ updateOptions(opts => {
+ opts.xAxis.data = ['06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00', '24:00'];
+ opts.series[0].data = [4623, 6145, 6268, 6411, 1890, 4251, 2978, 3880, 3606, 4311];
+ opts.series[1].data = [2208, 2016, 2916, 4512, 8281, 2008, 1963, 2367, 2956, 678];
+
+ return opts;
+ });
+}
+
+function updateLocale() {
+ updateOptions((opts, factory) => {
+ const originOpts = factory();
+
+ opts.legend.data = originOpts.legend.data;
+ opts.series[0].name = originOpts.series[0].name;
+ opts.series[1].name = originOpts.series[1].name;
+
+ return opts;
+ });
+}
+
+async function init() {
+ mockData();
+}
+
+watch(
+ () => appStore.locale,
+ () => {
+ updateLocale();
+ }
+);
+
+// init
+init();
+</script>
+
+<template>
+ <NCard :bordered="false" class="card-wrapper">
+ <div ref="domRef" class="h-360px overflow-hidden"></div>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/home/modules/pie-chart.vue b/ruoyi-plus-soybean/src/views/home/modules/pie-chart.vue
new file mode 100755
index 0000000..9ff7978
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/home/modules/pie-chart.vue
@@ -0,0 +1,109 @@
+<script setup lang="ts">
+import { watch } from 'vue';
+import { useAppStore } from '@/store/modules/app';
+import { useEcharts } from '@/hooks/common/echarts';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'PieChart'
+});
+
+const appStore = useAppStore();
+
+const { domRef, updateOptions } = useEcharts(() => ({
+ tooltip: {
+ trigger: 'item'
+ },
+ legend: {
+ bottom: '1%',
+ left: 'center',
+ itemStyle: {
+ borderWidth: 0
+ }
+ },
+ series: [
+ {
+ color: ['#5da8ff', '#8e9dff', '#fedc69', '#26deca'],
+ name: $t('page.home.schedule'),
+ type: 'pie',
+ radius: ['45%', '75%'],
+ avoidLabelOverlap: false,
+ itemStyle: {
+ borderRadius: 10,
+ borderColor: '#fff',
+ borderWidth: 1
+ },
+ label: {
+ show: false,
+ position: 'center'
+ },
+ emphasis: {
+ label: {
+ show: true,
+ fontSize: '12'
+ }
+ },
+ labelLine: {
+ show: false
+ },
+ data: [] as { name: string; value: number }[]
+ }
+ ]
+}));
+
+async function mockData() {
+ await new Promise(resolve => {
+ setTimeout(resolve, 1000);
+ });
+
+ updateOptions(opts => {
+ opts.series[0].data = [
+ { name: $t('page.home.study'), value: 20 },
+ { name: $t('page.home.entertainment'), value: 10 },
+ { name: $t('page.home.work'), value: 40 },
+ { name: $t('page.home.rest'), value: 30 }
+ ];
+
+ return opts;
+ });
+}
+
+function updateLocale() {
+ updateOptions((opts, factory) => {
+ const originOpts = factory();
+
+ opts.series[0].name = originOpts.series[0].name;
+
+ opts.series[0].data = [
+ { name: $t('page.home.study'), value: 20 },
+ { name: $t('page.home.entertainment'), value: 10 },
+ { name: $t('page.home.work'), value: 40 },
+ { name: $t('page.home.rest'), value: 30 }
+ ];
+
+ return opts;
+ });
+}
+
+async function init() {
+ mockData();
+}
+
+watch(
+ () => appStore.locale,
+ () => {
+ updateLocale();
+ }
+);
+
+// init
+init();
+</script>
+
+<template>
+ <NCard :bordered="false" class="card-wrapper">
+ <div ref="domRef" class="h-360px overflow-hidden"></div>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/home/modules/project-news.vue b/ruoyi-plus-soybean/src/views/home/modules/project-news.vue
new file mode 100755
index 0000000..5561567
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/home/modules/project-news.vue
@@ -0,0 +1,40 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'ProjectNews'
+});
+
+interface NewsItem {
+ id: number;
+ content: string;
+ time: string;
+}
+
+const newses = computed<NewsItem[]>(() => [
+ { id: 1, content: $t('page.home.projectNews.desc1'), time: '2021-05-28 22:22:22' },
+ { id: 2, content: $t('page.home.projectNews.desc2'), time: '2021-10-27 10:24:54' },
+ { id: 3, content: $t('page.home.projectNews.desc3'), time: '2021-10-31 22:43:12' },
+ { id: 4, content: $t('page.home.projectNews.desc4'), time: '2021-11-03 20:33:31' },
+ { id: 5, content: $t('page.home.projectNews.desc5'), time: '2021-11-07 22:45:32' }
+]);
+</script>
+
+<template>
+ <NCard :title="$t('page.home.projectNews.title')" :bordered="false" size="small" segmented class="card-wrapper">
+ <template #header-extra>
+ <a class="text-primary" href="javascript:;">{{ $t('page.home.projectNews.moreNews') }}</a>
+ </template>
+ <NList>
+ <NListItem v-for="item in newses" :key="item.id">
+ <template #prefix>
+ <SoybeanAvatar class="size-48px!" />
+ </template>
+ <NThing :title="item.content" :description="item.time" />
+ </NListItem>
+ </NList>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/monitor/cache/index.vue b/ruoyi-plus-soybean/src/views/monitor/cache/index.vue
new file mode 100755
index 0000000..fff6b5c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/monitor/cache/index.vue
@@ -0,0 +1,690 @@
+<script setup lang="ts">
+import { nextTick, onMounted, onUnmounted, ref } from 'vue';
+import { useLoading } from '@sa/hooks';
+import { fetchGetMonitorCacheInfo } from '@/service/api/monitor/cache';
+import { useEcharts } from '@/hooks/common/echarts';
+
+const { loading, startLoading, endLoading } = useLoading();
+const cacheInfo = ref<Api.Monitor.CacheInfo>();
+const fetchError = ref<string | null>(null);
+
+// 鑷姩鍒锋柊鐩稿叧鐘舵��
+const autoRefresh = ref(false);
+// 榛樿30绉掑埛鏂颁竴娆�
+const refreshInterval = ref(30);
+const refreshTimer = ref<NodeJS.Timeout | null>(null);
+
+async function getCacheInfo() {
+ startLoading();
+ fetchError.value = null;
+
+ try {
+ const { error, data } = await fetchGetMonitorCacheInfo();
+ if (!error) {
+ cacheInfo.value = data;
+
+ // 纭繚鍦ㄦ暟鎹洿鏂板悗璋冪敤鍥捐〃鏇存柊
+ nextTick(() => {
+ updateCharts();
+ // 鍗曠嫭璋冪敤鍐呭瓨鍥捐〃鏇存柊
+ updateMemoryChart();
+ });
+ } else {
+ fetchError.value = '鑾峰彇缂撳瓨淇℃伅澶辫触';
+ }
+ } catch {
+ fetchError.value = '鑾峰彇缂撳瓨淇℃伅鍑洪敊';
+ } finally {
+ endLoading();
+ }
+}
+
+// 澶勭悊鎵嬪姩鍒锋柊
+function handleRefresh() {
+ return getCacheInfo()
+ .then(() => {
+ // 棰濆鐨勫己鍒跺埛鏂板皾璇�
+ nextTick(() => {
+ forceUpdateCharts();
+ });
+ })
+ .catch(() => {
+ // 鍗充娇璇锋眰澶辫触锛屼篃灏濊瘯鏇存柊鍥捐〃锛屼互闃叉湁涓�浜涚紦瀛樻暟鎹彲鐢�
+ nextTick(() => {
+ if (cacheInfo.value) {
+ forceUpdateCharts();
+ }
+ });
+ });
+}
+
+// 澶勭悊鑷姩鍒锋柊寮�鍏�
+function toggleAutoRefresh() {
+ if (autoRefresh.value) {
+ startAutoRefresh();
+ } else {
+ stopAutoRefresh();
+ }
+}
+
+// 鍚姩鑷姩鍒锋柊
+function startAutoRefresh() {
+ stopAutoRefresh(); // 鍏堟竻闄ゅ彲鑳藉瓨鍦ㄧ殑瀹氭椂鍣�
+ refreshTimer.value = setInterval(() => {
+ getCacheInfo();
+ }, refreshInterval.value * 1000);
+}
+
+// 鍋滄鑷姩鍒锋柊
+function stopAutoRefresh() {
+ if (refreshTimer.value) {
+ clearInterval(refreshTimer.value);
+ refreshTimer.value = null;
+ }
+}
+
+// 鏇存柊鍒锋柊闂撮殧
+function updateRefreshInterval(value: number | null) {
+ if (value !== null) {
+ refreshInterval.value = value;
+ if (autoRefresh.value) {
+ startAutoRefresh(); // 閲嶆柊鍚姩瀹氭椂鍣ㄤ娇鏂伴棿闅旂敓鏁�
+ }
+ }
+}
+
+const { domRef: commandChartRef, updateOptions: updateCommandChart } = useEcharts(() => ({
+ tooltip: {
+ trigger: 'item',
+ formatter: '{a} <br/>{b} : {c} ({d}%)',
+ backgroundColor: 'rgba(255, 255, 255, 0.9)',
+ borderColor: '#e6e6e6',
+ borderWidth: 1,
+ textStyle: {
+ color: '#666'
+ },
+ extraCssText: 'box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);'
+ },
+ legend: {
+ type: 'scroll',
+ orient: 'vertical',
+ right: 10,
+ top: 20,
+ bottom: 20,
+ textStyle: {
+ color: '#666'
+ }
+ },
+ series: [
+ {
+ name: '鍛戒护',
+ type: 'pie',
+ roseType: 'radius',
+ radius: [15, 95],
+ center: ['40%', '50%'],
+ data: [] as Array<{ name: string; value: number }>,
+ animationEasing: 'cubicInOut',
+ animationDuration: 1000,
+ itemStyle: {
+ borderRadius: 10,
+ borderColor: '#fff',
+ borderWidth: 2
+ },
+ label: {
+ formatter: '{b}: {d}%',
+ color: '#666'
+ },
+ emphasis: {
+ label: {
+ show: true,
+ fontSize: '14',
+ fontWeight: 'bold'
+ },
+ itemStyle: {
+ shadowBlur: 10,
+ shadowOffsetX: 0,
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
+ }
+ }
+ }
+ ]
+}));
+
+const { domRef: memoryGaugeRef, setOptions } = useEcharts(
+ () => ({
+ tooltip: {
+ formatter: '鍐呭瓨浣跨敤鎯呭喌'
+ },
+ series: [
+ {
+ name: '鍐呭瓨',
+ type: 'gauge',
+ min: 0,
+ max: 100,
+ detail: {
+ formatter: '{value}%',
+ fontSize: 16,
+ fontWeight: 'bold',
+ offsetCenter: [0, '70%']
+ },
+ data: [
+ {
+ value: 0,
+ name: '鍐呭瓨浣跨敤鐜�'
+ }
+ ],
+ axisLine: {
+ lineStyle: {
+ width: 8,
+ color: [
+ [0.3, '#58d9f9'],
+ [0.7, '#26deca'],
+ [1, '#ff8c6a']
+ ]
+ }
+ },
+ pointer: {
+ itemStyle: {
+ color: 'auto'
+ }
+ },
+ axisTick: {
+ distance: -12,
+ length: 4,
+ lineStyle: {
+ color: '#999',
+ width: 1
+ }
+ },
+ splitLine: {
+ distance: -18,
+ length: 12,
+ lineStyle: {
+ color: '#999',
+ width: 1
+ }
+ },
+ axisLabel: {
+ color: '#666',
+ distance: 25,
+ fontSize: 12
+ },
+ title: {
+ offsetCenter: [0, '90%'],
+ fontSize: 14
+ },
+ animationDuration: 1000
+ }
+ ]
+ }),
+ {
+ // 鑷畾涔夋覆鏌撳拰鏇存柊閽╁瓙
+ onRender: chart => {
+ chart.hideLoading();
+ },
+ onUpdated: chart => {
+ chart.hideLoading();
+ }
+ }
+);
+
+// 棰滆壊閰嶇疆
+const colorPalette = [
+ '#5da8ff',
+ '#8e9dff',
+ '#fedc69',
+ '#26deca',
+ '#ff8c6a',
+ '#58d9f9',
+ '#05c091',
+ '#7367f0',
+ '#9e86ff',
+ '#f8d3a5',
+ '#4a5bcc',
+ '#22bd7c'
+];
+
+// 鏇存柊鍥捐〃鏁版嵁
+function updateCharts() {
+ // 纭繚瀛樺湪缂撳瓨淇℃伅鎵嶈繘琛屾洿鏂�
+ if (!cacheInfo.value) {
+ return;
+ }
+
+ try {
+ // 鏇存柊鍛戒护缁熻鍥捐〃
+ updateCommandChart(opts => {
+ try {
+ const commandStats = cacheInfo.value?.commandStats || [];
+ if (!commandStats.length) {
+ return opts;
+ }
+
+ // 瑙f瀽鍛戒护缁熻鏁版嵁 - 閫傞厤鏂扮殑鏁版嵁鏍煎紡 {name, value}
+ const data = commandStats
+ .map(item => {
+ try {
+ if (!item || typeof item !== 'object') {
+ return { name: '鏈煡鍛戒护', value: 0 };
+ }
+
+ // 妫�鏌PI杩斿洖鐨勬暟鎹牸寮�
+ if ('name' in item && 'value' in item) {
+ const name = String(item.name);
+ // 纭繚灏唙alue杞崲涓烘暟瀛�
+ let value = 0;
+ try {
+ value = Number.parseInt(String(item.value), 10);
+ if (Number.isNaN(value)) value = 0;
+ } catch {
+ value = 0;
+ }
+ return { name, value };
+ }
+ // 鑰佺殑澶勭悊閫昏緫锛屽吋瀹规棫鏍煎紡
+ const key = Object.keys(item)[0] || '鏈煡鍛戒护';
+ let value = 0;
+ try {
+ value = Number.parseInt(String(Object.values(item)[0] || '0'), 10);
+ if (Number.isNaN(value)) value = 0;
+ } catch {
+ value = 0;
+ }
+ return { name: key, value };
+ } catch {
+ return { name: '鏈煡鍛戒护', value: 0 };
+ }
+ })
+ .filter(item => item.name !== '鏈煡鍛戒护' || item.value > 0);
+
+ if (!data.length) {
+ return opts;
+ }
+
+ // 鎸夊�兼帓搴忥紝鍙栧墠10涓懡浠わ紝鍓╀綑鐨勫綊涓�"鍏朵粬"绫诲埆
+ const sortedData = data.sort((a, b) => b.value - a.value);
+
+ let pieData: Array<{ name: string; value: number }>;
+ if (sortedData.length > 10) {
+ const top10 = sortedData.slice(0, 10);
+ const others = sortedData.slice(10);
+ const othersValue = others.reduce((sum, item) => sum + item.value, 0);
+
+ pieData = [...top10, { name: '鍏朵粬鍛戒护', value: othersValue }];
+ } else {
+ pieData = sortedData;
+ }
+
+ // 璁剧疆楗煎浘鏁版嵁鍜岄鑹�
+ if (opts.series && Array.isArray(opts.series) && opts.series[0]) {
+ opts.series[0].data = pieData;
+
+ // 璁剧疆鑷畾涔夐鑹�
+ const newItemStyle = {
+ ...(opts.series[0].itemStyle || {}),
+ color(param: { dataIndex: number }) {
+ const index = param.dataIndex % colorPalette.length;
+ return colorPalette[index >= 0 ? index : 0];
+ }
+ };
+
+ opts.series[0].itemStyle = newItemStyle;
+
+ // 澧炲己tooltip灞曠ず
+ opts.tooltip = {
+ ...(opts.tooltip || {}),
+ formatter: function tooltipFormatter(params: any) {
+ if (!params || typeof params !== 'object') return '';
+
+ const name = params.name || '鏈煡鍛戒护';
+ const value = typeof params.value === 'number' ? params.value : 0;
+ const percent = typeof params.percent === 'number' ? params.percent : 0;
+
+ return (
+ `<div style="font-weight:bold;margin-bottom:5px;font-size:14px;">${name}</div>` +
+ `<div>鎵ц娆℃暟: <span style="font-weight:bold;float:right;">${value.toLocaleString()}</span></div>` +
+ `<div>鍗犳瘮: <span style="font-weight:bold;float:right;">${percent.toFixed(2)}%</span></div>`
+ );
+ } as any
+ };
+ }
+
+ return opts;
+ } catch {
+ return opts;
+ }
+ });
+
+ // 鏇存柊鍐呭瓨浠〃鐩�
+ updateMemoryChart();
+ } catch {
+ // 閿欒宸茶鎹曡幏锛屾棤闇�澶勭悊
+ }
+}
+
+// 鏇存柊鍐呭瓨浠〃鐩樻暟鎹殑鏂规硶
+function updateMemoryChart() {
+ try {
+ const info = cacheInfo.value?.info;
+ if (!info) {
+ return;
+ }
+
+ // 浣跨敤绫诲瀷鏂█澶勭悊API杩斿洖鐨勫疄闄呮暟鎹粨鏋�
+ const infoAny = info as any;
+
+ // 鑾峰彇鍐呭瓨淇℃伅瀛楃涓�
+ const usedMemoryHuman = String(infoAny.used_memory_human || '0B');
+ const maxMemoryHuman = String(infoAny.maxmemory_human || '0B');
+
+ // 瑙f瀽鍐呭瓨瀛楃涓�
+ const usedMem = parseMemoryString(usedMemoryHuman);
+ const maxMem = parseMemoryString(maxMemoryHuman);
+
+ // 璁$畻鍐呭瓨浣跨敤鐜�
+ let usagePercent = 0;
+
+ if (maxMem.bytes > 0) {
+ // 濡傛灉鏈夋渶澶у唴瀛橀檺鍒讹紝璁$畻浣跨敤鐧惧垎姣�
+ usagePercent = Math.min(100, Math.round((usedMem.bytes / maxMem.bytes) * 100));
+ } else if (infoAny.total_system_memory) {
+ // 浣跨敤绯荤粺鎬诲唴瀛樹綔涓哄弬鑰�
+ try {
+ const totalMemoryBytes = Number(infoAny.total_system_memory);
+ if (totalMemoryBytes > 0) {
+ usagePercent = Math.min(100, Math.round((usedMem.bytes / totalMemoryBytes) * 100));
+ } else {
+ // 鏃犳硶璁$畻鐧惧垎姣旓紝鏄剧ず缁濆鍊�
+ usagePercent = 50; // 浠〃鐩樻樉绀轰腑闂村��
+ }
+ } catch {
+ usagePercent = 50;
+ }
+ } else {
+ // 娌℃湁鏈�澶у唴瀛橀檺鍒讹紝鏄剧ず缁濆鍊�
+ usagePercent = 50; // 浠〃鐩樻樉绀轰腑闂村��
+ }
+
+ // 瀹屾暣鐨勪华琛ㄧ洏閫夐」
+ const gaugeOption = {
+ tooltip: {
+ formatter:
+ maxMem.bytes > 0
+ ? `鍐呭瓨浣跨敤: ${usedMemoryHuman}<br/>鏈�澶у唴瀛�: ${maxMemoryHuman}<br/>浣跨敤鐜�: ${usagePercent}%`
+ : `鍐呭瓨浣跨敤: ${usedMemoryHuman}<br/>鏈�澶у唴瀛�: 鏈缃檺鍒禶
+ },
+ series: [
+ {
+ name: '鍐呭瓨',
+ type: 'gauge' as const,
+ min: 0,
+ max: 100,
+ detail: {
+ formatter: maxMem.bytes > 0 ? '{value}%' : usedMemoryHuman,
+ fontSize: 16,
+ fontWeight: 'bold' as const,
+ offsetCenter: [0, '70%']
+ },
+ data: [
+ {
+ value: usagePercent,
+ name: maxMem.bytes > 0 ? '鍐呭瓨浣跨敤鐜�' : '鍐呭瓨浣跨敤閲�'
+ }
+ ],
+ axisLine: {
+ lineStyle: {
+ width: 8,
+ color: [
+ [0.3, '#58d9f9'],
+ [0.7, '#26deca'],
+ [1, '#ff8c6a']
+ ]
+ }
+ },
+ pointer: {
+ itemStyle: {
+ color: 'auto'
+ }
+ },
+ axisTick: {
+ distance: -12,
+ length: 4,
+ lineStyle: {
+ color: '#999',
+ width: 1
+ }
+ },
+ splitLine: {
+ distance: -18,
+ length: 12,
+ lineStyle: {
+ color: '#999',
+ width: 1
+ }
+ },
+ axisLabel: {
+ color: '#666',
+ distance: 25,
+ fontSize: 12
+ },
+ title: {
+ offsetCenter: [0, '90%'],
+ fontSize: 14
+ },
+ animationDuration: 1000
+ }
+ ]
+ };
+
+ // 鐩存帴璁剧疆鍥捐〃閫夐」
+ if (memoryGaugeRef.value) {
+ // 浣跨敤绫诲瀷鏂█缁曡繃TypeScript绫诲瀷妫�鏌�
+ setOptions(gaugeOption as any);
+ }
+ } catch {
+ // 閿欒宸茶鎹曡幏锛屾棤闇�澶勭悊
+ }
+}
+
+// 瑙f瀽鍐呭瓨瀛楃涓诧紝渚嬪 "3.6MB" -> { value: 3.6, unit: "MB" }
+function parseMemoryString(memoryStr: string): { value: number; unit: string; bytes: number } {
+ if (!memoryStr || typeof memoryStr !== 'string') {
+ return { value: 0, unit: 'B', bytes: 0 };
+ }
+
+ try {
+ // 鐢ㄦ鍒欒〃杈惧紡鎻愬彇鏁板�煎拰鍗曚綅
+ const match = memoryStr.match(/^([\d.]+)([KMGTP]?B)?$/i);
+ if (!match) {
+ return { value: 0, unit: 'B', bytes: 0 };
+ }
+
+ const value = Number.parseFloat(match[1]);
+ const unit = (match[2] || 'B').toUpperCase();
+
+ // 璁$畻瀛楄妭鏁�
+ const unitIndex = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'].indexOf(unit);
+ const bytes = unitIndex >= 0 ? value * 1024 ** unitIndex : value;
+
+ return { value, unit, bytes };
+ } catch {
+ return { value: 0, unit: 'B', bytes: 0 };
+ }
+}
+
+// 鍛戒护缁熻鍥捐〃寮哄埗鍒锋柊
+function forceUpdateCharts() {
+ try {
+ if (memoryGaugeRef.value) {
+ updateMemoryChart();
+ }
+
+ if (commandChartRef.value) {
+ // 鏇存柊鍛戒护缁熻鍥捐〃
+ updateCommandChart(opts => {
+ // ... existing code ...
+ return opts;
+ });
+ }
+ } catch {
+ // 閿欒宸茶鎹曡幏锛屾棤闇�澶勭悊
+ }
+}
+
+// 鏀硅繘缁勪欢鎸傝浇澶勭悊
+onMounted(async () => {
+ try {
+ await getCacheInfo();
+ } catch {
+ fetchError.value = '鍒濆鍖栨暟鎹け璐ワ紝璇峰皾璇曞埛鏂�';
+ }
+});
+
+// 纭繚鍦ㄧ粍浠跺嵏杞芥椂娓呯悊鎵�鏈夎祫婧�
+onUnmounted(() => {
+ stopAutoRefresh();
+ // 娓呯悊鍥捐〃璧勬簮
+ try {
+ if (commandChartRef.value) {
+ commandChartRef.value = null;
+ }
+ if (memoryGaugeRef.value) {
+ memoryGaugeRef.value = null;
+ }
+ } catch {
+ // 閿欒宸茶鎹曡幏锛屾棤闇�澶勭悊
+ }
+});
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-y-auto lt-sm:overflow-auto">
+ <NSpace vertical :size="16">
+ <!-- 鎺у埗闈㈡澘 -->
+ <NCard :bordered="false" class="control-panel card-wrapper">
+ <div class="flex flex-wrap items-center justify-between gap-y-12px">
+ <h3 class="m-0 text-16px font-medium">Redis 缂撳瓨鐩戞帶</h3>
+ <div class="flex flex-wrap items-center gap-16px">
+ <div class="flex items-center gap-8px">
+ <span class="whitespace-nowrap">鑷姩鍒锋柊锛�</span>
+ <NSwitch v-model:value="autoRefresh" @update:value="toggleAutoRefresh" />
+ </div>
+ <div v-if="autoRefresh" class="flex items-center gap-8px">
+ <span class="whitespace-nowrap">闂撮殧 (绉�)锛�</span>
+ <NInputNumber
+ v-model:value="refreshInterval"
+ :min="5"
+ :max="300"
+ size="small"
+ @update:value="updateRefreshInterval"
+ />
+ </div>
+ <NButton
+ type="primary"
+ :loading="loading"
+ :disabled="autoRefresh"
+ class="min-w-80px"
+ @click="handleRefresh"
+ >
+ {{ loading ? '鍒锋柊涓�...' : '鍒锋柊鏁版嵁' }}
+ </NButton>
+ </div>
+ </div>
+ </NCard>
+
+ <!-- 閿欒鎻愮ず -->
+ <NAlert v-if="fetchError" type="error" closable>
+ {{ fetchError }}
+ </NAlert>
+
+ <NCard title="Redis 鍩烘湰淇℃伅" :bordered="false" class="info-card card-wrapper">
+ <NSpin :show="loading">
+ <NDescriptions :column="4" bordered label-placement="left" label-class="w-150px">
+ <NDescriptionsItem label="Redis 鐗堟湰">{{ cacheInfo?.info?.redis_version }}</NDescriptionsItem>
+ <NDescriptionsItem label="杩愯妯″紡">
+ {{ cacheInfo?.info?.redis_mode === 'standalone' ? '鍗曟満' : '闆嗙兢' }}
+ </NDescriptionsItem>
+ <NDescriptionsItem label="绔彛">{{ cacheInfo?.info?.tcp_port }}</NDescriptionsItem>
+ <NDescriptionsItem label="瀹㈡埛绔暟">{{ cacheInfo?.info?.connected_clients }}</NDescriptionsItem>
+ <NDescriptionsItem label="杩愯鏃堕棿(澶�)">{{ cacheInfo?.info?.uptime_in_days }}</NDescriptionsItem>
+ <NDescriptionsItem label="浣跨敤鍐呭瓨">{{ cacheInfo?.info?.used_memory_human }}</NDescriptionsItem>
+ <NDescriptionsItem label="浣跨敤CPU">
+ {{
+ cacheInfo?.info?.used_cpu_user_children
+ ? parseFloat(cacheInfo?.info?.used_cpu_user_children).toFixed(2)
+ : ''
+ }}
+ </NDescriptionsItem>
+ <NDescriptionsItem label="鍐呭瓨閰嶇疆">{{ cacheInfo?.info?.maxmemory_human }}</NDescriptionsItem>
+ <NDescriptionsItem label="AOF 寮�鍚�">
+ {{ cacheInfo?.info?.aof_enabled === '0' ? '鍚�' : '鏄�' }}
+ </NDescriptionsItem>
+ <NDescriptionsItem label="RDB 鐘舵��">
+ {{ cacheInfo?.info?.rdb_last_bgsave_status }}
+ </NDescriptionsItem>
+ <NDescriptionsItem label="Key 鏁伴噺">{{ cacheInfo?.dbSize }}</NDescriptionsItem>
+ <NDescriptionsItem label="缃戠粶鍏ュ彛/鍑哄彛">
+ {{ cacheInfo?.info?.instantaneous_input_kbps }}kps/{{ cacheInfo?.info?.instantaneous_output_kbps }}kps
+ </NDescriptionsItem>
+ </NDescriptions>
+ </NSpin>
+ </NCard>
+
+ <NGrid :cols="2" :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
+ <NGi span="0:24 1000:12">
+ <NCard title="鍛戒护缁熻" :bordered="false" class="chart-card card-wrapper">
+ <div ref="commandChartRef" class="h-360px overflow-hidden"></div>
+ </NCard>
+ </NGi>
+ <NGi span="0:24 1000:12">
+ <NCard title="鍐呭瓨淇℃伅" :bordered="false" class="chart-card card-wrapper">
+ <NSpin :show="loading">
+ <div ref="memoryGaugeRef" class="h-360px overflow-hidden"></div>
+ </NSpin>
+ </NCard>
+ </NGi>
+ </NGrid>
+ </NSpace>
+ </div>
+</template>
+
+<style scoped>
+.card-wrapper {
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+ border-radius: 8px;
+ transition: all 0.3s ease;
+}
+
+.card-wrapper:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.control-panel {
+ background: linear-gradient(to right, rgba(115, 103, 240, 0.05), rgba(115, 103, 240, 0.01));
+}
+
+.info-card {
+ position: relative;
+ overflow: hidden;
+}
+
+.chart-card {
+ min-height: 420px;
+}
+
+@media (max-width: 768px) {
+ .flex-wrap {
+ flex-wrap: wrap;
+ }
+
+ .chart-card {
+ min-height: 360px;
+ }
+}
+
+@media (max-width: 480px) {
+ .chart-card {
+ min-height: 300px;
+ }
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/monitor/logininfor/index.vue b/ruoyi-plus-soybean/src/views/monitor/logininfor/index.vue
new file mode 100755
index 0000000..0402fd2
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/monitor/logininfor/index.vue
@@ -0,0 +1,279 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import {
+ fetchBatchDeleteLoginInfor,
+ fetchCleanLoginInfor,
+ fetchGetLoginInforList,
+ fetchUnlockLoginInfor
+} from '@/service/api/monitor/login-infor';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import { getBrowserIcon, getOsIcon } from '@/utils/icon-tag-format';
+import DictTag from '@/components/custom/dict-tag.vue';
+import SvgIcon from '@/components/custom/svg-icon.vue';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import LoginInforSearch from './modules/login-infor-search.vue';
+import LoginInforViewDrawer from './modules/login-infor-view-drawer.vue';
+
+defineOptions({
+ name: 'LoginInforList'
+});
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+useDict('sys_common_status');
+useDict('sys_device_type');
+
+const searchParams = ref<Api.Monitor.LoginInforSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ userName: null,
+ ipaddr: null,
+ status: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetLoginInforList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'userName',
+ title: '鐢ㄦ埛璐﹀彿',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'deviceType',
+ title: '璁惧绫诲瀷',
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ return <DictTag size="small" value={row.deviceType} dict-code="sys_device_type" />;
+ }
+ },
+ {
+ key: 'ipaddr',
+ title: '鐧诲綍IP鍦板潃',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'loginLocation',
+ title: '鐧诲綍鍦扮偣',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'browser',
+ title: '娴忚鍣ㄧ被鍨�',
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ return (
+ <div class="flex items-center justify-center gap-2">
+ <SvgIcon icon={getBrowserIcon(row.browser)} />
+ {row.browser}
+ </div>
+ );
+ }
+ },
+ {
+ key: 'os',
+ title: '鎿嶄綔绯荤粺',
+ align: 'center',
+ ellipsis: {
+ tooltip: true
+ },
+ minWidth: 120,
+ render: row => {
+ const osName = row.os?.split(' or ')[0] ?? '';
+ return (
+ <div class="flex items-center justify-center gap-2">
+ <SvgIcon icon={getOsIcon(osName)} />
+ {osName}
+ </div>
+ );
+ }
+ },
+ {
+ key: 'status',
+ title: '鐧诲綍鐘舵��',
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ return <DictTag size="small" value={row.status} dict-code="sys_common_status" />;
+ }
+ },
+ {
+ key: 'loginTime',
+ title: '璁块棶鏃堕棿',
+ align: 'center',
+ ellipsis: {
+ tooltip: true
+ },
+ minWidth: 120
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const viewBtn = () => {
+ return (
+ <ButtonIcon
+ type="primary"
+ text
+ icon="material-symbols:visibility-outline"
+ tooltipContent="璇︽儏"
+ onClick={() => view(row.infoId!)}
+ />
+ );
+ };
+
+ const unlockBtn = () => {
+ return (
+ <>
+ <NDivider vertical />
+ <ButtonIcon
+ type="primary"
+ text
+ icon="material-symbols:lock-open-outline"
+ tooltipContent="瑙i攣"
+ popconfirmContent={`纭瑙i攣鐢ㄦ埛 ${row.userName} 鍚楋紵`}
+ onPositiveClick={() => handleUnlockLoginInfor(row.userName!)}
+ />
+ </>
+ );
+ };
+ return (
+ <div class="flex-center gap-8px">
+ {viewBtn()}
+ {unlockBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const { drawerVisible, editingData, handleEdit, checkedRowKeys, onBatchDeleted } = useTableOperate(
+ data,
+ 'infoId',
+ getData
+);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteLoginInfor(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function view(infoId: CommonType.IdType) {
+ handleEdit(infoId);
+}
+
+async function handleExport() {
+ download('/monitor/logininfor/export', searchParams.value, `鐧诲綍鏃ュ織璁板綍_${new Date().getTime()}.xlsx`);
+}
+
+async function handleCleanLoginInfor() {
+ window.$dialog?.error({
+ title: '鎻愮ず',
+ content: '鏄惁纭娓呯┖鎵�鏈夌櫥褰曟棩蹇楁暟鎹」?',
+ positiveText: '纭娓呯┖',
+ negativeText: '鍙栨秷',
+ onPositiveClick: async () => {
+ const { error } = await fetchCleanLoginInfor();
+ if (error) return;
+ window.$message?.success('娓呯┖鎴愬姛');
+ await getData();
+ }
+ });
+}
+
+async function handleUnlockLoginInfor(username: string) {
+ const { error } = await fetchUnlockLoginInfor(username);
+ if (error) return;
+ window.$message?.success('瑙i攣鎴愬姛');
+ await getDataByPage();
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <LoginInforSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard title="鐧诲綍鏃ュ織鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="false"
+ :show-delete="hasAuth('monitor:logininfor:remove')"
+ :show-export="hasAuth('monitor:logininfor:export')"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ >
+ <template #prefix>
+ <NButton
+ v-if="hasAuth('monitor:logininfor:remove')"
+ type="error"
+ ghost
+ size="small"
+ @click="handleCleanLoginInfor"
+ >
+ <template #icon>
+ <icon-material-symbols-warning-outline-rounded />
+ </template>
+ 娓呯┖
+ </NButton>
+ </template>
+ </TableHeaderOperation>
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.infoId"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <LoginInforViewDrawer v-model:visible="drawerVisible" :row-data="editingData" />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/monitor/logininfor/modules/login-infor-search.vue b/ruoyi-plus-soybean/src/views/monitor/logininfor/modules/login-infor-search.vue
new file mode 100755
index 0000000..8500f70
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/monitor/logininfor/modules/login-infor-search.vue
@@ -0,0 +1,101 @@
+<script setup lang="ts">
+import { ref, toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'LoginInforSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const dateRangeLoginTime = ref<[string, string] | null>(null);
+
+const model = defineModel<Api.Monitor.LoginInforSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function onDateRangeLoginTimeUpdate(value: [string, string] | null) {
+ if (value?.length) {
+ model.value.params!.beginTime = value[0];
+ model.value.params!.endTime = value[1];
+ }
+}
+
+function resetModel() {
+ dateRangeLoginTime.value = null;
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:6" label="IP鍦板潃" path="ipaddr" class="pr-24px">
+ <NInput v-model:value="model.ipaddr" placeholder="璇疯緭鍏ョ櫥褰旾P鍦板潃" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鐢ㄦ埛璐﹀彿" path="userName" class="pr-24px">
+ <NInput v-model:value="model.userName" placeholder="璇疯緭鍏ョ敤鎴疯处鍙�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鐧诲綍鐘舵��" path="status" class="pr-24px">
+ <DictSelect
+ v-model:value="model.status"
+ placeholder="璇烽�夋嫨鐧诲綍鐘舵��"
+ dict-code="sys_common_status"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鐧诲綍鏃堕棿" path="loginTime" class="pr-24px">
+ <NDatePicker
+ v-model:formatted-value="dateRangeLoginTime"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ @update:formatted-value="onDateRangeLoginTimeUpdate"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/monitor/logininfor/modules/login-infor-view-drawer.vue b/ruoyi-plus-soybean/src/views/monitor/logininfor/modules/login-infor-view-drawer.vue
new file mode 100755
index 0000000..cb482ac
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/monitor/logininfor/modules/login-infor-view-drawer.vue
@@ -0,0 +1,71 @@
+<script setup lang="ts">
+import { getBrowserIcon, getOsIcon } from '@/utils/icon-tag-format';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'LoginInforViewDrawer'
+});
+
+interface Props {
+ /** the edit row data */
+ rowData: Api.Monitor.LoginInfor | null;
+}
+
+const props = defineProps<Props>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const title = '鐧诲綍淇℃伅璇︽儏';
+
+function closeDrawer() {
+ visible.value = false;
+}
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NDescriptions label-placement="left" :column="1" size="small" bordered>
+ <NDescriptionsItem label="璐﹀彿淇℃伅">
+ {{ props.rowData?.userName }} | {{ props.rowData?.ipaddr }} | {{ props.rowData?.loginLocation }}
+ </NDescriptionsItem>
+ <NDescriptionsItem label="瀹㈡埛绔�">
+ {{ props.rowData?.clientKey }}
+ </NDescriptionsItem>
+ <NDescriptionsItem label="璁惧绫诲瀷">
+ <DictTag size="small" :value="props.rowData?.deviceType" dict-code="sys_device_type" />
+ </NDescriptionsItem>
+ <NDescriptionsItem label="娴忚鍣ㄧ被鍨�">
+ <div class="flex items-center gap-2">
+ <SvgIcon :icon="getBrowserIcon(props.rowData?.browser ?? '')" />
+ {{ props.rowData?.browser }}
+ </div>
+ </NDescriptionsItem>
+ <NDescriptionsItem label="鎿嶄綔绯荤粺">
+ <div class="flex items-center gap-2">
+ <SvgIcon :icon="getOsIcon(props.rowData?.os ?? '')" />
+ {{ props.rowData?.os }}
+ </div>
+ </NDescriptionsItem>
+ <NDescriptionsItem label="鐧诲綍鐘舵��">
+ <DictTag size="small" :value="props.rowData?.status" dict-code="sys_common_status" />
+ </NDescriptionsItem>
+ <NDescriptionsItem label="鎻愮ず娑堟伅">
+ {{ props.rowData?.msg }}
+ </NDescriptionsItem>
+ <NDescriptionsItem label="璁块棶鏃堕棿">
+ {{ props.rowData?.loginTime }}
+ </NDescriptionsItem>
+ </NDescriptions>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.close') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/monitor/online/index.vue b/ruoyi-plus-soybean/src/views/monitor/online/index.vue
new file mode 100755
index 0000000..32882aa
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/monitor/online/index.vue
@@ -0,0 +1,172 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import dayjs from 'dayjs';
+import { fetchForceLogout, fetchGetOnlineUserList } from '@/service/api/monitor/online';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import { getBrowserIcon, getOsIcon } from '@/utils/icon-tag-format';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import DictTag from '@/components/custom/dict-tag.vue';
+import SvgIcon from '@/components/custom/svg-icon.vue';
+import { $t } from '@/locales';
+import OnlineSearch from './modules/online-search.vue';
+
+defineOptions({
+ name: 'OnlineList'
+});
+
+const appStore = useAppStore();
+const { hasAuth } = useAuth();
+
+useDict('sys_common_status');
+useDict('sys_device_type');
+
+const searchParams = ref<Api.Monitor.OnlineUserSearchParams>({
+ userName: null,
+ ipaddr: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, loading, scrollX } = useNaivePaginatedTable({
+ api: () => fetchGetOnlineUserList(searchParams.value),
+ transform: response => defaultTransform(response),
+ columns: () => [
+ {
+ key: 'userName',
+ title: '鐢ㄦ埛璐﹀彿',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'deviceType',
+ title: '璁惧绫诲瀷',
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ return <DictTag size="small" value={row.deviceType} dict-code="sys_device_type" />;
+ }
+ },
+ {
+ key: 'ipaddr',
+ title: '鐧诲綍IP鍦板潃',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'loginLocation',
+ title: '鐧诲綍鍦扮偣',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'browser',
+ title: '娴忚鍣ㄧ被鍨�',
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ return (
+ <div class="flex items-center justify-center gap-2">
+ <SvgIcon icon={getBrowserIcon(row.browser)} />
+ {row.browser}
+ </div>
+ );
+ }
+ },
+ {
+ key: 'os',
+ title: '鎿嶄綔绯荤粺',
+ align: 'center',
+ ellipsis: {
+ tooltip: true
+ },
+ minWidth: 120,
+ render: row => {
+ const osName = row.os?.split(' or ')[0] ?? '';
+ return (
+ <div class="flex items-center justify-center gap-2">
+ <SvgIcon icon={getOsIcon(osName)} />
+ {osName}
+ </div>
+ );
+ }
+ },
+ {
+ key: 'loginTime',
+ title: '鐧诲綍鏃堕棿',
+ align: 'center',
+ ellipsis: {
+ tooltip: true
+ },
+ minWidth: 120,
+ render: row => {
+ return dayjs(row.loginTime).format('YYYY-MM-DD HH:mm:ss');
+ }
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const forceLogoutBtn = () => {
+ if (!hasAuth('monitor:online:forceLogout')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ class="text-20px"
+ tooltipContent="寮哄埗涓嬬嚎"
+ popconfirmContent="纭寮哄埗涓嬬嚎鍚楋紵"
+ onPositiveClick={() => handleForceLogout(row.tokenId)}
+ />
+ );
+ };
+ return <div>{forceLogoutBtn()}</div>;
+ }
+ }
+ ]
+});
+
+async function handleForceLogout(tokenId: string) {
+ // request
+ const { error } = await fetchForceLogout(tokenId);
+ if (error) return;
+ getData();
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <OnlineSearch v-model:model="searchParams" @search="getData" />
+ <NCard title="鍦ㄧ嚎鐢ㄦ埛鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :loading="loading"
+ :show-add="false"
+ :show-delete="false"
+ :show-export="false"
+ @refresh="getData"
+ />
+ </template>
+ <NDataTable
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.tokenId"
+ class="sm:h-full"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/monitor/online/modules/online-search.vue b/ruoyi-plus-soybean/src/views/monitor/online/modules/online-search.vue
new file mode 100755
index 0000000..8aa0b91
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/monitor/online/modules/online-search.vue
@@ -0,0 +1,74 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'LoginInforSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Monitor.OnlineUserSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:8" label="IP鍦板潃" path="ipaddr" class="pr-24px">
+ <NInput v-model:value="model.ipaddr" placeholder="璇疯緭鍏P鍦板潃" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鐢ㄦ埛璐﹀彿" path="userName" class="pr-24px">
+ <NInput v-model:value="model.userName" placeholder="璇疯緭鍏ョ敤鎴疯处鍙�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:24 m:8" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/monitor/operlog/index.vue b/ruoyi-plus-soybean/src/views/monitor/operlog/index.vue
new file mode 100755
index 0000000..a9b8653
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/monitor/operlog/index.vue
@@ -0,0 +1,225 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NButton } from 'naive-ui';
+import { fetchBatchDeleteOperLog, fetchCleanOperLog, fetchGetOperLogList } from '@/service/api/monitor/oper-log';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import DictTag from '@/components/custom/dict-tag.vue';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import OperLogViewDrawer from './modules/oper-log-view-drawer.vue';
+import OperLogSearch from './modules/oper-log-search.vue';
+
+defineOptions({
+ name: 'OperLogList'
+});
+
+useDict('sys_common_status');
+useDict('sys_oper_type');
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.Monitor.OperLogSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ title: null,
+ businessType: null,
+ operName: null,
+ operIp: null,
+ status: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetOperLogList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'title',
+ title: '绯荤粺妯″潡',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'businessType',
+ title: '鎿嶄綔绫诲瀷',
+ align: 'center',
+ minWidth: 120,
+ render(row) {
+ return <DictTag size="small" value={row.businessType} dictCode="sys_oper_type" />;
+ }
+ },
+ {
+ key: 'operName',
+ title: '鎿嶄綔浜哄憳',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'operIp',
+ title: '鎿嶄綔IP',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'operLocation',
+ title: '鎿嶄綔鍦扮偣',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'status',
+ title: '鎿嶄綔鐘舵��',
+ align: 'center',
+ minWidth: 120,
+ render(row) {
+ return <DictTag size="small" value={row.status} dictCode="sys_common_status" />;
+ }
+ },
+ {
+ key: 'operTime',
+ title: '鎿嶄綔鏃堕棿',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'costTime',
+ title: '娑堣�楁椂闂�',
+ align: 'center',
+ minWidth: 120,
+ render(row) {
+ return `${row.costTime} ms`;
+ }
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const viewBtn = () => {
+ return (
+ <ButtonIcon
+ type="primary"
+ text
+ icon="material-symbols:visibility-outline"
+ tooltipContent="璇︽儏"
+ onClick={() => view(row.operId!)}
+ />
+ );
+ };
+ return <div class="flex-center gap-8px">{viewBtn()}</div>;
+ }
+ }
+ ]
+ });
+
+const { drawerVisible, editingData, handleEdit, checkedRowKeys, onBatchDeleted } = useTableOperate(
+ data,
+ 'operId',
+ getData
+);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteOperLog(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+async function view(operId: CommonType.IdType) {
+ handleEdit(operId);
+}
+
+async function handleExport() {
+ download('/monitor/operlog/export', searchParams.value, `鎿嶄綔鏃ュ織_${new Date().getTime()}.xlsx`);
+}
+
+async function handleCleanOperLog() {
+ window.$dialog?.error({
+ title: '鎻愮ず',
+ content: '鏄惁纭娓呯┖鎵�鏈夋搷浣滄棩蹇楁暟鎹」?',
+ positiveText: '纭娓呯┖',
+ negativeText: '鍙栨秷',
+ onPositiveClick: async () => {
+ const { error } = await fetchCleanOperLog();
+ if (error) return;
+ window.$message?.success('娓呯┖鎴愬姛');
+ await getData();
+ }
+ });
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <OperLogSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard title="鎿嶄綔鏃ュ織鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="false"
+ :show-delete="hasAuth('monitor:operlog:remove')"
+ :show-export="hasAuth('monitor:operlog:export')"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ >
+ <template #prefix>
+ <NButton
+ v-if="hasAuth('monitor:operlog:remove')"
+ type="error"
+ ghost
+ size="small"
+ @click="handleCleanOperLog"
+ >
+ <template #icon>
+ <icon-material-symbols-warning-outline-rounded />
+ </template>
+ 娓呯┖
+ </NButton>
+ </template>
+ </TableHeaderOperation>
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.operId"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <OperLogViewDrawer v-model:visible="drawerVisible" :row-data="editingData" />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/monitor/operlog/modules/oper-log-search.vue b/ruoyi-plus-soybean/src/views/monitor/operlog/modules/oper-log-search.vue
new file mode 100755
index 0000000..4b61265
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/monitor/operlog/modules/oper-log-search.vue
@@ -0,0 +1,112 @@
+<script setup lang="ts">
+import { ref, toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'OperLogSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const dateRangeOperTime = ref<[string, string] | null>(null);
+
+const model = defineModel<Api.Monitor.OperLogSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function onDateRangeOperTimeUpdate(value: [string, string] | null) {
+ if (value?.length) {
+ model.value.params!.beginTime = value[0];
+ model.value.params!.endTime = value[1];
+ }
+}
+
+function resetModel() {
+ dateRangeOperTime.value = null;
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:6" label="绯荤粺妯″潡" path="title" class="pr-24px">
+ <NInput v-model:value="model.title" placeholder="璇疯緭鍏ョ郴缁熸ā鍧�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鎿嶄綔绫诲瀷" path="businessType" class="pr-24px">
+ <DictSelect
+ v-model:value="model.businessType"
+ placeholder="璇烽�夋嫨鎿嶄綔绫诲瀷"
+ dict-code="sys_oper_type"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鎿嶄綔浜哄憳" path="operName" class="pr-24px">
+ <NInput v-model:value="model.operName" placeholder="璇疯緭鍏ユ搷浣滀汉鍛�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鎿嶄綔IP" path="operIp" class="pr-24px">
+ <NInput v-model:value="model.operIp" placeholder="璇疯緭鍏ユ搷浣淚P" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鎿嶄綔鐘舵��" path="status" class="pr-24px">
+ <DictSelect
+ v-model:value="model.status"
+ placeholder="璇烽�夋嫨鎿嶄綔鐘舵��"
+ dict-code="sys_common_status"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鎿嶄綔鏃堕棿" path="operTime" class="pr-24px">
+ <NDatePicker
+ v-model:formatted-value="dateRangeOperTime"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ @update:formatted-value="onDateRangeOperTimeUpdate"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/monitor/operlog/modules/oper-log-view-drawer.vue b/ruoyi-plus-soybean/src/views/monitor/operlog/modules/oper-log-view-drawer.vue
new file mode 100755
index 0000000..7a346ca
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/monitor/operlog/modules/oper-log-view-drawer.vue
@@ -0,0 +1,73 @@
+<script setup lang="tsx">
+import { NDescriptions, NDescriptionsItem, NTag } from 'naive-ui';
+import { getRequestMethodTagType } from '@/utils/icon-tag-format';
+import { $t } from '@/locales';
+import DictTag from '@/components/custom/dict-tag.vue';
+
+defineOptions({
+ name: 'OperLogViewDrawer'
+});
+
+interface Props {
+ /** the edit row data */
+ rowData: Api.Monitor.OperLog | null;
+}
+
+const props = defineProps<Props>();
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const title = '鎿嶄綔鏃ュ織璇︽儏';
+
+function closeDrawer() {
+ visible.value = false;
+}
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NDescriptions label-class="min-w-100px" :column="1" size="small" bordered label-placement="left">
+ <NDescriptionsItem label="鏃ュ織缂栧彿">{{ props.rowData?.operId }}</NDescriptionsItem>
+ <NDescriptionsItem label="鎿嶄綔鐘舵��">
+ <DictTag size="small" :value="props.rowData?.status" dict-code="sys_common_status" />
+ </NDescriptionsItem>
+ <NDescriptionsItem label="绯荤粺妯″潡">
+ <NSpace>
+ <NTag class="m-1" size="small" type="primary">{{ props.rowData?.title }}妯″潡</NTag>
+ <DictTag size="small" :value="props.rowData?.businessType" dict-code="sys_oper_type" />
+ </NSpace>
+ </NDescriptionsItem>
+ <NDescriptionsItem label="鎿嶄綔淇℃伅">
+ {{ props.rowData?.operName }} | {{ props.rowData?.deptName }} | {{ props.rowData?.operIp }} |
+ {{ props.rowData?.operLocation }}
+ </NDescriptionsItem>
+ <NDescriptionsItem label="璇锋眰淇℃伅">
+ <NSpace>
+ <NTag size="small" :type="getRequestMethodTagType(props.rowData?.requestMethod ?? '')">
+ {{ `${props.rowData?.requestMethod?.toUpperCase()}璇锋眰` }}
+ </NTag>
+ {{ props.rowData?.operUrl }}
+ </NSpace>
+ </NDescriptionsItem>
+ <NDescriptionsItem label="鎿嶄綔鏃堕棿">{{ props.rowData?.operTime }}</NDescriptionsItem>
+ <NDescriptionsItem label="璇锋眰鍙傛暟">
+ <JsonPreview :code="props.rowData?.operParam" />
+ </NDescriptionsItem>
+ <NDescriptionsItem label="杩斿洖鍙傛暟">
+ <JsonPreview :code="props.rowData?.jsonResult" />
+ </NDescriptionsItem>
+ <NDescriptionsItem label="娑堣�楁椂闂�">
+ {{ `${props.rowData?.costTime} ms` }}
+ </NDescriptionsItem>
+ <NDescriptionsItem label="閿欒娑堟伅">{{ props.rowData?.errorMsg }}</NDescriptionsItem>
+ </NDescriptions>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.close') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
diff --git a/ruoyi-plus-soybean/src/views/system/client/index.vue b/ruoyi-plus-soybean/src/views/system/client/index.vue
new file mode 100755
index 0000000..56c8b60
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/client/index.vue
@@ -0,0 +1,275 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDeleteClient, fetchGetClientList, fetchUpdateClientStatus } from '@/service/api/system/client';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import { $t } from '@/locales';
+import DictTag from '@/components/custom/dict-tag.vue';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import StatusSwitch from '@/components/custom/status-switch.vue';
+import ClientOperateDrawer from './modules/client-operate-drawer.vue';
+import ClientSearch from './modules/client-search.vue';
+
+defineOptions({
+ name: 'ClientList'
+});
+
+useDict('sys_grant_type');
+useDict('sys_device_type');
+useDict('sys_normal_disable');
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.System.ClientSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ // if you want to use the searchParams in Form, you need to define the following properties, and the value is null
+ // the value can not be undefined, otherwise the property in Form will not be reactive
+ clientKey: null,
+ clientSecret: null,
+ status: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetClientList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ disabled: row => row.id === 1,
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'clientId',
+ title: $t('page.system.client.clientId'),
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'clientKey',
+ title: $t('page.system.client.clientKey'),
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'clientSecret',
+ title: $t('page.system.client.clientSecret'),
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'grantTypeList',
+ title: $t('page.system.client.grantTypeList'),
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ return <DictTag value={row.grantTypeList} dict-code="sys_grant_type" />;
+ }
+ },
+ {
+ key: 'deviceType',
+ title: $t('page.system.client.deviceType'),
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ return <DictTag value={row.deviceType} dict-code="sys_device_type" />;
+ }
+ },
+ {
+ key: 'activeTimeout',
+ title: $t('page.system.client.activeTimeout'),
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ return `${row.activeTimeout}${$t('common.second')}`;
+ }
+ },
+ {
+ key: 'timeout',
+ title: $t('page.system.client.timeout'),
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ return `${row.timeout}${$t('common.second')}`;
+ }
+ },
+ {
+ key: 'status',
+ title: $t('page.system.user.status'),
+ align: 'center',
+ width: 80,
+ render(row) {
+ return (
+ <StatusSwitch
+ v-model:value={row.status}
+ disabled={row.id === 1}
+ info={row.clientId}
+ onSubmitted={(value, callback) => handleStatusChange(row, value, callback)}
+ />
+ );
+ }
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('system:client:edit') || !hasAuth('system:client:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('system:client:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.id!)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('system:client:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.id!)}
+ disabled={row.id === 1}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'id', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteClient(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteClient([id]);
+ if (error) return;
+ onDeleted();
+}
+
+async function edit(id: CommonType.IdType) {
+ handleEdit(id);
+}
+
+async function handleExport() {
+ download('/system/client/export', searchParams.value, `瀹㈡埛绔痏${new Date().getTime()}.xlsx`);
+}
+
+/** 澶勭悊鐘舵�佸垏鎹� */
+async function handleStatusChange(
+ row: Api.System.Client,
+ value: Api.Common.EnableStatus,
+ callback: (flag: boolean) => void
+) {
+ const { error } = await fetchUpdateClientStatus({
+ clientId: row.clientId,
+ status: value
+ });
+
+ callback(!error);
+
+ if (!error) {
+ window.$message?.success($t('page.system.user.statusChangeSuccess'));
+ getData();
+ }
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <ClientSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard :title="$t('page.system.client.title')" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('system:client:add')"
+ :show-delete="hasAuth('system:client:remove')"
+ :show-export="hasAuth('system:client:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ />
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.id"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <ClientOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getData"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/client/modules/client-operate-drawer.vue b/ruoyi-plus-soybean/src/views/system/client/modules/client-operate-drawer.vue
new file mode 100755
index 0000000..c7389ba
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/client/modules/client-operate-drawer.vue
@@ -0,0 +1,221 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateClient, fetchUpdateClient } from '@/service/api/system/client';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'ClientOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.System.Client | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: $t('page.system.client.addClient'),
+ edit: $t('page.system.client.editClient')
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.System.ClientOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ clientKey: '',
+ clientSecret: '',
+ grantTypeList: [],
+ deviceType: undefined,
+ activeTimeout: 1800,
+ timeout: 604800,
+ status: '0'
+ };
+}
+
+type RuleKey = Extract<
+ keyof Model,
+ 'id' | 'clientKey' | 'clientSecret' | 'grantTypeList' | 'deviceType' | 'activeTimeout' | 'timeout'
+>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ id: createRequiredRule($t('page.system.client.form.clientId.required')),
+ clientKey: createRequiredRule($t('page.system.client.form.clientKey.required')),
+ clientSecret: createRequiredRule($t('page.system.client.form.clientSecret.required')),
+ grantTypeList: createRequiredRule($t('page.system.client.form.grantTypeList.required')),
+ deviceType: createRequiredRule($t('page.system.client.form.deviceType.required')),
+ activeTimeout: createRequiredRule($t('page.system.client.form.activeTimeout.required')),
+ timeout: createRequiredRule($t('page.system.client.form.timeout.required'))
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { id, clientId, clientKey, clientSecret, grantTypeList, deviceType, activeTimeout, timeout, status } =
+ model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateClient({
+ clientKey,
+ clientSecret,
+ grantTypeList,
+ deviceType,
+ activeTimeout,
+ timeout,
+ status
+ });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateClient({
+ id,
+ clientId,
+ clientKey,
+ clientSecret,
+ grantTypeList,
+ deviceType,
+ activeTimeout,
+ timeout,
+ status
+ });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.saveSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem v-if="operateType === 'edit'" :label="$t('page.system.client.clientId')" path="clientId">
+ <NInput
+ v-model:value="model.clientId"
+ disabled
+ :placeholder="$t('page.system.client.form.clientId.required')"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.client.clientKey')" path="clientKey">
+ <NInput
+ v-model:value="model.clientKey"
+ :disabled="operateType === 'edit'"
+ :placeholder="$t('page.system.client.form.clientKey.required')"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.client.clientSecret')" path="clientSecret">
+ <NInput
+ v-model:value="model.clientSecret"
+ :disabled="operateType === 'edit'"
+ :placeholder="$t('page.system.client.form.clientSecret.required')"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.client.grantTypeList')" path="grantTypeList">
+ <DictSelect
+ v-model:value="model.grantTypeList"
+ :placeholder="$t('page.system.client.form.grantTypeList.required')"
+ dict-code="sys_grant_type"
+ multiple
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.client.deviceType')" path="deviceType">
+ <DictSelect
+ v-model:value="model.deviceType"
+ :placeholder="$t('page.system.client.form.deviceType.required')"
+ dict-code="sys_device_type"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.client.activeTimeout')" path="activeTimeout">
+ <template #label>
+ <div class="flex-center">
+ <FormTip :content="$t('page.system.client.form.activeTimeout.tooltip')" />
+ <span class="pl-3px">{{ $t('page.system.client.activeTimeout') }}</span>
+ </div>
+ </template>
+ <NInputNumber
+ v-model:value="model.activeTimeout"
+ :placeholder="$t('page.system.client.form.activeTimeout.required')"
+ >
+ <template #suffix>
+ <span class="text-sm">{{ $t('common.second') }}</span>
+ </template>
+ </NInputNumber>
+ </NFormItem>
+ <NFormItem :label="$t('page.system.client.timeout')" path="timeout">
+ <template #label>
+ <div class="flex-center">
+ <FormTip :content="$t('page.system.client.form.timeout.tooltip')" />
+ <span class="pl-3px">{{ $t('page.system.client.timeout') }}</span>
+ </div>
+ </template>
+ <NInputNumber v-model:value="model.timeout" :placeholder="$t('page.system.client.form.timeout.required')">
+ <template #suffix>
+ <span class="text-sm">{{ $t('common.second') }}</span>
+ </template>
+ </NInputNumber>
+ </NFormItem>
+ <NFormItem :label="$t('page.system.client.status')" path="status">
+ <DictRadio
+ v-model:value="model.status"
+ :placeholder="$t('page.system.client.form.status.required')"
+ :disabled="model.id == 1"
+ dict-code="sys_normal_disable"
+ />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.save') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/client/modules/client-search.vue b/ruoyi-plus-soybean/src/views/system/client/modules/client-search.vue
new file mode 100755
index 0000000..8341820
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/client/modules/client-search.vue
@@ -0,0 +1,95 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'ClientSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.System.ClientSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="90">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi
+ span="24 s:12 m:6"
+ :label="$t('page.system.client.clientKey')"
+ path="clientKey"
+ class="pr-24px"
+ >
+ <NInput v-model:value="model.clientKey" :placeholder="$t('page.system.client.form.clientKey.required')" />
+ </NFormItemGi>
+ <NFormItemGi
+ span="24 s:12 m:6"
+ :label="$t('page.system.client.clientSecret')"
+ path="clientSecret"
+ class="pr-24px"
+ >
+ <NInput
+ v-model:value="model.clientSecret"
+ :placeholder="$t('page.system.client.form.clientSecret.required')"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" :label="$t('page.system.client.status')" path="status" class="pr-24px">
+ <DictSelect
+ v-model:value="model.status"
+ :placeholder="$t('page.system.client.form.status.required')"
+ dict-code="sys_normal_disable"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/config/index.vue b/ruoyi-plus-soybean/src/views/system/config/index.vue
new file mode 100755
index 0000000..588145f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/config/index.vue
@@ -0,0 +1,240 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDeleteConfig, fetchGetConfigList, fetchRefreshCache } from '@/service/api/system/config';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import { $t } from '@/locales';
+import DictTag from '@/components/custom/dict-tag.vue';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import ConfigOperateDrawer from './modules/config-operate-drawer.vue';
+import ConfigSearch from './modules/config-search.vue';
+
+defineOptions({
+ name: 'ConfigList'
+});
+
+useDict('sys_yes_no');
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.System.ConfigSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ configName: null,
+ configKey: null,
+ configType: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetConfigList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'configName',
+ title: $t('page.system.config.configName'),
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'configKey',
+ title: $t('page.system.config.configKey'),
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'configValue',
+ title: $t('page.system.config.configValue'),
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'configType',
+ title: $t('page.system.config.configType'),
+ align: 'center',
+ minWidth: 120,
+ render(row) {
+ return <DictTag size="small" value={row.configType} dictCode="sys_yes_no" />;
+ }
+ },
+ {
+ key: 'remark',
+ title: $t('page.system.config.remark'),
+ align: 'center',
+ minWidth: 120,
+ ellipsis: {
+ tooltip: true
+ }
+ },
+ {
+ key: 'createTime',
+ title: $t('page.system.config.createTime'),
+ align: 'center',
+ minWidth: 120,
+ ellipsis: {
+ tooltip: true
+ }
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('system:config:edit') || !hasAuth('system:config:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('system:config:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.configId!)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('system:config:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.configId!)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'configId', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteConfig(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(configId: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteConfig([configId]);
+ if (error) return;
+ onDeleted();
+}
+
+async function edit(configId: CommonType.IdType) {
+ handleEdit(configId);
+}
+
+async function handleExport() {
+ download('/system/config/export', searchParams.value, `鍙傛暟閰嶇疆_${new Date().getTime()}.xlsx`);
+}
+
+async function handleRefreshCache() {
+ const { error } = await fetchRefreshCache();
+ if (error) return;
+ window.$message?.success($t('page.system.config.refreshCacheSuccess'));
+ await getData();
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <ConfigSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard :title="$t('page.system.config.title')" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('system:config:add')"
+ :show-delete="hasAuth('system:config:remove')"
+ :show-export="hasAuth('system:config:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ >
+ <template #prefix>
+ <NButton v-if="hasAuth('system:config:remove')" ghost size="small" @click="handleRefreshCache">
+ <template #icon>
+ <icon-material-symbols-sync-outline />
+ </template>
+ {{ $t('page.system.config.refreshCache') }}
+ </NButton>
+ </template>
+ </TableHeaderOperation>
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.configId"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <ConfigOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getData"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/config/modules/config-operate-drawer.vue b/ruoyi-plus-soybean/src/views/system/config/modules/config-operate-drawer.vue
new file mode 100755
index 0000000..6e10c56
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/config/modules/config-operate-drawer.vue
@@ -0,0 +1,145 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateConfig, fetchUpdateConfig } from '@/service/api/system/config';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'ConfigOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.System.Config | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: $t('page.system.config.addConfig'),
+ edit: $t('page.system.config.editConfig')
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.System.ConfigOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ configName: '',
+ configKey: '',
+ configValue: '',
+ configType: 'Y',
+ remark: ''
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'configId' | 'configName' | 'configKey' | 'configValue' | 'configType'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ configId: createRequiredRule($t('page.system.config.form.configId.required')),
+ configName: createRequiredRule($t('page.system.config.form.configName.required')),
+ configKey: createRequiredRule($t('page.system.config.form.configKey.required')),
+ configValue: createRequiredRule($t('page.system.config.form.configValue.required')),
+ configType: createRequiredRule($t('page.system.config.form.configType.required'))
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { configId, configName, configKey, configValue, configType, remark } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateConfig({ configName, configKey, configValue, configType, remark });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateConfig({ configId, configName, configKey, configValue, configType, remark });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem :label="$t('page.system.config.configName')" path="configName">
+ <NInput v-model:value="model.configName" :placeholder="$t('page.system.config.form.configName.required')" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.config.configKey')" path="configKey">
+ <NInput v-model:value="model.configKey" :placeholder="$t('page.system.config.form.configKey.required')" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.config.configValue')" path="configValue">
+ <NInput
+ v-model:value="model.configValue"
+ :rows="3"
+ :placeholder="$t('page.system.config.form.configValue.required')"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.config.configType')" path="configType">
+ <DictRadio v-model:value="model.configType" dict-code="sys_yes_no" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.config.remark')" path="remark">
+ <NInput
+ v-model:value="model.remark"
+ :rows="3"
+ type="textarea"
+ :placeholder="$t('page.system.config.form.remark.required')"
+ />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/config/modules/config-search.vue b/ruoyi-plus-soybean/src/views/system/config/modules/config-search.vue
new file mode 100755
index 0000000..54c8db8
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/config/modules/config-search.vue
@@ -0,0 +1,122 @@
+<script setup lang="ts">
+import { ref, toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'ConfigSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const dateRangeCreateTime = ref<[string, string] | null>(null);
+
+const model = defineModel<Api.System.ConfigSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function onDateRangeCreateTimeUpdate(value: [string, string] | null) {
+ const params = model.value.params!;
+ if (value && value.length === 2) {
+ [params.beginTime, params.endTime] = value;
+ } else {
+ params.beginTime = undefined;
+ params.endTime = undefined;
+ }
+}
+
+function resetModel() {
+ dateRangeCreateTime.value = null;
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi
+ span="24 s:12 m:6"
+ :label="$t('page.system.config.configName')"
+ path="configName"
+ class="pr-24px"
+ >
+ <NInput
+ v-model:value="model.configName"
+ :placeholder="$t('page.system.config.form.configName.required')"
+ />
+ </NFormItemGi>
+ <NFormItemGi
+ span="24 s:12 m:6"
+ :label="$t('page.system.config.configKey')"
+ path="configKey"
+ class="pr-24px"
+ >
+ <NInput v-model:value="model.configKey" :placeholder="$t('page.system.config.form.configKey.required')" />
+ </NFormItemGi>
+ <NFormItemGi
+ span="24 s:12 m:6"
+ :label="$t('page.system.config.configType')"
+ path="configType"
+ class="pr-24px"
+ >
+ <DictSelect
+ v-model:value="model.configType"
+ :placeholder="$t('page.system.config.form.configType.required')"
+ dict-code="sys_yes_no"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鍒涘缓鏃堕棿" path="createTime" class="pr-24px">
+ <NDatePicker
+ v-model:formatted-value="dateRangeCreateTime"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ @update:formatted-value="onDateRangeCreateTimeUpdate"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/dept/index.vue b/ruoyi-plus-soybean/src/views/system/dept/index.vue
new file mode 100755
index 0000000..4deb182
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/dept/index.vue
@@ -0,0 +1,218 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NButton, NDivider } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import { fetchBatchDeleteDept, fetchGetDeptList } from '@/service/api/system/dept';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { treeTransform, useNaiveTreeTable, useTableOperate } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import DictTag from '@/components/custom/dict-tag.vue';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import DeptOperateDrawer from './modules/dept-operate-drawer.vue';
+import DeptSearch from './modules/dept-search.vue';
+
+defineOptions({
+ name: 'DeptList'
+});
+
+useDict('sys_normal_disable');
+
+const appStore = useAppStore();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.System.DeptSearchParams>({
+ deptName: null,
+ status: null,
+ params: {}
+});
+
+const {
+ columns,
+ columnChecks,
+ data,
+ rows,
+ getData,
+ loading,
+ expandedRowKeys,
+ isCollapse,
+ expandAll,
+ collapseAll,
+ scrollX
+} = useNaiveTreeTable({
+ keyField: 'deptId',
+ api: () => fetchGetDeptList(searchParams.value),
+ transform: response => treeTransform(response, { idField: 'deptId' }),
+ columns: () => [
+ {
+ key: 'deptName',
+ title: $t('page.system.dept.deptName'),
+ align: 'center',
+ width: 230,
+ ellipsis: true
+ },
+ {
+ key: 'deptCategory',
+ title: $t('page.system.dept.deptCategory'),
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'orderNum',
+ title: $t('page.system.dept.sort'),
+ align: 'center',
+ minWidth: 60
+ },
+ {
+ key: 'status',
+ title: $t('page.system.dept.status'),
+ align: 'center',
+ minWidth: 120,
+ render(row) {
+ return <DictTag size="small" value={row.status} dictCode="sys_normal_disable" />;
+ }
+ },
+ {
+ key: 'createTime',
+ title: $t('page.system.dept.createTime'),
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 150,
+ render: row => {
+ const addBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:add-2-rounded"
+ tooltipContent={$t('common.add')}
+ onClick={() => addInRow(row)}
+ />
+ );
+ };
+
+ const editBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.deptId)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.deptId)}
+ />
+ );
+ };
+
+ const buttons = [];
+ if (hasAuth('system:dept:add')) buttons.push(addBtn());
+ if (hasAuth('system:dept:edit')) buttons.push(editBtn());
+ if (hasAuth('system:dept:remove')) buttons.push(deleteBtn());
+
+ return (
+ <div class="flex-center gap-8px">
+ {buttons.map((btn, index) => (
+ <>
+ {index !== 0 && <NDivider vertical />}
+ {btn}
+ </>
+ ))}
+ </div>
+ );
+ }
+ }
+ ]
+});
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, onDeleted } = useTableOperate(
+ rows,
+ 'deptId',
+ getData
+);
+
+async function handleDelete(deptId: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteDept([deptId]);
+ if (error) return;
+ onDeleted();
+}
+
+function edit(deptId: CommonType.IdType) {
+ handleEdit(deptId);
+}
+
+function addInRow(row: Api.System.Dept) {
+ editingData.value = jsonClone(row);
+ handleAdd();
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <DeptSearch v-model:model="searchParams" @search="getData" />
+ <NCard :title="$t('page.system.dept.title')" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :loading="loading"
+ :show-add="hasAuth('system:dept:add')"
+ :show-delete="false"
+ @add="handleAdd"
+ @refresh="getData"
+ >
+ <template #prefix>
+ <NButton v-if="!isCollapse" :disabled="!data.length" size="small" @click="expandAll">
+ <template #icon>
+ <icon-quill-expand />
+ </template>
+ {{ $t('page.system.dept.expandAll') }}
+ </NButton>
+ <NButton v-if="isCollapse" :disabled="!data.length" size="small" @click="collapseAll">
+ <template #icon>
+ <icon-quill-collapse />
+ </template>
+ {{ $t('page.system.dept.collapseAll') }}
+ </NButton>
+ </template>
+ </TableHeaderOperation>
+ </template>
+ <NDataTable
+ v-model:expanded-row-keys="expandedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ :row-key="row => row.deptId"
+ class="sm:h-full"
+ />
+ <DeptOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getData"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/dept/modules/dept-operate-drawer.vue b/ruoyi-plus-soybean/src/views/system/dept/modules/dept-operate-drawer.vue
new file mode 100755
index 0000000..428be24
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/dept/modules/dept-operate-drawer.vue
@@ -0,0 +1,248 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { NInputNumber } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateDept, fetchGetDeptList, fetchGetExcludeDeptList, fetchUpdateDept } from '@/service/api/system/dept';
+import { fetchGetDeptUserList } from '@/service/api/system/user';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { handleTree } from '@/utils/common';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'DeptOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.System.Dept | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule, patternRules } = useFormRules();
+
+const { loading: deptLoading, startLoading: startDeptLoading, endLoading: endDeptLoading } = useLoading();
+const { loading: userLoading, startLoading: startUserLoading, endLoading: endUserLoading } = useLoading();
+const deptData = ref<Api.System.Dept[]>([]);
+const userOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
+const placeholder = ref<string>($t('page.system.dept.placeholder.defaultLeaderPlaceHolder'));
+const disabled = ref<boolean>(false);
+const expandedKeys = ref<CommonType.IdType[]>([]);
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: $t('page.system.dept.addDept'),
+ edit: $t('page.system.dept.editDept')
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.System.DeptOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ parentId: props.rowData?.deptId || '',
+ deptName: '',
+ deptCategory: '',
+ orderNum: null,
+ leader: null,
+ phone: '',
+ email: '',
+ status: '0'
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'deptId' | 'parentId' | 'orderNum' | 'deptName' | 'phone' | 'email'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ deptId: createRequiredRule($t('page.system.dept.form.deptId.invalid')),
+ parentId: createRequiredRule($t('page.system.dept.form.parentId.invalid')),
+ orderNum: createRequiredRule($t('page.system.dept.form.orderNum.invalid')),
+ deptName: createRequiredRule($t('page.system.dept.form.deptName.invalid')),
+ phone: patternRules.phone,
+ email: patternRules.email
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { deptId, parentId, deptName, deptCategory, orderNum, leader, phone, email, status } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateDept({
+ parentId,
+ deptName,
+ deptCategory,
+ orderNum,
+ leader,
+ phone,
+ email,
+ status
+ });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateDept({
+ deptId,
+ parentId,
+ deptName,
+ deptCategory,
+ orderNum,
+ leader,
+ phone,
+ email,
+ status
+ });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+async function getDeptData() {
+ startDeptLoading();
+ const { data, error } =
+ props.operateType === 'add' ? await fetchGetDeptList() : await fetchGetExcludeDeptList(props.rowData?.deptId);
+
+ if (error) {
+ window.$message?.error(error.message || $t('page.system.dept.error.getDeptDataFail'));
+ return;
+ }
+
+ if (data) {
+ const { tree } = handleTree(data, { idField: 'deptId' });
+ deptData.value = tree;
+ if (deptData.value?.length) {
+ expandedKeys.value = [deptData.value[0].deptId];
+ }
+ }
+ endDeptLoading();
+}
+
+async function getUserData() {
+ if (props.operateType === 'add' || !props.rowData?.deptId) {
+ placeholder.value = $t('page.system.dept.placeholder.addDataLeaderPlaceHolder');
+ disabled.value = true;
+ return;
+ }
+ startUserLoading();
+ const { data, error } = await fetchGetDeptUserList(props.rowData.deptId);
+ if (error) {
+ window.$message?.error(error.message || $t('page.system.dept.error.getDeptUserDataFail'));
+ return;
+ }
+ if (data.length === 0) {
+ placeholder.value = $t('page.system.dept.placeholder.deptUserIsEmptyLeaderPlaceHolder');
+ disabled.value = true;
+ }
+ userOptions.value = data.map(item => ({
+ label: `${item.userName} | ${item.nickName}`,
+ value: item.userId
+ }));
+ endUserLoading();
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ disabled.value = false;
+ getDeptData();
+ getUserData();
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem v-if="model.parentId !== 0" :label="$t('page.system.dept.parentId')" path="parentId">
+ <NTreeSelect
+ v-model:value="model.parentId"
+ v-model:expanded-keys="expandedKeys"
+ :loading="deptLoading"
+ clearable
+ :options="deptData"
+ label-field="deptName"
+ key-field="deptId"
+ :placeholder="$t('page.system.dept.form.parentId.required')"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dept.deptName')" path="deptName">
+ <NInput v-model:value="model.deptName" :placeholder="$t('page.system.dept.form.deptName.required')" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dept.orderNum')" path="orderNum">
+ <NInputNumber
+ v-model:value="model.orderNum"
+ class="w-full"
+ :placeholder="$t('page.system.dept.form.orderNum.required')"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dept.deptCategory')" path="deptCategory">
+ <NInput v-model:value="model.deptCategory" :placeholder="$t('page.system.dept.form.deptCategory.required')" />
+ </NFormItem>
+
+ <NFormItem :label="$t('page.system.dept.leader')" path="leader">
+ <NSelect
+ v-model:value="model.leader"
+ :loading="userLoading"
+ :disabled="disabled"
+ :placeholder="placeholder"
+ :options="userOptions"
+ />
+ </NFormItem>
+
+ <NFormItem :label="$t('page.system.dept.phone')" path="phone">
+ <NInput v-model:value="model.phone" :placeholder="$t('page.system.dept.form.phone.required')" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dept.email')" path="email">
+ <NInput v-model:value="model.email" :placeholder="$t('page.system.dept.form.email.required')" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dept.status')" path="status">
+ <DictRadio v-model:value="model.status" dict-code="sys_normal_disable" />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/dept/modules/dept-search.vue b/ruoyi-plus-soybean/src/views/system/dept/modules/dept-search.vue
new file mode 100755
index 0000000..8a0036c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/dept/modules/dept-search.vue
@@ -0,0 +1,82 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { useDict } from '@/hooks/business/dict';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'DeptSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const { options: sysNormalDisableOptions } = useDict('sys_normal_disable', false);
+
+const model = defineModel<Api.System.DeptSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:8" :label="$t('page.system.dept.deptName')" path="deptName" class="pr-24px">
+ <NInput v-model:value="model.deptName" :placeholder="$t('page.system.dept.form.deptName.required')" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8 " :label="$t('page.system.dept.status')" path="status" class="pr-24px">
+ <NSelect
+ v-model:value="model.status"
+ :placeholder="$t('page.system.dept.form.status.required')"
+ :options="sysNormalDisableOptions"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/dict/index.vue b/ruoyi-plus-soybean/src/views/system/dict/index.vue
new file mode 100755
index 0000000..b1305a8
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/dict/index.vue
@@ -0,0 +1,519 @@
+<script setup lang="tsx">
+import { computed, ref } from 'vue';
+import { useRoute } from 'vue-router';
+import type { TreeOption } from 'naive-ui';
+import { NDivider, NEllipsis, NTooltip } from 'naive-ui';
+import { useBoolean, useLoading } from '@sa/hooks';
+import {
+ fetchBatchDeleteDictData,
+ fetchBatchDeleteDictType,
+ fetchGetDictDataList,
+ fetchGetDictTypeOption,
+ fetchRefreshCache
+} from '@/service/api/system';
+import { useAppStore } from '@/store/modules/app';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { handleCopy } from '@/utils/copy';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import { $t } from '@/locales';
+import DictTag from '@/components/custom/dict-tag.vue';
+import DictDataSearch from './modules/dict-data-search.vue';
+import DictDataOperateDrawer from './modules/dict-data-operate-drawer.vue';
+import DictTypeOperateDrawer from './modules/dict-type-operate-drawer.vue';
+
+defineOptions({
+ name: 'DictList'
+});
+
+useDict('sys_user_sex');
+
+const { hasAuth } = useAuth();
+const appStore = useAppStore();
+const { download } = useDownload();
+const route = useRoute();
+
+const selectedKeys = ref<string[]>([]);
+const dictTypeData = ref<Api.System.DictType>();
+const dictOperateType = ref<NaiveUI.TableOperateType>('add');
+const { bool: dictTypeDrawerVisible, setTrue: openDictTypeDrawer } = useBoolean();
+
+const searchParams = ref<Api.System.DictDataSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ dictLabel: null,
+ dictType: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetDictDataList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'dictLabel',
+ title: $t('page.system.dict.data.label'),
+ align: 'center',
+ minWidth: 80,
+ resizable: true,
+ ellipsis: {
+ tooltip: true
+ },
+ render(row) {
+ return <DictTag size="small" dictData={row} />;
+ }
+ },
+ {
+ key: 'dictValue',
+ title: $t('page.system.dict.data.value'),
+ align: 'center',
+ minWidth: 80,
+ resizable: true,
+ ellipsis: {
+ tooltip: true
+ }
+ },
+ {
+ key: 'dictSort',
+ title: $t('page.system.dict.data.dictSort'),
+ align: 'center',
+ minWidth: 80,
+ resizable: true,
+ ellipsis: {
+ tooltip: true
+ }
+ },
+ {
+ key: 'remark',
+ title: $t('page.system.dict.data.remark'),
+ align: 'center',
+ minWidth: 80,
+ resizable: true,
+ ellipsis: {
+ tooltip: true
+ }
+ },
+ {
+ key: 'createTime',
+ title: $t('page.system.dict.data.createTime'),
+ align: 'center',
+ minWidth: 80,
+ resizable: true,
+ ellipsis: {
+ tooltip: true
+ }
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 160,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('system:dict:edit') || !hasAuth('system:dict:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('system:dict:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.dictCode!)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('system:dict:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.dictCode!)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'dictCode', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteDictData(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(dictCode: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteDictData([dictCode]);
+ if (error) return;
+ onDeleted();
+}
+
+async function edit(dictCode: CommonType.IdType) {
+ handleEdit(dictCode);
+}
+
+async function handleExport() {
+ download('/system/dict/data/export', searchParams.value, `瀛楀吀鏁版嵁_${new Date().getTime()}.xlsx`);
+}
+
+async function handleResetSearch() {
+ searchParams.value.dictLabel = null;
+ selectedKeys.value = [];
+ await getDataByPage();
+}
+
+async function handleRefreshCache() {
+ const { error } = await fetchRefreshCache();
+ if (error) return;
+ window.$message?.success($t('page.system.dict.refreshCacheSuccess'));
+ await getData();
+}
+
+const { loading: treeLoading, startLoading: startTreeLoading, endLoading: endTreeLoading } = useLoading();
+const dictPattern = ref<string>();
+const dictData = ref<Api.System.DictType[]>([]);
+
+function dictFilter(pattern: string, node: TreeOption) {
+ const dictName = node.dictName as string;
+ const dictType = node.dictType as string;
+ return dictName.includes(pattern) || dictType.includes(pattern);
+}
+
+async function getTreeData() {
+ startTreeLoading();
+ const { data: tree, error } = await fetchGetDictTypeOption();
+ if (!error) {
+ dictData.value = tree;
+ handleClickTree(route.query.dictType ? [route.query.dictType as string] : []);
+ }
+ endTreeLoading();
+}
+
+getTreeData();
+
+function handleClickTree(keys: string[]) {
+ const dictType = keys.length ? keys[0] : null;
+ selectedKeys.value = keys;
+ searchParams.value.dictType = dictType;
+ window.history.pushState(null, '', `${route.path}${dictType ? `?dictType=${dictType}` : ''}`);
+ checkedRowKeys.value = [];
+ getDataByPage();
+}
+
+function handleResetTreeData() {
+ dictPattern.value = '';
+ getTreeData();
+}
+
+function renderLabel({ option }: { option: TreeOption }) {
+ return (
+ <NTooltip placement="left">
+ {{
+ trigger: () => (
+ <div class="w-200px flex gap-6px overflow-hidden text-ellipsis whitespace-nowrap">
+ <span>{option.dictName}</span>
+ <span class="text-12px text-gray-500">( {option.dictType} )</span>
+ </div>
+ ),
+ default: () => (
+ <div class="flex-col">
+ <span>
+ {option.dictName} {option.dictType}
+ </span>
+ {option.remark ? <span>( {option.remark} )</span> : null}
+ <span>{option.createTime}</span>
+ </div>
+ )
+ }}
+ </NTooltip>
+ );
+}
+
+function renderSuffix({ option }: { option: TreeOption }) {
+ return (
+ <div class="flex-center gap-12px">
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltip-content={$t('common.edit')}
+ onClick={(event: Event) => {
+ event.stopPropagation();
+ handleEditType(option as Api.System.DictType);
+ }}
+ />
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltip-content={$t('common.delete')}
+ popconfirm-content={`${$t('page.system.dict.confirmDeleteDictType')} ${option.dictType} 锛焋}
+ onClick={(event: Event) => event.stopPropagation()}
+ onPositiveClick={() => handleDeleteType(option as Api.System.DictType)}
+ />
+ </div>
+ );
+}
+
+function handleAddType() {
+ dictTypeData.value = undefined;
+ dictOperateType.value = 'add';
+ openDictTypeDrawer();
+}
+
+function handleEditType(dictType: Api.System.DictType) {
+ dictTypeData.value = dictType;
+ dictOperateType.value = 'edit';
+ openDictTypeDrawer();
+}
+
+async function handleDeleteType(dictType: Api.System.DictType) {
+ const { error } = await fetchBatchDeleteDictType([dictType.dictId]);
+ if (error) return;
+ window.$message?.success($t('common.deleteSuccess'));
+ getTreeData();
+}
+
+async function handleExportType() {
+ download(
+ '/system/dict/type/export',
+ searchParams.value,
+ `${$t('page.system.dict.dictType')}_${new Date().getTime()}.xlsx`
+ );
+}
+
+const selectable = computed(() => {
+ return !loading.value;
+});
+
+const tableTitle = computed(() => {
+ const dictType = dictData.value.find(item => item.dictType === searchParams.value.dictType);
+ return dictType ? (
+ <NEllipsis lineClamp={2} class="flex">
+ <span>{dictType.dictName}</span>
+ <span class="cursor-copy" onClick={async () => await handleCopy(dictType.dictType)}>
+ {` (${dictType.dictType} )`}
+ </span>
+ </NEllipsis>
+ ) : (
+ <div>{$t('page.system.dict.title')}</div>
+ );
+});
+</script>
+
+<template>
+ <TableSiderLayout :sider-title="$t('page.system.dict.dictTypeTitle')">
+ <template #header-extra>
+ <ButtonIcon
+ v-if="hasAuth('system:dict:add')"
+ size="small"
+ icon="material-symbols:add-rounded"
+ class="h-18px text-icon"
+ :tooltip-content="$t('page.system.dict.addDictType')"
+ @click.stop="() => handleAddType()"
+ />
+ <ButtonIcon
+ v-if="hasAuth('system:dict:export')"
+ size="small"
+ icon="material-symbols:download-rounded"
+ class="h-18px text-icon"
+ :tooltip-content="$t('page.system.dict.exportDictType')"
+ @click.stop="() => handleExportType()"
+ />
+ <ButtonIcon
+ size="small"
+ icon="material-symbols:refresh-rounded"
+ class="h-18px text-icon"
+ :tooltip-content="$t('page.system.dict.refreshDictType')"
+ @click.stop="() => handleResetTreeData()"
+ />
+ </template>
+ <template #sider>
+ <NInput v-model:value="dictPattern" clearable :placeholder="$t('common.keywordSearch')" />
+ <NSpin class="dict-tree" :show="treeLoading">
+ <NTree
+ v-model:selected-keys="selectedKeys"
+ block-node
+ show-line
+ :data="dictData as []"
+ :show-irrelevant-nodes="false"
+ :pattern="dictPattern"
+ :filter="dictFilter"
+ class="infinite-scroll h-full min-h-200px py-3"
+ key-field="dictType"
+ label-field="dictName"
+ virtual-scroll
+ :selectable="selectable"
+ :render-label="renderLabel"
+ :render-suffix="renderSuffix"
+ @update:selected-keys="handleClickTree"
+ >
+ <template #empty>
+ <NEmpty :description="$t('page.system.dict.dictTypeIsEmpty')" class="h-full min-h-200px justify-center" />
+ </template>
+ </NTree>
+ </NSpin>
+ </template>
+ <div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto">
+ <DictDataSearch v-model:model="searchParams" @reset="handleResetSearch" @search="getDataByPage" />
+ <TableRowCheckAlert v-model:checked-row-keys="checkedRowKeys" />
+ <NCard :title="() => tableTitle" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :disable-add="!searchParams.dictType"
+ :loading="loading"
+ :show-add="hasAuth('system:user:add')"
+ :show-delete="hasAuth('system:user:remove')"
+ :show-export="hasAuth('system:user:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @refresh="getData"
+ @export="handleExport"
+ >
+ <template #prefix>
+ <NButton ghost size="small" @click="handleRefreshCache">
+ <template #icon>
+ <icon-material-symbols-refresh-rounded class="text-icon" />
+ </template>
+ {{ $t('page.system.dict.refreshCache') }}
+ </NButton>
+ </template>
+ </TableHeaderOperation>
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.dictCode"
+ :pagination="mobilePagination"
+ class="h-full"
+ />
+ <DictDataOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ :dict-type="searchParams.dictType || ''"
+ @submitted="getData"
+ />
+ <DictTypeOperateDrawer
+ v-model:visible="dictTypeDrawerVisible"
+ :operate-type="dictOperateType"
+ :row-data="dictTypeData"
+ @submitted="getTreeData"
+ />
+ </NCard>
+ </div>
+ </TableSiderLayout>
+</template>
+
+<style scoped lang="scss">
+.dict-tree {
+ .n-button {
+ --n-padding: 8px !important;
+ }
+
+ :deep(.n-tree__empty) {
+ height: 100%;
+ justify-content: center;
+ }
+
+ :deep(.n-spin-content) {
+ height: 100%;
+ }
+
+ :deep(.infinite-scroll) {
+ height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
+ max-height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
+ }
+
+ @media screen and (max-width: 1024px) {
+ :deep(.infinite-scroll) {
+ height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
+ max-height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
+ }
+ }
+
+ :deep(.n-tree-node) {
+ height: 30px;
+ }
+
+ :deep(.n-tree-node-switcher) {
+ height: 30px;
+ }
+
+ :deep(.n-tree-node-switcher__icon) {
+ font-size: 16px !important;
+ height: 16px !important;
+ width: 16px !important;
+ }
+}
+
+:deep(.n-data-table-wrapper),
+:deep(.n-data-table-base-table),
+:deep(.n-data-table-base-table-body) {
+ height: 100%;
+}
+
+@media screen and (max-width: 800px) {
+ :deep(.n-data-table-base-table-body) {
+ max-height: calc(100vh - 400px - var(--calc-footer-height, 0px));
+ }
+}
+
+@media screen and (max-width: 802px) {
+ :deep(.n-data-table-base-table-body) {
+ max-height: calc(100vh - 473px - var(--calc-footer-height, 0px));
+ }
+}
+
+:deep(.n-card-header__main) {
+ min-width: 180px !important;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/system/dict/modules/dict-data-operate-drawer.vue b/ruoyi-plus-soybean/src/views/system/dict/modules/dict-data-operate-drawer.vue
new file mode 100755
index 0000000..626d811
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/dict/modules/dict-data-operate-drawer.vue
@@ -0,0 +1,206 @@
+<script setup lang="tsx">
+import { computed, ref, watch } from 'vue';
+import { NTag } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateDictData, fetchUpdateDictData } from '@/service/api/system/dict-data';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { useDict } from '@/hooks/business/dict';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'DictDataOperateDrawer'
+});
+useDict('sys_yes_no');
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.System.DictData | null;
+ dictType: string;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: $t('page.system.dict.addDictData'),
+ edit: $t('page.system.dict.editDictData')
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.System.DictDataOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+const listClassOptions: Record<string, string>[] = [
+ { label: 'Text', value: 'text' },
+ { label: 'Default', value: 'default' },
+ { label: 'Tertiary', value: 'tertiary' },
+ { label: 'Primary', value: 'primary' },
+ { label: 'Info', value: 'info' },
+ { label: 'Success', value: 'success' },
+ { label: 'Warning', value: 'warning' },
+ { label: 'Error', value: 'error' }
+];
+
+function createDefaultModel(): Model {
+ return {
+ dictSort: 0,
+ dictLabel: '',
+ dictValue: '',
+ dictType: props.dictType,
+ cssClass: '',
+ listClass: null,
+ remark: '',
+ isDefault: 'N'
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'dictCode' | 'dictLabel' | 'dictValue'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ dictCode: createRequiredRule($t('page.system.dict.form.dictCode.invalid')),
+ dictLabel: createRequiredRule($t('page.system.dict.form.dictLabel.invalid')),
+ dictValue: createRequiredRule($t('page.system.dict.form.dictValue.invalid'))
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { dictCode, dictSort, dictLabel, dictValue, dictType, cssClass, listClass, isDefault, remark } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateDictData({
+ dictSort,
+ dictLabel,
+ dictValue,
+ dictType,
+ cssClass,
+ listClass,
+ isDefault,
+ remark
+ });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateDictData({
+ dictCode,
+ dictSort,
+ dictLabel,
+ dictValue,
+ dictType,
+ cssClass,
+ listClass,
+ isDefault,
+ remark
+ });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+
+function renderTagLabel(option: { label: string; value: string }) {
+ if (option.value === 'text') {
+ return option.label;
+ }
+ return (
+ <NTag size="small" type={option.value as any}>
+ {option.label}
+ </NTag>
+ );
+}
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem :label="$t('page.system.dict.dictType')" path="dictType">
+ <NInput
+ v-model:value="model.dictType"
+ disabled
+ :placeholder="$t('page.system.dict.form.dictType.required')"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dict.data.listClass')" path="listClass">
+ <NSelect
+ v-model:value="model.listClass"
+ clearable
+ :options="listClassOptions"
+ :placeholder="$t('page.system.dict.form.listClass.required')"
+ :render-label="renderTagLabel"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dict.data.label')" path="dictLabel">
+ <NInput v-model:value="model.dictLabel" :placeholder="$t('page.system.dict.form.dictLabel.required')" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dict.data.value')" path="dictValue">
+ <NInput v-model:value="model.dictValue" :placeholder="$t('page.system.dict.form.dictValue.required')" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dict.data.cssClass')" path="cssClass">
+ <NInput v-model:value="model.cssClass" :placeholder="$t('page.system.dict.form.cssClass.required')" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dict.data.dictSort')" path="dictSort">
+ <NInputNumber v-model:value="model.dictSort" :placeholder="$t('page.system.dict.form.dictSort.required')" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dict.data.isDefault')" path="isDefault">
+ <DictRadio v-model:value="model.isDefault" dict-code="sys_yes_no" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dict.data.remark')" path="remark">
+ <NInput
+ v-model:value="model.remark"
+ :rows="3"
+ type="textarea"
+ :placeholder="$t('page.system.dict.form.remark.required')"
+ />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/dict/modules/dict-data-search.vue b/ruoyi-plus-soybean/src/views/system/dict/modules/dict-data-search.vue
new file mode 100755
index 0000000..17f043c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/dict/modules/dict-data-search.vue
@@ -0,0 +1,77 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'DictDataSearch'
+});
+
+interface Emits {
+ (e: 'reset'): void;
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.System.DictDataSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('reset');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi
+ span="24 s:12 m:12"
+ :label="$t('page.system.dict.data.label')"
+ path="dictLabel"
+ class="pr-24px"
+ >
+ <NInput v-model:value="model.dictLabel" :placeholder="$t('page.system.dict.form.dictLabel.required')" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:12" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/dict/modules/dict-type-operate-drawer.vue b/ruoyi-plus-soybean/src/views/system/dict/modules/dict-type-operate-drawer.vue
new file mode 100755
index 0000000..653a110
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/dict/modules/dict-type-operate-drawer.vue
@@ -0,0 +1,132 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateDictType, fetchUpdateDictType } from '@/service/api/system/dict';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'DictTypeOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.System.DictType | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: $t('page.system.dict.addDictType'),
+ edit: $t('page.system.dict.editDictType')
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.System.DictTypeOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ dictName: '',
+ dictType: '',
+ remark: ''
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'dictId' | 'dictName' | 'dictType'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ dictId: createRequiredRule($t('page.system.dict.form.dictValue.invalid')),
+ dictName: createRequiredRule($t('page.system.dict.form.dictName.invalid')),
+ dictType: createRequiredRule($t('page.system.dict.form.dictType.invalid'))
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { dictId, dictName, dictType, remark } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateDictType({ dictName, dictType, remark });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateDictType({ dictId, dictName, dictType, remark });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem :label="$t('page.system.dict.dictName')" path="dictName">
+ <NInput v-model:value="model.dictName" :placeholder="$t('page.system.dict.form.dictName.required')" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dict.dictType')" path="dictType">
+ <NInput v-model:value="model.dictType" :placeholder="$t('page.system.dict.form.dictValue.required')" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.dict.remark')" path="remark">
+ <NInput
+ v-model:value="model.remark"
+ :rows="3"
+ type="textarea"
+ :placeholder="$t('page.system.dict.form.remark.required')"
+ />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/menu/index.vue b/ruoyi-plus-soybean/src/views/system/menu/index.vue
new file mode 100755
index 0000000..a801866
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/menu/index.vue
@@ -0,0 +1,581 @@
+<script setup lang="tsx">
+import { computed, ref } from 'vue';
+import type { DataTableColumns, TreeInst, TreeOption } from 'naive-ui';
+import { NButton, NDivider, NIcon, NInput, NPopconfirm } from 'naive-ui';
+import { useBoolean, useLoading } from '@sa/hooks';
+import { menuIsFrameRecord, menuTypeRecord } from '@/constants/business';
+import { fetchDeleteMenu, fetchGetMenuList } from '@/service/api/system';
+import { useAppStore } from '@/store/modules/app';
+import { useDict } from '@/hooks/business/dict';
+import { useAuth } from '@/hooks/business/auth';
+import { handleTree } from '@/utils/common';
+import { $t } from '@/locales';
+import SvgIcon from '@/components/custom/svg-icon.vue';
+import DictTag from '@/components/custom/dict-tag.vue';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import MenuOperateDrawer from './modules/menu-operate-drawer.vue';
+import MenuCascadeDeleteModal from './modules/menu-cascade-delete-modal.vue';
+
+useDict('sys_show_hide');
+useDict('sys_normal_disable');
+
+const defaultIcon = import.meta.env.VITE_MENU_ICON;
+
+const { hasAuth } = useAuth();
+const appStore = useAppStore();
+const editingData = ref<Api.System.Menu>();
+const operateType = ref<NaiveUI.TableOperateType>('add');
+const { loading, startLoading, endLoading } = useLoading();
+const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
+const { bool: cascadeDeleteVisible, setTrue: openCascadeDeleteDrawer } = useBoolean();
+const { loading: btnLoading, startLoading: startBtnLoading, endLoading: endBtnLoading } = useLoading();
+/** tree pattern name , use tree search */
+const name = ref<string>();
+const createType = ref<Api.System.MenuType>();
+const createPid = ref<CommonType.IdType>(0);
+const currentMenu = ref<Api.System.Menu>();
+const treeData = ref<Api.System.Menu[]>([]);
+const checkedKeys = ref<CommonType.IdType[]>([0]);
+const expandedKeys = ref<CommonType.IdType[]>([0]);
+
+// 鏄惁涓虹洰褰曠被鍨�
+const isCatalog = computed(() => currentMenu.value?.menuType === 'M');
+
+// 鏄惁涓鸿彍鍗曠被鍨�
+const isMenu = computed(() => currentMenu.value?.menuType === 'C');
+
+// 澶栭摼绫诲瀷
+const isExternalType = computed(() => currentMenu.value?.isFrame === '0');
+
+// iframe绫诲瀷
+const isIframeType = computed(() => currentMenu.value?.isFrame === '2');
+
+const menuTreeRef = ref<TreeInst>();
+const btnData = ref<Api.System.MenuList>([]);
+
+const getMeunTree = async () => {
+ startLoading();
+ const { data, error } = await fetchGetMenuList();
+ if (error) return;
+ const { tree } = handleTree(data, { idField: 'menuId', filterFn: item => item.menuType !== 'F' });
+ treeData.value = [
+ {
+ menuId: 0,
+ menuName: $t('page.system.menu.rootName'),
+ icon: 'material-symbols:home-outline-rounded',
+ children: tree
+ }
+ ] as Api.System.Menu[];
+ endLoading();
+};
+
+getMeunTree();
+
+async function handleSubmitted(menuType?: Api.System.MenuType) {
+ if (menuType === 'F') {
+ await getBtnMenuList();
+ return;
+ }
+ await getMeunTree();
+ if (operateType.value === 'edit') {
+ currentMenu.value = menuTreeRef.value?.getCheckedData().options[0] as Api.System.Menu;
+ }
+}
+
+function handleAddMenu(pid: CommonType.IdType) {
+ createPid.value = pid;
+ createType.value = pid === 0 ? 'M' : 'C';
+ operateType.value = 'add';
+ openDrawer();
+}
+
+function handleUpdateMenu() {
+ operateType.value = 'edit';
+ editingData.value = currentMenu.value;
+ openDrawer();
+}
+
+async function handleDeleteMenu(id?: CommonType.IdType) {
+ const { error } = await fetchDeleteMenu(id || checkedKeys.value[0]);
+ if (error) return;
+ window.$message?.success($t('common.deleteSuccess'));
+ if (id) {
+ getBtnMenuList();
+ return;
+ }
+ expandedKeys.value.filter(item => !checkedKeys.value.includes(item));
+ currentMenu.value = undefined;
+ checkedKeys.value = [];
+ getMeunTree();
+}
+
+function renderLabel({ option }: { option: TreeOption }) {
+ let label = String(option.menuName);
+ if (label?.startsWith('route.') || label?.startsWith('menu.')) {
+ label = $t(label as App.I18n.I18nKey);
+ }
+ // 绂佺敤鐨勮彍鍗曟樉绀虹孩鑹�
+ if (option.status === '1') {
+ return (
+ <div class="flex items-center gap-4px text-error-200">
+ {label}
+ <SvgIcon icon="ri:prohibited-line" class="text-16px" />
+ </div>
+ );
+ }
+ // 闅愯棌鐨勮彍鍗曟樉绀虹伆鑹�
+ if (option.visible === '1') {
+ return (
+ <div class="flex items-center gap-4px text-gray-400">
+ {label}
+ <SvgIcon icon="codex:hidden" class="text-21px" />
+ </div>
+ );
+ }
+ return <div>{label}</div>;
+}
+
+function renderPrefix({ option }: { option: TreeOption }) {
+ const renderLocalIcon = String(option.icon).startsWith('local-icon-');
+ const icon = renderLocalIcon ? undefined : String(option.icon);
+ const localIcon = renderLocalIcon ? String(option.icon).replace('local-icon-', 'menu-') : undefined;
+ return <SvgIcon icon={icon || defaultIcon} localIcon={localIcon} />;
+}
+
+function renderSuffix({ option }: { option: TreeOption }) {
+ if (!['M'].includes(String(option.menuType)) || !hasAuth('system:menu:add')) {
+ return null;
+ }
+
+ return (
+ <div class="flex-center gap-8px">
+ <ButtonIcon
+ text
+ class="h-18px"
+ icon="ic-round-plus"
+ tooltip-content={$t('page.system.menu.addChildMenu')}
+ onClick={(event: Event) => {
+ event.stopPropagation();
+ handleAddMenu(option.menuId as CommonType.IdType);
+ }}
+ />
+ </div>
+ );
+}
+
+function reset() {
+ name.value = undefined;
+ getMeunTree();
+}
+
+function handleClickTree(option: Array<TreeOption | null>) {
+ checkedKeys.value = option?.map(item => item?.menuId as CommonType.IdType);
+
+ const menu = option[0] as Api.System.Menu;
+ if (menu?.menuId === 0) {
+ return;
+ }
+ currentMenu.value = menu;
+ getBtnMenuList();
+}
+
+const tagMap: Record<'0' | '1' | '2', NaiveUI.ThemeColor> = {
+ '0': 'success',
+ '1': 'warning',
+ '2': 'primary'
+};
+
+let controller = new AbortController();
+
+async function getBtnMenuList() {
+ if (!currentMenu.value?.menuId) {
+ return;
+ }
+ controller.abort();
+ controller = new AbortController();
+ startBtnLoading();
+ btnData.value = [];
+ const { data, error } = await fetchGetMenuList(
+ { parentId: currentMenu.value?.menuId, menuType: 'F' },
+ controller.signal
+ );
+ if (error) return;
+ btnData.value = data || [];
+ endBtnLoading();
+}
+
+function addBtnMenu() {
+ operateType.value = 'add';
+ createType.value = 'F';
+ createPid.value = currentMenu.value?.menuId || 0;
+ openDrawer();
+}
+
+function handleDeleteBtnMenu(id: CommonType.IdType) {
+ handleDeleteMenu(id);
+}
+
+function handleUpdateBtnMenu(row: Api.System.Menu) {
+ operateType.value = 'edit';
+ editingData.value = row;
+ openDrawer();
+}
+
+const btnColumns: DataTableColumns<Api.System.Menu> = [
+ {
+ key: 'index',
+ width: 64,
+ align: 'center',
+ title() {
+ return (
+ <NButton circle type="primary" size="small" onClick={() => addBtnMenu()}>
+ {{
+ icon: () => (
+ <NIcon>
+ <SvgIcon icon="ic-round-plus" />
+ </NIcon>
+ )
+ }}
+ </NButton>
+ );
+ },
+ render(_, index) {
+ return index + 1;
+ }
+ },
+ {
+ title: $t('page.system.menu.menuName'),
+ key: 'menuName',
+ minWidth: 120
+ },
+ {
+ title: $t('page.system.menu.perms'),
+ key: 'perms',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ title: $t('page.system.menu.status'),
+ key: 'status',
+ minWidth: 80,
+ align: 'center',
+ render(row) {
+ return <DictTag size="small" value={row.status} dictCode="sys_normal_disable" />;
+ }
+ },
+ {
+ title: $t('page.system.menu.createTime'),
+ key: 'createTime',
+ align: 'center',
+ minWidth: 150
+ },
+ {
+ title: $t('common.action'),
+ key: 'actions',
+ width: 80,
+ align: 'center',
+ render(row) {
+ const divider = () => {
+ if (!hasAuth('system:menu:edit') || !hasAuth('system:menu:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('system:menu:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => handleUpdateBtnMenu(row)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('system:menu:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDeleteBtnMenu(row.menuId!)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+];
+
+function renderMenuName(menuName: string) {
+ return menuName?.startsWith('route.') || menuName?.startsWith('menu.') ? $t(menuName as App.I18n.I18nKey) : menuName;
+}
+
+const renderIframeQuery = (queryParam: string) => {
+ try {
+ return JSON.parse(queryParam || '{}')?.url;
+ } catch {
+ return queryParam;
+ }
+};
+</script>
+
+<template>
+ <TableSiderLayout default-expanded>
+ <template #header>{{ $t('page.system.menu.title') }}</template>
+ <template #header-extra>
+ <ButtonIcon
+ v-if="hasAuth('system:menu:add')"
+ size="small"
+ icon="material-symbols:add-rounded"
+ class="h-28px text-icon color-primary"
+ :tooltip-content="$t('page.system.menu.addMenu')"
+ @click.stop="handleAddMenu(0)"
+ />
+ <ButtonIcon
+ v-if="hasAuth('system:menu:add')"
+ size="small"
+ icon="material-symbols:delete-outline"
+ class="h-28px text-icon color-error"
+ :tooltip-content="$t('page.system.menu.cascadeDelete')"
+ @click.stop="openCascadeDeleteDrawer"
+ />
+ <ButtonIcon
+ size="small"
+ icon="material-symbols:refresh-rounded"
+ class="h-28px text-icon"
+ :tooltip-content="$t('common.refresh')"
+ @click.stop="reset"
+ />
+ </template>
+ <template #sider>
+ <div class="flex gap-6px">
+ <NInput v-model:value="name" size="small" :placeholder="$t('page.system.menu.form.menuName.required')" />
+ </div>
+ <NSpin :show="loading" class="infinite-scroll">
+ <NTree
+ ref="menuTreeRef"
+ v-model:checked-keys="checkedKeys"
+ v-model:expanded-keys="expandedKeys"
+ :cancelable="false"
+ block-node
+ show-line
+ :data="treeData as []"
+ :default-expanded-keys="[0]"
+ :show-irrelevant-nodes="false"
+ :pattern="name"
+ class="menu-tree h-full min-h-200px py-3"
+ key-field="menuId"
+ label-field="menuName"
+ virtual-scroll
+ checkable
+ :render-label="renderLabel"
+ :render-prefix="renderPrefix"
+ :render-suffix="renderSuffix"
+ @update:selected-keys="(_: Array<string & number>, option: Array<TreeOption | null>) => handleClickTree(option)"
+ >
+ <template #empty>
+ <NEmpty :description="$t('page.system.menu.emptyMenu')" class="h-full min-h-200px justify-center" />
+ </template>
+ </NTree>
+ </NSpin>
+ </template>
+ <div class="h-full flex-col-stretch gap-16px">
+ <template v-if="currentMenu">
+ <NCard
+ :title="$t('page.system.menu.menuDetail')"
+ :bordered="false"
+ size="small"
+ class="max-h-50% card-wrapper"
+ content-class="overflow-auto mb-12px"
+ >
+ <template #header-extra>
+ <NSpace>
+ <NButton
+ v-if="isCatalog && hasAuth('system:menu:add')"
+ size="small"
+ ghost
+ type="primary"
+ @click="handleAddMenu(currentMenu.menuId!)"
+ >
+ <template #icon>
+ <icon-material-symbols-add-rounded />
+ </template>
+ {{ $t('page.system.menu.addChildMenu') }}
+ </NButton>
+ <NButton v-if="hasAuth('system:menu:edit')" size="small" ghost type="primary" @click="handleUpdateMenu">
+ <template #icon>
+ <icon-material-symbols-drive-file-rename-outline-outline />
+ </template>
+ {{ $t('common.edit') }}
+ </NButton>
+ <NPopconfirm @positive-click="() => handleDeleteMenu()">
+ <template #trigger>
+ <NButton
+ v-if="hasAuth('system:menu:remove')"
+ size="small"
+ ghost
+ type="error"
+ :disabled="btnData.length > 0 || btnLoading"
+ >
+ <template #icon>
+ <icon-material-symbols-delete-outline />
+ </template>
+ {{ $t('common.delete') }}
+ </NButton>
+ </template>
+ {{ $t('common.confirmDelete') }}
+ </NPopconfirm>
+ </NSpace>
+ </template>
+ <NDescriptions
+ label-placement="left"
+ size="small"
+ bordered
+ :column="appStore.isMobile ? 1 : 2"
+ label-class="w-20% min-w-88px"
+ content-class="w-100px"
+ >
+ <NDescriptionsItem :label="$t('page.system.menu.menuType')">
+ <NTag class="m-1" size="small" type="primary">{{ menuTypeRecord[currentMenu.menuType!] }}</NTag>
+ </NDescriptionsItem>
+ <NDescriptionsItem :label="$t('page.system.menu.status')">
+ <DictTag size="small" :value="currentMenu.status" dict-code="sys_normal_disable" />
+ </NDescriptionsItem>
+ <NDescriptionsItem :label="$t('page.system.menu.menuName')">
+ {{ renderMenuName(currentMenu.menuName) }}
+ </NDescriptionsItem>
+ <NDescriptionsItem v-if="isMenu" :label="$t('page.system.menu.component')">
+ {{
+ currentMenu.component?.startsWith('layout.blank$view.')
+ ? `${currentMenu.component?.slice(18, currentMenu.component.length)?.replaceAll('_', '/')}/index`
+ : currentMenu.component
+ }}
+ </NDescriptionsItem>
+ <NDescriptionsItem
+ :label="!isExternalType ? $t('page.system.menu.path') : $t('page.system.menu.externalPath')"
+ >
+ {{ currentMenu.path }}
+ </NDescriptionsItem>
+ <NDescriptionsItem v-if="isMenu && !isExternalType && !isIframeType" :label="$t('page.system.menu.query')">
+ {{ currentMenu.queryParam }}
+ </NDescriptionsItem>
+ <NDescriptionsItem
+ v-if="isMenu && !isExternalType && isIframeType"
+ :label="$t('page.system.menu.iframeQuery')"
+ >
+ {{ renderIframeQuery(currentMenu.queryParam) }}
+ </NDescriptionsItem>
+ <NDescriptionsItem v-if="!isCatalog" :label="$t('page.system.menu.perms')">
+ {{ currentMenu.perms }}
+ </NDescriptionsItem>
+ <NDescriptionsItem :label="$t('page.system.menu.isFrame')">
+ <NTag v-if="currentMenu.isFrame" class="m-1" size="small" :type="tagMap[currentMenu.isFrame]">
+ {{ menuIsFrameRecord[currentMenu.isFrame] }}
+ </NTag>
+ </NDescriptionsItem>
+ <NDescriptionsItem :label="$t('page.system.menu.visible')">
+ <DictTag size="small" :value="currentMenu.visible" dict-code="sys_show_hide" />
+ </NDescriptionsItem>
+ <NDescriptionsItem v-if="isMenu" :label="$t('page.system.menu.isCache')">
+ <NTag v-if="currentMenu.isCache" class="m-1" size="small" :type="tagMap[currentMenu.isCache]">
+ {{ currentMenu.isCache === '0' ? $t('page.system.menu.cache') : $t('page.system.menu.noCache') }}
+ </NTag>
+ </NDescriptionsItem>
+ </NDescriptions>
+ </NCard>
+
+ <NCard
+ :title="$t('page.system.menu.buttonPermissionList')"
+ :bordered="false"
+ size="small"
+ class="h-full overflow-auto card-wrapper"
+ content-class="overflow-auto mb-12px"
+ >
+ <template #header-extra>
+ <ButtonIcon
+ size="small"
+ icon="ic-round-refresh"
+ class="h-28px text-icon"
+ :tooltip-content="$t('common.refresh')"
+ @click.stop="getBtnMenuList"
+ />
+ </template>
+ <NDataTable class="h-full" :loading="btnLoading" :columns="btnColumns" :data="btnData" />
+ </NCard>
+ </template>
+ <NCard v-else :bordered="false" size="small" class="h-full card-wrapper">
+ <NEmpty class="h-full flex-center" size="large" />
+ </NCard>
+ </div>
+ <MenuOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ :tree-data="treeData"
+ :pid="createPid"
+ :menu-type="createType"
+ @submitted="handleSubmitted"
+ />
+ <MenuCascadeDeleteModal v-model:visible="cascadeDeleteVisible" @submitted="handleSubmitted" />
+ </TableSiderLayout>
+</template>
+
+<style scoped lang="scss">
+:deep(.infinite-scroll) {
+ height: calc(100vh - 224px - var(--calc-footer-height, 0px)) !important;
+ max-height: calc(100vh - 224px - var(--calc-footer-height, 0px)) !important;
+}
+
+@media screen and (max-width: 1024px) {
+ :deep(.infinite-scroll) {
+ height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
+ max-height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
+ }
+}
+
+:deep(.n-spin-content) {
+ height: 100%;
+}
+
+:deep(.n-tree-node-checkbox) {
+ display: none;
+}
+
+:deep(.n-data-table-base-table) {
+ height: 100% !important;
+}
+
+.menu-tree {
+ :deep(.n-tree-node) {
+ height: 25px;
+ }
+
+ :deep(.n-tree-node-switcher) {
+ height: 25px;
+ }
+
+ :deep(.n-tree-node-switcher__icon) {
+ font-size: 16px !important;
+ height: 16px !important;
+ width: 16px !important;
+ }
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/system/menu/modules/menu-cascade-delete-modal.vue b/ruoyi-plus-soybean/src/views/system/menu/modules/menu-cascade-delete-modal.vue
new file mode 100755
index 0000000..307f755
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/menu/modules/menu-cascade-delete-modal.vue
@@ -0,0 +1,118 @@
+<script setup lang="ts">
+import { reactive, ref, watch } from 'vue';
+import { useLoading } from '@sa/hooks';
+import { fetchCascadeDeleteMenu } from '@/service/api/system';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+import MenuTree from '@/components/custom/menu-tree.vue';
+
+defineOptions({
+ name: 'MenuCascadeDeleteModal'
+});
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const menuTreeRef = ref<InstanceType<typeof MenuTree> | null>(null);
+const menuOptions = ref<Api.System.MenuList>([]);
+const { loading: menuLoading } = useLoading();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+type Model = {
+ menuIds: CommonType.IdType[];
+};
+
+const model: Model = reactive(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ menuIds: []
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'menuIds'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ menuIds: createRequiredRule($t('page.system.menu.form.menuIds.invalid'))
+};
+
+async function handleUpdateModelWhenEdit() {
+ menuOptions.value = [];
+ Object.assign(model, createDefaultModel());
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+ window.$dialog?.warning({
+ title: $t('page.system.menu.cascadeDelete'),
+ content: $t('page.system.menu.cascadeDeleteContent'),
+ positiveText: $t('common.delete'),
+ positiveButtonProps: {
+ type: 'error'
+ },
+ negativeText: $t('common.cancel'),
+ onPositiveClick: async () => {
+ const { error } = await fetchCascadeDeleteMenu(model.menuIds);
+ if (error) return;
+ window.$message?.success($t('common.deleteSuccess'));
+ closeDrawer();
+ emit('submitted');
+ }
+ });
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NModal
+ v-model:show="visible"
+ :title="$t('page.system.menu.cascadeDelete')"
+ preset="card"
+ :bordered="false"
+ display-directive="show"
+ class="max-w-90% w-500px"
+ @close="closeDrawer"
+ >
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem :show-label="false" path="menuIds">
+ <MenuTree
+ v-if="visible"
+ ref="menuTreeRef"
+ v-model:options="menuOptions"
+ v-model:loading="menuLoading"
+ v-model:checked-keys="model.menuIds"
+ :cascade="true"
+ :show-header="false"
+ :immediate="true"
+ />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace justify="end" :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NModal>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/menu/modules/menu-operate-drawer.vue b/ruoyi-plus-soybean/src/views/system/menu/modules/menu-operate-drawer.vue
new file mode 100755
index 0000000..ad7c197
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/menu/modules/menu-operate-drawer.vue
@@ -0,0 +1,511 @@
+<script setup lang="tsx">
+import { computed, ref, watch } from 'vue';
+import type { SelectOption } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import { menuIconTypeOptions, menuIsFrameOptions, menuLayoutOptions, menuTypeOptions } from '@/constants/business';
+import { fetchCreateMenu, fetchUpdateMenu } from '@/service/api/system';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { getLocalMenuIcons } from '@/utils/icon';
+import { isNotNull } from '@/utils/common';
+import { $t } from '@/locales';
+import SvgIcon from '@/components/custom/svg-icon.vue';
+
+defineOptions({
+ name: 'MenuOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.System.Menu | null;
+ /** tree option data */
+ treeData?: Api.System.Menu[] | null;
+ /** parent id */
+ pid?: string | number;
+ /** menu type */
+ menuType?: Api.System.MenuType;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted', menuType: Api.System.MenuType): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const defaultIcon = import.meta.env.VITE_MENU_ICON;
+
+const layoutType = ref<string>('0');
+const iconType = ref<Api.System.IconType>('1');
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule, createNumberRequiredRule } = useFormRules();
+const queryList = ref<{ key: string; value: string }[]>([]);
+
+const drawerTitle = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: $t('page.system.menu.addMenu'),
+ edit: $t('page.system.menu.editMenu')
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.System.MenuOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ parentId: props.pid || 0,
+ menuName: '',
+ orderNum: 1,
+ path: '',
+ component: '',
+ queryParam: '',
+ isFrame: '1',
+ isCache: '1',
+ menuType: props.menuType || 'M',
+ visible: '0',
+ status: '0',
+ perms: '',
+ icon: defaultIcon,
+ remark: ''
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'menuName' | 'orderNum' | 'path' | 'component'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ menuName: createRequiredRule($t('page.system.menu.form.menuName.invalid')),
+ orderNum: createNumberRequiredRule($t('page.system.menu.form.orderNum.invalid')),
+ path: createRequiredRule($t('page.system.menu.form.path.invalid')),
+ component: createRequiredRule($t('page.system.menu.form.component.invalid'))
+};
+
+// 鏄惁涓虹洰褰曠被鍨�
+const isCatalog = computed(() => model.value.menuType === 'M');
+
+// 鏄惁涓鸿彍鍗曠被鍨�
+const isMenu = computed(() => model.value.menuType === 'C');
+
+// 鏄惁涓烘寜閽被鍨�
+const isBtn = computed(() => model.value.menuType === 'F');
+
+// 澶栭摼绫诲瀷
+const isExternalType = computed(() => model.value.isFrame === '0');
+
+// 鍐呴儴绫诲瀷
+const isInternalType = computed(() => model.value.isFrame === '1');
+
+// 绌虹櫧甯冨眬
+const isBlankLayout = computed(() => layoutType.value === '1');
+
+// iframe绫诲瀷
+const isIframeType = computed(() => model.value.isFrame === '2');
+
+// 鏈湴鍥炬爣绫诲瀷
+const isLocalIcon = computed(() => iconType.value === '2');
+
+// 甯冨眬绫诲瀷绂佺敤
+const layoutDisabled = computed(() => !(isMenu.value && model.value.parentId === 0));
+
+// 鏈湴鍥炬爣
+const localIcons = getLocalMenuIcons();
+const localIconOptions = localIcons.map<SelectOption>(item => ({
+ label: () => (
+ <div class="flex-y-center gap-16px">
+ <SvgIcon localIcon={`menu-${item}`} class="text-icon" />
+ <span>{item}</span>
+ </div>
+ ),
+ value: `local-icon-${item}`
+}));
+
+function handleInitModel() {
+ queryList.value = [];
+ iconType.value = '1';
+ layoutType.value = '0';
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ const component = model.value.component;
+ if (component?.startsWith('layout.blank$view.')) {
+ layoutType.value = '1';
+ model.value.component = component?.slice(18, component.length)?.replaceAll('_', '/');
+ } else if (isMenu.value && isInternalType.value) {
+ model.value.component = component?.slice(0, -6);
+ }
+ iconType.value = model.value.icon?.startsWith('local-icon-') ? '2' : '1';
+
+ if (model.value.isFrame === '1') {
+ const queryObj: { [key: string]: string } = JSON.parse(model.value.queryParam || '{}');
+ queryList.value = Object.keys(queryObj).map(item => ({ key: item, value: queryObj[item] }));
+ return;
+ }
+
+ try {
+ if (model.value.isFrame === '2') {
+ model.value.queryParam = JSON.parse(model.value.queryParam || '{}')?.url || '';
+ }
+ } catch {}
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+// 澶勭悊璺緞
+function processPath(path: string | null | undefined): string {
+ return path?.startsWith('/') ? path.substring(1) : path || '';
+}
+
+// 澶勭悊缁勪欢
+function processComponent(component: string | null | undefined): string {
+ if (isCatalog.value && isInternalType.value) {
+ return 'Layout';
+ }
+ if (isIframeType.value || isExternalType.value) {
+ return 'FrameView';
+ }
+ if (isMenu.value && isBlankLayout.value) {
+ return `layout.blank$view.${component?.replaceAll('/', '_')}`;
+ }
+ if (isMenu.value && isInternalType.value) {
+ return component?.endsWith('/index') ? component : `${component || ''}/index`;
+ }
+ return component || '';
+}
+
+function processQueryParam(queryParam: string | null | undefined): string {
+ // 澶栭摼绫诲瀷涓嶉渶瑕佹煡璇㈠弬鏁�
+ if (isExternalType.value) {
+ return '';
+ }
+
+ // 鍐呴儴閾炬帴绫诲瀷锛屽鐞嗗姩鎬佸弬鏁�
+ if (isInternalType.value && queryList.value.length) {
+ return JSON.stringify(Object.fromEntries(queryList.value.map(({ key, value }) => [key, value])));
+ }
+
+ // iframe绫诲瀷锛岀洿鎺ヤ娇鐢ㄥ師濮嬪弬鏁�
+ if (isIframeType.value) {
+ return queryParam ? `{"url": "${queryParam}"}` : '';
+ }
+
+ return '';
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const {
+ menuId,
+ parentId,
+ menuName,
+ orderNum,
+ isFrame,
+ isCache,
+ menuType,
+ icon,
+ visible: menuVisible,
+ status,
+ perms,
+ remark,
+ component,
+ queryParam
+ } = model.value;
+
+ const payload = {
+ menuName,
+ path: processPath(model.value.path),
+ parentId,
+ orderNum,
+ queryParam: processQueryParam(queryParam),
+ isFrame,
+ isCache,
+ menuType,
+ visible: menuVisible,
+ status,
+ perms,
+ icon: icon || defaultIcon,
+ component: processComponent(component),
+ remark
+ };
+
+ const { error } =
+ props.operateType === 'add' ? await fetchCreateMenu(payload) : await fetchUpdateMenu({ ...payload, menuId });
+
+ if (error) {
+ return;
+ }
+
+ window.$message?.success($t(props.operateType === 'add' ? 'common.addSuccess' : 'common.updateSuccess'));
+ closeDrawer();
+ emit('submitted', menuType!);
+}
+
+watch(
+ () => model.value.menuType,
+ newType => {
+ if (newType === 'M') {
+ model.value.isFrame = '1';
+ }
+ }
+);
+
+watch(
+ layoutDisabled,
+ () => {
+ if (!layoutDisabled.value) {
+ return;
+ }
+ layoutType.value = '0';
+ model.value.visible = '0';
+ },
+ { immediate: true }
+);
+
+watch(visible, () => {
+ if (visible.value) {
+ handleInitModel();
+ restoreValidation();
+ }
+});
+
+function handleLayoutChange(value: string) {
+ model.value.visible = value as Api.Common.VisibleStatus;
+}
+
+function onCreate() {
+ return {
+ key: '',
+ value: ''
+ };
+}
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" display-directive="show" :width="600" class="max-w-90%">
+ <NDrawerContent :title="drawerTitle" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi :span="24" :label="$t('page.system.menu.parentId')" path="pid">
+ <MenuTreeSelect
+ v-model:value="model.parentId"
+ :immediate="false"
+ :options="treeData as []"
+ :placeholder="$t('page.system.menu.form.parentId.required')"
+ />
+ </NFormItemGi>
+ <NFormItemGi v-if="!isBtn" :span="12" :label="$t('page.system.menu.menuType')" path="menuType">
+ <NRadioGroup v-model:value="model.menuType">
+ <NRadioButton
+ v-for="item in menuTypeOptions.filter(item => item.value !== 'F')"
+ :key="item.value"
+ :value="item.value"
+ :label="item.label"
+ />
+ </NRadioGroup>
+ </NFormItemGi>
+ <NFormItemGi :span="12" path="layout">
+ <template #label>
+ <div class="flex-center">
+ <FormTip :content="$t('page.system.menu.layoutTip')" />
+ <span>{{ $t('page.system.menu.layout') }}</span>
+ </div>
+ </template>
+ <NRadioGroup v-model:value="layoutType" :disabled="layoutDisabled" @update:value="handleLayoutChange">
+ <NRadio v-for="item in menuLayoutOptions" :key="item.value" :value="item.value" :label="item.label" />
+ </NRadioGroup>
+ </NFormItemGi>
+ <NFormItemGi span="24" :label="$t('page.system.menu.menuName')" path="menuName">
+ <NInput v-model:value="model.menuName" :placeholder="$t('page.system.menu.form.menuName.required')" />
+ </NFormItemGi>
+ <NFormItemGi v-if="!isBtn" span="12" :label="$t('page.system.menu.iconType')">
+ <NRadioGroup v-model:value="iconType">
+ <NRadio v-for="item in menuIconTypeOptions" :key="item.value" :value="item.value" :label="item.label" />
+ </NRadioGroup>
+ </NFormItemGi>
+ <NFormItemGi v-if="!isBtn" span="12" path="icon">
+ <template #label>
+ <div class="flex-center">
+ <FormTip :content="$t('page.system.menu.iconifyTip')" />
+ <span class="pl-3px">{{ $t('page.system.menu.icon') }}</span>
+ </div>
+ </template>
+ <template v-if="isLocalIcon">
+ <NSelect
+ v-model:value="model.icon"
+ :placeholder="$t('page.system.menu.placeholder.localIconPlaceholder')"
+ filterable
+ :options="localIconOptions"
+ />
+ </template>
+ <template v-else>
+ <NInput
+ v-model:value="model.icon"
+ :placeholder="$t('page.system.menu.placeholder.iconifyIconPlaceholder')"
+ class="flex-1"
+ >
+ <template #suffix>
+ <SvgIcon v-if="model.icon" :icon="model.icon" class="text-icon" />
+ </template>
+ </NInput>
+ </template>
+ </NFormItemGi>
+ <NFormItemGi v-if="!isBtn" :span="12" path="isFrame">
+ <template #label>
+ <div class="flex-center">
+ <FormTip :content="$t('page.system.menu.isFrameTip')" />
+ <span>{{ $t('page.system.menu.isFrame') }}</span>
+ </div>
+ </template>
+ <NRadioGroup v-model:value="model.isFrame">
+ <NSpace>
+ <NRadio
+ v-for="option in menuIsFrameOptions"
+ :key="option.value"
+ :value="option.value"
+ :label="option.label"
+ :disabled="option.value === '2' && isCatalog"
+ />
+ </NSpace>
+ </NRadioGroup>
+ </NFormItemGi>
+ <NFormItemGi v-if="isMenu" :span="12" path="isCache">
+ <template #label>
+ <div class="flex-center">
+ <FormTip :content="$t('page.system.menu.isCacheTip')" />
+ <span>{{ $t('page.system.menu.isCache') }}</span>
+ </div>
+ </template>
+ <NRadioGroup v-model:value="model.isCache">
+ <NSpace>
+ <NRadio value="0" :label="$t('common.yesOrNo.yes')" />
+ <NRadio value="1" :label="$t('common.yesOrNo.no')" />
+ </NSpace>
+ </NRadioGroup>
+ </NFormItemGi>
+ <NFormItemGi v-if="!isBtn" :span="24" path="path">
+ <template #label>
+ <div class="flex-center">
+ <FormTip :content="$t('page.system.menu.pathTip')" />
+ <span>
+ {{ !isExternalType ? $t('page.system.menu.path') : $t('page.system.menu.externalPath') }}
+ </span>
+ </div>
+ </template>
+ <NInput v-model:value="model.path" :placeholder="$t('page.system.menu.form.path.required')" />
+ </NFormItemGi>
+ <NFormItemGi v-if="isMenu && isInternalType" :span="24" path="component">
+ <template #label>
+ <div class="flex-center">
+ <FormTip :content="$t('page.system.menu.componentTip')" />
+ <span>{{ $t('page.system.menu.component') }}</span>
+ </div>
+ </template>
+ <NInputGroup>
+ <NInputGroupLabel>views/</NInputGroupLabel>
+ <NInput v-model:value="model.component" :placeholder="$t('page.system.menu.form.path.required')" />
+ <NInputGroupLabel>/index.vue</NInputGroupLabel>
+ </NInputGroup>
+ </NFormItemGi>
+ <NFormItemGi
+ v-if="isMenu && !isExternalType"
+ span="24"
+ :show-feedback="!queryList.length"
+ :label="isInternalType ? $t('page.system.menu.query') : $t('page.system.menu.iframeQuery')"
+ >
+ <NDynamicInput
+ v-if="isInternalType"
+ v-model:value="queryList"
+ item-style="margin-bottom: 0"
+ :on-create="onCreate"
+ >
+ <template #default="{ index }">
+ <div class="w-full flex">
+ <NFormItem
+ class="w-full"
+ ignore-path-change
+ :show-label="false"
+ :path="`query[${index}].key`"
+ :rule="{
+ ...createRequiredRule($t('page.system.menu.placeholder.queryKey')),
+ validator: value => isNotNull(value)
+ }"
+ >
+ <NInput v-model:value="queryList[index].key" placeholder="Key" @keydown.enter.prevent />
+ </NFormItem>
+ <div class="mx-8px h-34px lh-34px">=</div>
+ <NFormItem
+ class="w-full"
+ ignore-path-change
+ :show-label="false"
+ :path="`query[${index}].value`"
+ :rule="{
+ ...createRequiredRule($t('page.system.menu.placeholder.queryValue')),
+ validator: value => isNotNull(value)
+ }"
+ >
+ <NInput v-model:value="queryList[index].value" placeholder="Value" @keydown.enter.prevent />
+ </NFormItem>
+ </div>
+ </template>
+ </NDynamicInput>
+ <NInput
+ v-else
+ v-model:value="model.queryParam"
+ :placeholder="$t('page.system.menu.placeholder.queryIframe')"
+ />
+ </NFormItemGi>
+ <NFormItemGi v-if="!isCatalog" :span="24" path="perms">
+ <template #label>
+ <div class="flex-center">
+ <FormTip :content="$t('page.system.menu.permsTip')" />
+ <span>{{ $t('page.system.menu.perms') }}</span>
+ </div>
+ </template>
+ <NInput v-model:value="model.perms" :placeholder="$t('page.system.menu.form.perms.required')" />
+ </NFormItemGi>
+
+ <NFormItemGi v-if="!isBtn" :span="12" :label="$t('page.system.menu.visible')" path="visible">
+ <template #label>
+ <div class="flex-center">
+ <FormTip :content="$t('page.system.menu.visibleTip')" />
+ <span>{{ $t('page.system.menu.visible') }}</span>
+ </div>
+ </template>
+ <DictRadio v-model:value="model.visible" dict-code="sys_show_hide" :disabled="isBlankLayout" />
+ </NFormItemGi>
+ <NFormItemGi :span="12" path="status">
+ <template #label>
+ <div class="flex-center">
+ <FormTip :content="$t('page.system.menu.statusTip')" />
+ <span>{{ $t('page.system.menu.status') }}</span>
+ </div>
+ </template>
+ <DictRadio v-model:value="model.status" dict-code="sys_normal_disable" />
+ </NFormItemGi>
+ <NFormItemGi :span="12" :label="$t('page.system.menu.orderNum')" path="orderNum">
+ <NInputNumber v-model:value="model.orderNum" :placeholder="$t('page.system.menu.form.orderNum.required')" />
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.save') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/notice/index.vue b/ruoyi-plus-soybean/src/views/system/notice/index.vue
new file mode 100755
index 0000000..2184116
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/notice/index.vue
@@ -0,0 +1,200 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import { fetchBatchDeleteNotice, fetchGetNoticeList } from '@/service/api/system/notice';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import { $t } from '@/locales';
+import DictTag from '@/components/custom/dict-tag.vue';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import NoticeOperateDrawer from './modules/notice-operate-drawer.vue';
+import NoticeSearch from './modules/notice-search.vue';
+
+defineOptions({
+ name: 'NoticeList'
+});
+
+useDict('sys_notice_type');
+useDict('sys_normal_disable');
+const appStore = useAppStore();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.System.NoticeSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ noticeTitle: null,
+ noticeType: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetNoticeList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'noticeTitle',
+ title: '鍏憡鏍囬',
+ align: 'center',
+ width: 300
+ },
+ {
+ key: 'noticeType',
+ title: '鍏憡绫诲瀷',
+ align: 'center',
+ minWidth: 120,
+ render(row) {
+ return <DictTag size="small" value={row.noticeType} dictCode="sys_notice_type" />;
+ }
+ },
+ {
+ key: 'status',
+ title: '鍏憡鐘舵��',
+ align: 'center',
+ minWidth: 120,
+ render(row) {
+ return <DictTag size="small" value={row.status} dictCode="sys_normal_disable" />;
+ }
+ },
+ {
+ key: 'createByName',
+ title: '鍒涘缓鑰�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('system:notice:edit') || !hasAuth('system:notice:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('system:notice:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.noticeId!)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('system:notice:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.noticeId!)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'noticeId', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteNotice(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(noticeId: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteNotice([noticeId]);
+ if (error) return;
+ onDeleted();
+}
+
+async function edit(noticeId: CommonType.IdType) {
+ handleEdit(noticeId);
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <NoticeSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard title="閫氱煡鍏憡鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('system:notice:add')"
+ :show-delete="hasAuth('system:notice:remove')"
+ :show-export="false"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @refresh="getData"
+ />
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.noticeId"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <NoticeOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getData"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/notice/modules/notice-operate-drawer.vue b/ruoyi-plus-soybean/src/views/system/notice/modules/notice-operate-drawer.vue
new file mode 100755
index 0000000..f69dad7
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/notice/modules/notice-operate-drawer.vue
@@ -0,0 +1,145 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import type { UmoEditor } from '@umoteam/editor';
+import { fetchCreateNotice, fetchUpdateNotice } from '@/service/api/system/notice';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'NoticeOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.System.Notice | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const umoEditorRef = ref<InstanceType<typeof UmoEditor>>();
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: '鏂板閫氱煡鍏憡',
+ edit: '缂栬緫閫氱煡鍏憡'
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.System.NoticeOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ noticeTitle: '',
+ noticeType: '1',
+ noticeContent: '',
+ status: '0'
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'noticeId' | 'noticeTitle' | 'noticeType' | 'noticeContent' | 'status'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ noticeId: createRequiredRule('鍏憡ID涓嶈兘涓虹┖'),
+ noticeTitle: createRequiredRule('鍏憡鏍囬涓嶈兘涓虹┖'),
+ noticeType: createRequiredRule('鍏憡绫诲瀷涓嶈兘涓虹┖'),
+ noticeContent: createRequiredRule('鍏憡鍐呭涓嶈兘涓虹┖'),
+ status: createRequiredRule('鍏憡鐘舵�佷笉鑳戒负绌�')
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ umoEditorRef.value?.saveContent();
+ await validate();
+
+ const { noticeId, noticeTitle, noticeType, noticeContent, status } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateNotice({ noticeTitle, noticeType, noticeContent, status });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateNotice({ noticeId, noticeTitle, noticeType, noticeContent, status });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer
+ v-model:show="visible"
+ :trap-focus="false"
+ :title="title"
+ display-directive="show"
+ :width="1000"
+ class="max-w-90%"
+ >
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <div class="grid grid-cols-1 gap-16px md:grid-cols-4">
+ <NFormItem class="col-span-2" label="鍏憡鏍囬" path="noticeTitle">
+ <NInput v-model:value="model.noticeTitle" placeholder="璇疯緭鍏ュ叕鍛婃爣棰�" />
+ </NFormItem>
+ <NFormItem class="col-span-1" label="鍏憡绫诲瀷" path="noticeType">
+ <DictRadio v-model:value="model.noticeType" dict-code="sys_notice_type" />
+ </NFormItem>
+ <NFormItem class="col-span-1" label="鍏憡鐘舵��" path="status">
+ <DictRadio v-model:value="model.status" dict-code="sys_normal_disable" />
+ </NFormItem>
+ </div>
+ <NFormItem :show-label="false" path="noticeContent">
+ <UmoDocEditor ref="umoEditorRef" v-model:value="model.noticeContent!" />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/notice/modules/notice-search.vue b/ruoyi-plus-soybean/src/views/system/notice/modules/notice-search.vue
new file mode 100755
index 0000000..26b6b81
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/notice/modules/notice-search.vue
@@ -0,0 +1,74 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'NoticeSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.System.NoticeSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:8" label="鍏憡鏍囬" path="noticeTitle" class="pr-24px">
+ <NInput v-model:value="model.noticeTitle" placeholder="璇疯緭鍏ュ叕鍛婃爣棰�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鍏憡绫诲瀷" path="noticeType" class="pr-24px">
+ <DictSelect v-model:value="model.noticeType" dict-code="sys_notice_type" placeholder="璇烽�夋嫨鍏憡绫诲瀷" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/oss-config/index.vue b/ruoyi-plus-soybean/src/views/system/oss-config/index.vue
new file mode 100755
index 0000000..3a7980f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/oss-config/index.vue
@@ -0,0 +1,260 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider, NTag } from 'naive-ui';
+import {
+ fetchBatchDeleteOssConfig,
+ fetchGetOssConfigList,
+ fetchUpdateOssConfigStatus
+} from '@/service/api/system/oss-config';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import StatusSwitch from '@/components/custom/status-switch.vue';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import OssConfigOperateDrawer from './modules/oss-config-operate-drawer.vue';
+import OssConfigSearch from './modules/oss-config-search.vue';
+
+defineOptions({
+ name: 'OssConfigList'
+});
+
+useDict('sys_yes_no');
+
+const appStore = useAppStore();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.System.OssConfigSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ configKey: null,
+ bucketName: null,
+ region: null,
+ status: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetOssConfigList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'configKey',
+ title: '閰嶇疆鍚嶇О',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'bucketName',
+ title: '妗跺悕绉�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'endpoint',
+ title: '璁块棶绔欑偣',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'region',
+ title: '鍩�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'accessPolicy',
+ title: '妗舵潈闄愮被鍨�',
+ align: 'center',
+ minWidth: 120,
+ render(row) {
+ if (row.accessPolicy === '0') {
+ return <NTag type="info">绉佹湁</NTag>;
+ }
+ if (row.accessPolicy === '1') {
+ return <NTag type="success">鍏湁</NTag>;
+ }
+ if (row.accessPolicy === '2') {
+ return <NTag type="warning">鑷畾涔�</NTag>;
+ }
+ return null;
+ }
+ },
+ {
+ key: 'status',
+ title: '鏄惁榛樿',
+ align: 'center',
+ minWidth: 120,
+ render(row) {
+ return (
+ <StatusSwitch
+ v-model:value={row.status}
+ info={row.configKey}
+ onSubmitted={(value, callback) => handleStatusChange(row, value, callback)}
+ />
+ );
+ }
+ },
+ {
+ key: 'remark',
+ title: '澶囨敞',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('system:ossConfig:edit') || !hasAuth('system:ossConfig:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('system:ossConfig:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.ossConfigId!)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('system:ossConfig:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.ossConfigId!)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'ossConfigId', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteOssConfig(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(ossConfigId: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteOssConfig([ossConfigId]);
+ if (error) return;
+ onDeleted();
+}
+
+async function edit(ossConfigId: CommonType.IdType) {
+ handleEdit(ossConfigId);
+}
+
+/** 澶勭悊鐘舵�佸垏鎹� */
+async function handleStatusChange(
+ row: Api.System.OssConfig,
+ value: Api.Common.EnableStatus,
+ callback: (flag: boolean) => void
+) {
+ const { error } = await fetchUpdateOssConfigStatus({
+ configKey: row.configKey,
+ ossConfigId: row.ossConfigId,
+ status: value
+ });
+
+ callback(!error);
+
+ if (!error) {
+ window.$message?.success('鐘舵�佷慨鏀规垚鍔�');
+ getData();
+ }
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <OssConfigSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard title="OSS閰嶇疆鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('system:ossConfig:add')"
+ :show-delete="hasAuth('system:ossConfig:remove')"
+ :show-export="false"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @refresh="getData"
+ />
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.ossConfigId"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <OssConfigOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getData"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/oss-config/modules/oss-config-operate-drawer.vue b/ruoyi-plus-soybean/src/views/system/oss-config/modules/oss-config-operate-drawer.vue
new file mode 100755
index 0000000..ce7d5bf
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/oss-config/modules/oss-config-operate-drawer.vue
@@ -0,0 +1,226 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { ossAccessPolicyOptions, ossConfigIsHttpsOptions } from '@/constants/business';
+import { fetchCreateOssConfig, fetchUpdateOssConfig } from '@/service/api/system/oss-config';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'OssConfigOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.System.OssConfig | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: '鏂板OSS閰嶇疆',
+ edit: '缂栬緫OSS閰嶇疆'
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.System.OssConfigOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ configKey: '',
+ accessKey: '',
+ secretKey: '',
+ bucketName: '',
+ prefix: '',
+ endpoint: '',
+ domain: '',
+ isHttps: 'N',
+ region: '',
+ accessPolicy: '1',
+ remark: ''
+ };
+}
+
+type RuleKey = Extract<
+ keyof Model,
+ 'ossConfigId' | 'configKey' | 'accessKey' | 'secretKey' | 'bucketName' | 'endpoint' | 'accessPolicy'
+>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ ossConfigId: createRequiredRule('涓婚敭涓嶈兘涓虹┖'),
+ configKey: createRequiredRule('閰嶇疆鍚嶇О涓嶈兘涓虹┖'),
+ accessKey: createRequiredRule('accessKey涓嶈兘涓虹┖'),
+ secretKey: createRequiredRule('secretKey涓嶈兘涓虹┖'),
+ bucketName: createRequiredRule('妗跺悕绉颁笉鑳戒负绌�'),
+ endpoint: createRequiredRule('璁块棶绔欑偣涓嶈兘涓虹┖'),
+ accessPolicy: createRequiredRule('妗舵潈闄愮被鍨嬩笉鑳戒负绌�')
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const {
+ ossConfigId,
+ configKey,
+ accessKey,
+ secretKey,
+ bucketName,
+ prefix,
+ endpoint,
+ domain,
+ isHttps,
+ region,
+ accessPolicy,
+ remark
+ } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateOssConfig({
+ configKey,
+ accessKey,
+ secretKey,
+ bucketName,
+ prefix,
+ endpoint,
+ domain,
+ isHttps,
+ region,
+ accessPolicy,
+ remark
+ });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateOssConfig({
+ ossConfigId,
+ configKey,
+ accessKey,
+ secretKey,
+ bucketName,
+ prefix,
+ endpoint,
+ domain,
+ isHttps,
+ region,
+ accessPolicy,
+ remark
+ });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NDivider>鍩烘湰淇℃伅</NDivider>
+ <NFormItem label="閰嶇疆鍚嶇О" path="configKey">
+ <NInput v-model:value="model.configKey" placeholder="璇疯緭鍏ラ厤缃悕绉�" />
+ </NFormItem>
+ <NFormItem label="璁块棶绔欑偣" path="endpoint">
+ <NInputGroup>
+ <NSelect
+ v-model:value="model.isHttps"
+ class="w-110px"
+ :options="ossConfigIsHttpsOptions"
+ placeholder="璇烽�夋嫨璁块棶鍗忚"
+ />
+ <NInput v-model:value="model.endpoint" placeholder="璇疯緭鍏ヨ闂珯鐐�" />
+ </NInputGroup>
+ </NFormItem>
+ <NFormItem label="鑷畾涔夊煙鍚�" path="domain">
+ <NInput v-model:value="model.domain" placeholder="璇疯緭鍏ヨ嚜瀹氫箟鍩熷悕" />
+ </NFormItem>
+ <NDivider>璁よ瘉淇℃伅</NDivider>
+ <NFormItem label="accessKey" path="accessKey">
+ <NInput v-model:value="model.accessKey" placeholder="璇疯緭鍏� AccessKey" />
+ </NFormItem>
+ <NFormItem label="secretKey" path="secretKey">
+ <NInput v-model:value="model.secretKey" placeholder="璇疯緭鍏ョ閽� SecretKey" />
+ </NFormItem>
+ <NDivider>妗朵俊鎭�</NDivider>
+ <NFormItem label="妗跺悕绉�" path="bucketName">
+ <NInput v-model:value="model.bucketName" placeholder="璇疯緭鍏ユ《鍚嶇О" />
+ </NFormItem>
+ <NFormItem label="鍓嶇紑" path="prefix">
+ <NInput v-model:value="model.prefix" placeholder="璇疯緭鍏ュ墠缂�" />
+ </NFormItem>
+ <NGrid :cols="2" :x-gap="24">
+ <NGridItem>
+ <NFormItem label="妗舵潈闄愮被鍨�" path="accessPolicy">
+ <NRadioGroup v-model:value="model.accessPolicy">
+ <NSpace>
+ <NRadio
+ v-for="option in ossAccessPolicyOptions"
+ :key="option.value"
+ :value="option.value"
+ :label="option.label"
+ />
+ </NSpace>
+ </NRadioGroup>
+ </NFormItem>
+ </NGridItem>
+ </NGrid>
+ <NFormItem label="鍩�" path="region">
+ <NInput v-model:value="model.region" placeholder="璇疯緭鍏ュ煙" />
+ </NFormItem>
+ <NFormItem label="澶囨敞" path="remark">
+ <NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/oss-config/modules/oss-config-search.vue b/ruoyi-plus-soybean/src/views/system/oss-config/modules/oss-config-search.vue
new file mode 100755
index 0000000..38ce6bf
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/oss-config/modules/oss-config-search.vue
@@ -0,0 +1,92 @@
+<script setup lang="ts">
+import { ref, toRaw } from 'vue';
+import type { SelectOption } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'OssConfigSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const isDefaltOptions = ref<SelectOption[]>([
+ {
+ label: '鏄�',
+ value: '0'
+ },
+ {
+ label: '鍚�',
+ value: '1'
+ }
+]);
+
+const model = defineModel<Api.System.OssConfigSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:6" label="閰嶇疆鍚嶇О" path="configKey" class="pr-24px">
+ <NInput v-model:value="model.configKey" placeholder="璇疯緭鍏ラ厤缃悕绉�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="妗跺悕绉�" path="bucketName" class="pr-24px">
+ <NInput v-model:value="model.bucketName" placeholder="璇疯緭鍏ユ《鍚嶇О" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鍩�" path="region" class="pr-24px">
+ <NInput v-model:value="model.region" placeholder="璇疯緭鍏ュ煙" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鏄惁榛樿" path="status" class="pr-24px">
+ <NSelect v-model:value="model.status" placeholder="璇烽�夋嫨鏄惁榛樿" :options="isDefaltOptions" clearable />
+ </NFormItemGi>
+ <NFormItemGi span="24" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/oss/index.vue b/ruoyi-plus-soybean/src/views/system/oss/index.vue
new file mode 100755
index 0000000..75aee0d
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/oss/index.vue
@@ -0,0 +1,360 @@
+<script setup lang="tsx">
+import { onMounted, ref } from 'vue';
+import type { DataTableSortState } from 'naive-ui';
+import { NButton, NDivider, NEllipsis, NImage, NTag, NTooltip } from 'naive-ui';
+import { useBoolean, useLoading } from '@sa/hooks';
+import { fetchBatchDeleteOss, fetchGetOssList } from '@/service/api/system/oss';
+import { fetchGetConfigByKey, fetchUpdateConfigByKey } from '@/service/api/system/config';
+import { useAppStore } from '@/store/modules/app';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { useRouterPush } from '@/hooks/common/router';
+import { isImage } from '@/utils/common';
+import { handleCopy } from '@/utils/copy';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import OssSearch from './modules/oss-search.vue';
+import OssUploadModal from './modules/oss-upload-modal.vue';
+
+defineOptions({
+ name: 'OssList'
+});
+
+const { routerPushByKey } = useRouterPush();
+const { hasAuth } = useAuth();
+const { oss } = useDownload();
+const appStore = useAppStore();
+
+const fileUploadType = ref<'file' | 'image'>('file');
+const { bool: preview, setBool: setPreview } = useBoolean(true);
+const { loading: previewLoading, startLoading: startPreviewLoading, endLoading: endPreviewLoading } = useLoading(false);
+const { bool: uploadVisible, setTrue: showFUploadModal } = useBoolean(false);
+
+const searchParams = ref<Api.System.OssSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ fileName: null,
+ originalName: null,
+ fileSuffix: null,
+ service: null,
+ isAsc: 'desc',
+ orderByColumn: 'createTime',
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetOssList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'ossId',
+ title: '瀵硅薄瀛樺偍涓婚敭',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'fileName',
+ title: '鏂囦欢鍚�',
+ align: 'center',
+ ellipsis: {
+ tooltip: true,
+ lineClamp: 3
+ },
+ minWidth: 120
+ },
+ {
+ key: 'originalName',
+ title: '鍘熷悕',
+ align: 'center',
+ ellipsis: {
+ tooltip: true,
+ lineClamp: 3
+ },
+ minWidth: 120
+ },
+ {
+ key: 'fileSuffix',
+ title: '鏂囦欢鍚庣紑鍚�',
+ align: 'center',
+ minWidth: 100
+ },
+ {
+ key: 'url',
+ title: 'URL鍦板潃',
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ if (preview.value && isImage(row.fileSuffix)) {
+ return <NImage class="h-40px w-40px object-contain" src={row.url} />;
+ }
+ return (
+ <NTooltip>
+ {{
+ default: () => <span>鐐瑰嚮澶嶅埗</span>,
+ trigger: () => (
+ <div class="cursor-pointer" onClick={async () => await handleCopy(row.url)}>
+ <NEllipsis line-clamp={3} tooltip={false}>
+ {row.url}
+ </NEllipsis>
+ </div>
+ )
+ }}
+ </NTooltip>
+ );
+ }
+ },
+ {
+ key: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ align: 'center',
+ minWidth: 120,
+ sorter: true,
+ defaultSortOrder: 'descend'
+ },
+ {
+ key: 'createByName',
+ title: '涓婁紶浜�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'service',
+ title: '鏈嶅姟鍟�',
+ align: 'center',
+ minWidth: 100,
+ render: row => {
+ return <NTag type="primary">{row.service}</NTag>;
+ }
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('system:oss:download') || !hasAuth('system:oss:delete')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const downloadBtn = () => {
+ if (!hasAuth('system:oss:download')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:download-rounded"
+ class="text-20px"
+ tooltipContent={$t('common.download')}
+ onClick={() => download(row.ossId!)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('system:oss:delete')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ class="text-20px"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.ossId!)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {downloadBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const { handleAdd, checkedRowKeys, onBatchDeleted, onDeleted } = useTableOperate(data, 'ossId', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteOss(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(ossId: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteOss([ossId]);
+ if (error) return;
+ onDeleted();
+}
+
+function download(ossId: CommonType.IdType) {
+ oss(ossId);
+}
+
+function handleUpdateSorter(sorters: DataTableSortState) {
+ if (!sorters.order) {
+ searchParams.value.orderByColumn = null;
+ searchParams.value.isAsc = null;
+ } else {
+ searchParams.value.orderByColumn = sorters.columnKey as keyof Api.System.Oss;
+ searchParams.value.isAsc = sorters.order === 'ascend' ? 'asc' : 'desc';
+ }
+ getDataByPage();
+}
+
+function handleUpload(type: 'file' | 'image') {
+ fileUploadType.value = type;
+ showFUploadModal();
+}
+
+async function getConfigKey() {
+ const { data: previewStr, error } = await fetchGetConfigByKey('sys.oss.previewListResource');
+ if (error) return;
+ setPreview(previewStr === 'true');
+}
+
+onMounted(() => {
+ getConfigKey();
+});
+
+async function handleUpdatePreview(checked: boolean) {
+ setPreview(!checked);
+ window.$dialog?.warning({
+ title: '鎻愮ず',
+ content: `鏄惁纭${checked ? '寮�鍚�' : '鍏抽棴'}棰勮锛焋,
+ positiveText: '纭',
+ negativeText: '鍙栨秷',
+ onPositiveClick: async () => {
+ startPreviewLoading();
+ const { error } = await fetchUpdateConfigByKey({
+ configKey: 'sys.oss.previewListResource',
+ configValue: String(checked)
+ });
+ if (error) {
+ setPreview(!checked);
+ endPreviewLoading();
+ return;
+ }
+ setPreview(checked);
+ window.$message?.success('鏇存柊鎴愬姛');
+ endPreviewLoading();
+ },
+ onNegativeClick: () => {
+ setPreview(!checked);
+ }
+ });
+}
+
+function handleToOssConfig() {
+ routerPushByKey('system_oss-config');
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <OssSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard title="OSS 瀵硅薄瀛樺偍鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="false"
+ :show-delete="hasAuth('system:oss:delete')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @refresh="getData"
+ >
+ <template #prefix>
+ <NSwitch
+ v-model:value="preview"
+ class="mt-1px"
+ :loading="previewLoading"
+ size="large"
+ :round="false"
+ @update:value="handleUpdatePreview"
+ >
+ <template #checked>
+ <span class="text-14px">绂佺敤棰勮</span>
+ </template>
+ <template #unchecked>
+ <span class="text-14px">寮�鍚瑙�</span>
+ </template>
+ </NSwitch>
+
+ <NButton size="small" ghost @click="handleUpload('file')">
+ <template #icon>
+ <icon-material-symbols-upload-rounded />
+ </template>
+ 涓婁紶鏂囦欢
+ </NButton>
+ <NButton size="small" ghost @click="handleUpload('image')">
+ <template #icon>
+ <icon-material-symbols-image-outline />
+ </template>
+ 涓婁紶鍥剧墖
+ </NButton>
+ <NButton type="primary" size="small" ghost @click="handleToOssConfig">
+ <template #icon>
+ <icon-hugeicons-configuration-01 />
+ </template>
+ 閰嶇疆绠$悊
+ </NButton>
+ </template>
+ </TableHeaderOperation>
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.ossId"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ @update:sorter="handleUpdateSorter"
+ />
+ <OssUploadModal v-model:visible="uploadVisible" :upload-type="fileUploadType" @close="getDataByPage" />
+ </NCard>
+ </div>
+</template>
+
+<style scoped>
+.n-switch {
+ --n-rail-height: 27px !important;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/system/oss/modules/oss-search.vue b/ruoyi-plus-soybean/src/views/system/oss/modules/oss-search.vue
new file mode 100755
index 0000000..bf566e0
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/oss/modules/oss-search.vue
@@ -0,0 +1,102 @@
+<script setup lang="ts">
+import { ref, toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'OssSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const dateRangeCreateTime = ref<[string, string] | null>(null);
+
+const model = defineModel<Api.System.OssSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function onDateRangeCreateTimeUpdate(value: [string, string] | null) {
+ const params = model.value.params!;
+ if (value && value.length === 2) {
+ [params.beginTime, params.endTime] = value;
+ } else {
+ params.beginTime = undefined;
+ params.endTime = undefined;
+ }
+}
+
+function resetModel() {
+ dateRangeCreateTime.value = null;
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="100">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:6" label="鏂囦欢鍚�" path="fileName" class="pr-24px">
+ <NInput v-model:value="model.fileName" placeholder="璇疯緭鍏ユ枃浠跺悕" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鍘熷悕" path="originalName" class="pr-24px">
+ <NInput v-model:value="model.originalName" placeholder="璇疯緭鍏ュ師鍚�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鏂囦欢鍚庣紑鍚�" path="fileSuffix" class="pr-24px">
+ <NInput v-model:value="model.fileSuffix" placeholder="璇疯緭鍏ユ枃浠跺悗缂�鍚�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鏈嶅姟鍟�" path="service" class="pr-24px">
+ <NInput v-model:value="model.service" placeholder="璇疯緭鍏ユ湇鍔″晢" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:12" label="鍒涘缓鏃堕棿" path="createTime" class="pr-24px">
+ <NDatePicker
+ v-model:formatted-value="dateRangeCreateTime"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ @update:formatted-value="onDateRangeCreateTimeUpdate"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:12" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/oss/modules/oss-upload-modal.vue b/ruoyi-plus-soybean/src/views/system/oss/modules/oss-upload-modal.vue
new file mode 100755
index 0000000..01b9a51
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/oss/modules/oss-upload-modal.vue
@@ -0,0 +1,67 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import type { UploadFileInfo } from 'naive-ui';
+import FileUpload from '@/components/custom/file-upload.vue';
+import { AcceptType } from '@/enum/business';
+
+defineOptions({
+ name: 'OssUploadModal'
+});
+
+interface Props {
+ uploadType: 'file' | 'image';
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'close'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const accept = computed(() => (props.uploadType === 'file' ? AcceptType.File : AcceptType.Image));
+
+const fileList = ref<UploadFileInfo[]>([]);
+
+function handleUpdateModelWhenUpload() {
+ fileList.value = [];
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+function handleClose() {
+ closeDrawer();
+ if (fileList.value?.length > 0) {
+ emit('close');
+ }
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenUpload();
+ }
+});
+</script>
+
+<template>
+ <NModal
+ v-model:show="visible"
+ class="max-h-520px max-w-90% w-690px"
+ preset="card"
+ :title="`涓婁紶${uploadType === 'file' ? '鏂囦欢' : '鍥剧墖'}`"
+ size="huge"
+ :bordered="false"
+ @after-leave="handleClose"
+ >
+ <FileUpload v-model:file-list="fileList" :upload-type="uploadType" :accept="accept" />
+ </NModal>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/post/index.vue b/ruoyi-plus-soybean/src/views/system/post/index.vue
new file mode 100755
index 0000000..572f4fb
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/post/index.vue
@@ -0,0 +1,358 @@
+<script setup lang="tsx">
+import { computed, ref } from 'vue';
+import { NButton, NDivider } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { fetchBatchDeletePost, fetchGetPostDeptSelect, fetchGetPostList } from '@/service/api/system/post';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import DictTag from '@/components/custom/dict-tag.vue';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import PostOperateDrawer from './modules/post-operate-drawer.vue';
+import PostSearch from './modules/post-search.vue';
+
+defineOptions({
+ name: 'PostList'
+});
+
+useDict('sys_normal_disable');
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+const searchParams = ref<Api.System.PostSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ postCode: null,
+ postName: null,
+ status: null,
+ belongDeptId: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetPostList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'postCode',
+ title: '宀椾綅缂栫爜',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'postCategory',
+ title: '绫诲埆缂栫爜',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'postName',
+ title: '宀椾綅鍚嶇О',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'postSort',
+ title: '鏄剧ず椤哄簭',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'status',
+ title: '鐘舵��',
+ align: 'center',
+ minWidth: 120,
+ render(row) {
+ return <DictTag size="small" value={row.status} dictCode="sys_normal_disable" />;
+ }
+ },
+ {
+ key: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ align: 'center',
+ minWidth: 120,
+ ellipsis: {
+ tooltip: true
+ }
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('system:post:edit') || !hasAuth('system:post:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('system:post:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ type="primary"
+ text
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.postId!)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('system:post:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.postId!)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'postId', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeletePost(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(postId: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeletePost([postId]);
+ if (error) return;
+ onDeleted();
+}
+
+async function edit(postId: CommonType.IdType) {
+ handleEdit(postId);
+}
+
+async function handleExport() {
+ download('/system/post/export', searchParams.value, `宀椾綅淇℃伅_${new Date().getTime()}.xlsx`);
+}
+
+const expandedKeys = ref<CommonType.IdType[]>([100]);
+
+const selectable = computed(() => {
+ return !loading.value;
+});
+
+const { loading: treeLoading, startLoading: startTreeLoading, endLoading: endTreeLoading } = useLoading();
+const deptPattern = ref<string>();
+const deptData = ref<Api.Common.CommonTreeRecord>([]);
+const selectedKeys = ref<string[]>([]);
+
+async function getDeptOptions() {
+ // 鍔犺浇
+ startTreeLoading();
+ const { data: tree, error } = await fetchGetPostDeptSelect();
+ if (!error) {
+ deptData.value = tree;
+ }
+ endTreeLoading();
+}
+getDeptOptions();
+
+function handleClickTree(keys: string[]) {
+ searchParams.value.belongDeptId = keys.length ? keys[0] : null;
+ checkedRowKeys.value = [];
+ getDataByPage();
+}
+
+function handleResetTreeData() {
+ deptPattern.value = undefined;
+ getDeptOptions();
+}
+
+function handleResetSearch() {
+ selectedKeys.value = [];
+ getDataByPage();
+}
+</script>
+
+<template>
+ <TableSiderLayout sider-title="閮ㄩ棬鍒楄〃">
+ <template #header-extra>
+ <NButton size="small" text class="h-18px" @click.stop="() => handleResetTreeData()">
+ <template #icon>
+ <SvgIcon icon="ic:round-refresh" />
+ </template>
+ </NButton>
+ </template>
+ <template #sider>
+ <NInput v-model:value="deptPattern" clearable :placeholder="$t('common.keywordSearch')" />
+ <NSpin class="dept-tree" :show="treeLoading">
+ <NTree
+ v-model:expanded-keys="expandedKeys"
+ v-model:selected-keys="selectedKeys"
+ block-node
+ show-line
+ :data="deptData as []"
+ :show-irrelevant-nodes="false"
+ :pattern="deptPattern"
+ block-line
+ class="infinite-scroll h-full min-h-200px py-3"
+ key-field="id"
+ label-field="label"
+ virtual-scroll
+ :selectable="selectable"
+ @update:selected-keys="handleClickTree"
+ >
+ <template #empty>
+ <NEmpty description="鏆傛棤閮ㄩ棬淇℃伅" class="h-full min-h-200px justify-center" />
+ </template>
+ </NTree>
+ </NSpin>
+ </template>
+ <div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto">
+ <PostSearch v-model:model="searchParams" @reset="handleResetSearch" @search="getDataByPage" />
+ <NCard title="宀椾綅淇℃伅鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('system:post:add')"
+ :show-delete="hasAuth('system:post:remove')"
+ :show-export="hasAuth('system:post:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ />
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.postId"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <PostOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ :dept-data="deptData"
+ @submitted="getData"
+ />
+ </NCard>
+ </div>
+ </TableSiderLayout>
+</template>
+
+<style scoped lang="scss">
+.dept-tree {
+ .n-button {
+ --n-padding: 8px !important;
+ }
+
+ :deep(.n-tree__empty) {
+ height: 100%;
+ justify-content: center;
+ }
+
+ :deep(.n-spin-content) {
+ height: 100%;
+ }
+
+ :deep(.infinite-scroll) {
+ height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
+ max-height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
+ }
+
+ @media screen and (max-width: 1024px) {
+ :deep(.infinite-scroll) {
+ height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
+ max-height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
+ }
+ }
+
+ :deep(.n-tree-node) {
+ height: 30px;
+ }
+
+ :deep(.n-tree-node-switcher) {
+ height: 30px;
+ }
+
+ :deep(.n-tree-node-switcher__icon) {
+ font-size: 16px !important;
+ height: 16px !important;
+ width: 16px !important;
+ }
+}
+
+:deep(.n-data-table-wrapper),
+:deep(.n-data-table-base-table),
+:deep(.n-data-table-base-table-body) {
+ height: 100%;
+}
+
+@media screen and (max-width: 800px) {
+ :deep(.n-data-table-base-table-body) {
+ max-height: calc(100vh - 400px - var(--calc-footer-height, 0px));
+ }
+}
+
+@media screen and (max-width: 802px) {
+ :deep(.n-data-table-base-table-body) {
+ max-height: calc(100vh - 473px - var(--calc-footer-height, 0px));
+ }
+}
+
+:deep(.n-card-header__main) {
+ min-width: 69px !important;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/system/post/modules/post-operate-drawer.vue b/ruoyi-plus-soybean/src/views/system/post/modules/post-operate-drawer.vue
new file mode 100755
index 0000000..aa0af9f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/post/modules/post-operate-drawer.vue
@@ -0,0 +1,169 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useLoading } from '@sa/hooks';
+import { fetchCreatePost, fetchUpdatePost } from '@/service/api/system/post';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'PostOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.System.Post | null;
+ /** the dept tree data */
+ deptData?: Api.Common.CommonTreeRecord;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+const { loading: deptLoading, startLoading: startDeptLoading, endLoading: endDeptLoading } = useLoading();
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: '鏂板宀椾綅淇℃伅',
+ edit: '缂栬緫宀椾綅淇℃伅'
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.System.PostOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ deptId: null,
+ postCode: '',
+ postCategory: '',
+ postName: '',
+ postSort: null,
+ status: '0',
+ remark: ''
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'postId' | 'deptId' | 'postCode' | 'postName' | 'postSort' | 'status'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ postId: createRequiredRule('宀椾綅ID涓嶈兘涓虹┖'),
+ deptId: createRequiredRule('褰掑睘閮ㄩ棬涓嶈兘涓虹┖'),
+ postCode: createRequiredRule('宀椾綅缂栫爜涓嶈兘涓虹┖'),
+ postName: createRequiredRule('宀椾綅鍚嶇О涓嶈兘涓虹┖'),
+ postSort: createRequiredRule('鏄剧ず椤哄簭涓嶈兘涓虹┖'),
+ status: createRequiredRule('鐘舵�佷笉鑳戒负绌�')
+};
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ startDeptLoading();
+ Object.assign(model.value, jsonClone(props.rowData));
+ endDeptLoading();
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { postId, deptId, postCode, postCategory, postName, postSort, status, remark } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreatePost({ deptId, postCode, postCategory, postName, postSort, status, remark });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdatePost({
+ postId,
+ deptId,
+ postCode,
+ postCategory,
+ postName,
+ postSort,
+ status,
+ remark
+ });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem label="褰掑睘閮ㄩ棬" path="deptId">
+ <NTreeSelect
+ v-model:value="model.deptId"
+ :loading="deptLoading"
+ clearable
+ :options="deptData as []"
+ label-field="label"
+ key-field="id"
+ :default-expanded-keys="deptData?.length ? [deptData[0].id] : []"
+ placeholder="璇烽�夋嫨褰掑睘閮ㄩ棬"
+ />
+ </NFormItem>
+ <NFormItem label="宀椾綅缂栫爜" path="postCode">
+ <NInput v-model:value="model.postCode" placeholder="璇疯緭鍏ュ矖浣嶇紪鐮�" />
+ </NFormItem>
+ <NFormItem label="绫诲埆缂栫爜" path="postCategory">
+ <NInput v-model:value="model.postCategory" placeholder="璇疯緭鍏ョ被鍒紪鐮�" />
+ </NFormItem>
+ <NFormItem label="宀椾綅鍚嶇О" path="postName">
+ <NInput v-model:value="model.postName" placeholder="璇疯緭鍏ュ矖浣嶅悕绉�" />
+ </NFormItem>
+ <NFormItem label="鏄剧ず椤哄簭" path="postSort">
+ <NInputNumber v-model:value="model.postSort" placeholder="璇疯緭鍏ユ樉绀洪『搴�" />
+ </NFormItem>
+ <NFormItem label="鐘舵��" path="status">
+ <DictRadio v-model:value="model.status" dict-code="sys_normal_disable" />
+ </NFormItem>
+ <NFormItem label="澶囨敞" path="remark">
+ <NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/post/modules/post-search.vue b/ruoyi-plus-soybean/src/views/system/post/modules/post-search.vue
new file mode 100755
index 0000000..95d9131
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/post/modules/post-search.vue
@@ -0,0 +1,86 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { useDict } from '@/hooks/business/dict';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'PostSearch'
+});
+
+interface Emits {
+ (e: 'reset'): void;
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.System.PostSearchParams>('model', { required: true });
+
+const { options: sysCommonStatusOptions } = useDict('sys_normal_disable', false);
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('reset');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:6" label="宀椾綅缂栫爜" path="postCode" class="pr-24px">
+ <NInput v-model:value="model.postCode" placeholder="璇疯緭鍏ュ矖浣嶇紪鐮�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="宀椾綅鍚嶇О" path="postName" class="pr-24px">
+ <NInput v-model:value="model.postName" placeholder="璇疯緭鍏ュ矖浣嶅悕绉�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鐘舵��" path="status" class="pr-24px">
+ <NSelect
+ v-model:value="model.status"
+ placeholder="璇烽�夋嫨鐘舵��"
+ :options="sysCommonStatusOptions"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/role/index.vue b/ruoyi-plus-soybean/src/views/system/role/index.vue
new file mode 100755
index 0000000..ecfd4fd
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/role/index.vue
@@ -0,0 +1,293 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider, NTag } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import { useBoolean } from '@sa/hooks';
+import { dataScopeRecord } from '@/constants/business';
+import { fetchBatchDeleteRole, fetchGetRoleList, fetchUpdateRoleStatus } from '@/service/api/system/role';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import StatusSwitch from '@/components/custom/status-switch.vue';
+import RoleOperateDrawer from './modules/role-operate-drawer.vue';
+import RoleSearch from './modules/role-search.vue';
+import RoleDataScopeDrawer from './modules/role-data-scope-drawer.vue';
+import RoleAuthUserDrawer from './modules/role-auth-user-drawer.vue';
+
+defineOptions({
+ name: 'RoleList'
+});
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+useDict('sys_normal_disable');
+
+const { bool: dataScopeDrawerVisible, setTrue: openDataScopeDrawer } = useBoolean(false);
+const { bool: authUserDrawerVisible, setTrue: openAuthUserDrawer } = useBoolean(false);
+
+const searchParams = ref<Api.System.RoleSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ roleName: null,
+ roleKey: null,
+ status: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetRoleList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'roleName',
+ title: '瑙掕壊鍚嶇О',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'roleKey',
+ title: '瑙掕壊鏉冮檺瀛楃涓�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'roleSort',
+ title: '鏄剧ず椤哄簭',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'dataScope',
+ title: '鏁版嵁鑼冨洿',
+ align: 'center',
+ minWidth: 180,
+ render: row => {
+ return <NTag type="info">{dataScopeRecord[row.dataScope]}</NTag>;
+ }
+ },
+ {
+ key: 'status',
+ title: '瑙掕壊鐘舵��',
+ align: 'center',
+ minWidth: 120,
+ render(row) {
+ return (
+ <StatusSwitch
+ v-model:value={row.status}
+ disabled={row.roleId === 1}
+ info={row.roleKey}
+ onSubmitted={(value, callback) => handleStatusChange(row, value, callback)}
+ />
+ );
+ }
+ },
+ {
+ key: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 230,
+ render: row => {
+ if (row.roleId === 1) return null;
+
+ const editBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.roleId)}
+ />
+ );
+ };
+
+ const dataScopeBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:database"
+ tooltipContent="鏁版嵁鑼冨洿鏉冮檺"
+ onClick={() => handleDataScope(row)}
+ />
+ );
+ };
+
+ const authUserBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:assignment-ind-outline"
+ tooltipContent="鍒嗛厤鐢ㄦ埛"
+ onClick={() => handleAuthUser(row)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.roleId)}
+ />
+ );
+ };
+
+ const buttons = [];
+ if (hasAuth('system:role:edit')) {
+ buttons.push(editBtn());
+ buttons.push(dataScopeBtn());
+ buttons.push(authUserBtn());
+ }
+ if (hasAuth('system:role:remove')) buttons.push(deleteBtn());
+
+ return (
+ <div class="flex-center gap-8px">
+ {buttons.map((btn, index) => (
+ <>
+ {index !== 0 && <NDivider vertical />}
+ {btn}
+ </>
+ ))}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'roleId', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteRole(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(roleId: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteRole([roleId]);
+ if (error) return;
+ onDeleted();
+}
+
+async function edit(roleId: CommonType.IdType) {
+ handleEdit(roleId);
+}
+
+async function handleExport() {
+ download('/system/role/export', searchParams.value, `瑙掕壊_${new Date().getTime()}.xlsx`);
+}
+
+/** 澶勭悊鐘舵�佸垏鎹� */
+async function handleStatusChange(
+ row: Api.System.Role,
+ value: Api.Common.EnableStatus,
+ callback: (flag: boolean) => void
+) {
+ const { error } = await fetchUpdateRoleStatus({
+ roleId: row.roleId,
+ status: value
+ });
+
+ callback(!error);
+
+ if (!error) {
+ window.$message?.success('鐘舵�佷慨鏀规垚鍔�');
+ getData();
+ }
+}
+
+function handleDataScope(row: Api.System.Role) {
+ const findItem = data.value.find(item => item.roleId === row.roleId) || null;
+ editingData.value = jsonClone(findItem);
+ openDataScopeDrawer();
+}
+
+function handleAuthUser(row: Api.System.Role) {
+ const findItem = data.value.find(item => item.roleId === row.roleId) || null;
+ editingData.value = jsonClone(findItem);
+ openAuthUserDrawer();
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <RoleSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard title="瑙掕壊鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('system:role:add')"
+ :show-delete="hasAuth('system:role:remove')"
+ :show-export="hasAuth('system:role:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ />
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.roleId"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <RoleOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getData"
+ />
+ <RoleDataScopeDrawer v-model:visible="dataScopeDrawerVisible" :row-data="editingData" @submitted="getData" />
+ <RoleAuthUserDrawer v-model:visible="authUserDrawerVisible" :row-data="editingData" @submitted="getData" />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/role/modules/role-auth-user-drawer.vue b/ruoyi-plus-soybean/src/views/system/role/modules/role-auth-user-drawer.vue
new file mode 100755
index 0000000..527c347
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/role/modules/role-auth-user-drawer.vue
@@ -0,0 +1,275 @@
+<script setup lang="tsx">
+import { computed, ref, toRaw, watch } from 'vue';
+import { NDatePicker } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import {
+ fetchGetRoleUserList,
+ fetchGetUserList,
+ fetchUpdateRoleAuthUser,
+ fetchUpdateRoleAuthUserCancel
+} from '@/service/api/system';
+import { useAppStore } from '@/store/modules/app';
+import { useDict } from '@/hooks/business/dict';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { arraysEqualSet } from '@/utils/common';
+import { $t } from '@/locales';
+import DictTag from '@/components/custom/dict-tag.vue';
+
+defineOptions({
+ name: 'RoleAuthUserDrawer'
+});
+
+interface Props {
+ /** the edit row data */
+ rowData?: Api.System.Role | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const appStore = useAppStore();
+
+const title = computed(() => '鍒嗛厤鐢ㄦ埛鏉冮檺');
+
+useDict('sys_normal_disable', false);
+
+const searchParams = ref<Api.System.UserSearchParams>({
+ pageNum: 1,
+ pageSize: 20,
+ deptId: null,
+ userName: null,
+ nickName: null,
+ phonenumber: null,
+ status: null,
+ params: {}
+});
+
+const defaultModel = jsonClone(toRaw(searchParams.value));
+
+const { columns, data, getData, getDataByPage, loading, mobilePagination, scrollX } = useNaivePaginatedTable({
+ immediate: false,
+ api: () => fetchGetUserList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64
+ },
+ {
+ key: 'userName',
+ title: '鐢ㄦ埛鍚嶇О',
+ align: 'center',
+ minWidth: 120,
+ ellipsis: true
+ },
+ {
+ key: 'nickName',
+ title: '鐢ㄦ埛鏄电О',
+ align: 'center',
+ minWidth: 120,
+ ellipsis: true
+ },
+ {
+ key: 'deptName',
+ title: '閮ㄩ棬',
+ align: 'center',
+ minWidth: 120,
+ ellipsis: true
+ },
+ {
+ key: 'phonenumber',
+ title: '鎵嬫満鍙风爜',
+ align: 'center',
+ minWidth: 120,
+ ellipsis: true
+ },
+ {
+ key: 'status',
+ title: '鐘舵��',
+ align: 'center',
+ minWidth: 80,
+ render(row) {
+ return <DictTag size="small" value={row.status} dictCode="sys_normal_disable" />;
+ }
+ },
+ {
+ key: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ align: 'center',
+ minWidth: 120
+ }
+ ]
+});
+
+const { checkedRowKeys } = useTableOperate(data, 'userId', getData);
+
+const checkedUserIds = ref<CommonType.IdType[]>([]);
+
+async function handleUpdateModelWhenEdit() {
+ checkedRowKeys.value = [];
+ getDataByPage();
+ const { data: roleUserList } = await fetchGetRoleUserList({
+ roleId: props.rowData?.roleId
+ });
+ checkedUserIds.value = roleUserList?.rows.map(item => item.userId) || [];
+ checkedRowKeys.value = checkedUserIds.value;
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ if (arraysEqualSet(checkedUserIds.value, checkedRowKeys.value)) {
+ window.$message?.warning($t('common.noChange'));
+ return;
+ }
+
+ // 鎵归噺鍙栨秷鐢ㄦ埛鎺堟潈
+ const cancelUserIds = checkedUserIds.value.filter(item => !checkedRowKeys.value.includes(item));
+ if (cancelUserIds.length > 0) {
+ const { error: cancelError } = await fetchUpdateRoleAuthUserCancel(props.rowData!.roleId, cancelUserIds);
+ if (cancelError) return;
+ }
+
+ // 鎵归噺閫夋嫨鐢ㄦ埛鎺堟潈
+ const addUserIds = checkedRowKeys.value.filter(item => !checkedUserIds.value.includes(item));
+ if (addUserIds.length > 0) {
+ const { error: addError } = await fetchUpdateRoleAuthUser(props.rowData!.roleId, addUserIds);
+ if (addError) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ reset();
+ handleUpdateModelWhenEdit();
+ }
+});
+
+const dateRangeCreateTime = ref<[string, string] | null>(null);
+
+const datePickerRef = ref<InstanceType<typeof NDatePicker>>();
+
+function onDateRangeCreateTimeUpdate(value: [string, string] | null) {
+ const params = searchParams.value.params!;
+ if (value && value.length === 2) {
+ [params.beginTime, params.endTime] = value;
+ } else {
+ params.beginTime = undefined;
+ params.endTime = undefined;
+ }
+}
+
+function reset() {
+ dateRangeCreateTime.value = null;
+ Object.assign(searchParams.value, defaultModel);
+}
+</script>
+
+<template>
+ <NDrawer
+ v-model:show="visible"
+ :title="title"
+ display-directive="show"
+ :width="1300"
+ class="max-w-90%"
+ content-class="h-full"
+ wrapper-class="h-full"
+ >
+ <NDrawerContent :title="title" :native-scrollbar="false" closable body-class="h-full" body-content-class="h-full">
+ <div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto">
+ <NForm :model="searchParams" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:8" label="鐢ㄦ埛鍚嶇О" path="userName" class="pr-24px">
+ <NInput v-model:value="searchParams.userName" placeholder="璇疯緭鍏ョ敤鎴峰悕绉�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鐢ㄦ埛鏄电О" path="nickName" class="pr-24px">
+ <NInput v-model:value="searchParams.nickName" placeholder="璇疯緭鍏ョ敤鎴锋樀绉�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鎵嬫満鍙风爜" path="phonenumber" class="pr-24px">
+ <NInput v-model:value="searchParams.phonenumber" placeholder="璇疯緭鍏ユ墜鏈哄彿鐮�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鎵�灞為儴闂�" path="deptId" class="pr-24px">
+ <DeptTreeSelect v-model:value="searchParams.deptId" placeholder="璇烽�夋嫨閮ㄩ棬" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:10" label="鍒涘缓鏃堕棿" path="createTime" class="pr-24px">
+ <NDatePicker
+ ref="datePickerRef"
+ v-model:formatted-value="dateRangeCreateTime"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ @update:formatted-value="onDateRangeCreateTimeUpdate"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" class="pr-24px" :show-feedback="false">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="() => getDataByPage()">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ <TableRowCheckAlert v-model:checked-row-keys="checkedRowKeys" />
+ <NCard :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.userId"
+ :pagination="mobilePagination"
+ class="h-full"
+ />
+ </NCard>
+ </div>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/role/modules/role-data-scope-drawer.vue b/ruoyi-plus-soybean/src/views/system/role/modules/role-data-scope-drawer.vue
new file mode 100755
index 0000000..67fc6a6
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/role/modules/role-data-scope-drawer.vue
@@ -0,0 +1,154 @@
+<script setup lang="ts">
+import { computed, reactive, ref, watch } from 'vue';
+import { useLoading } from '@sa/hooks';
+import { dataScopeOptions } from '@/constants/business';
+import { fetchGetRoleDeptTreeSelect, fetchUpdateRoleDataScope } from '@/service/api/system/role';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+import DeptTree from '@/components/custom/dept-tree.vue';
+
+defineOptions({
+ name: 'RoleDataScopeDrawer'
+});
+
+interface Props {
+ /** the edit row data */
+ rowData?: Api.System.Role | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const deptTreeRef = ref<InstanceType<typeof DeptTree> | null>(null);
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const deptOptions = ref<Api.System.Dept[]>([]);
+
+const { loading: deptLoading, startLoading: startDeptLoading, endLoading: endDeptLoading } = useLoading();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => '鍒嗛厤鏁版嵁鏉冮檺');
+
+type Model = Api.System.RoleOperateParams;
+
+const model: Model = reactive(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ roleId: props.rowData?.roleId,
+ roleName: props.rowData?.roleName,
+ roleKey: props.rowData?.roleKey,
+ roleSort: props.rowData?.roleSort,
+ deptIds: [],
+ menuIds: [],
+ deptCheckStrictly: true,
+ dataScope: '1'
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'dataScope'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ dataScope: createRequiredRule('鏁版嵁鏉冮檺鑼冨洿涓嶈兘涓虹┖')
+};
+
+async function handleUpdateModelWhenEdit() {
+ startDeptLoading();
+ deptOptions.value = [];
+ model.deptIds = [];
+
+ if (props.rowData) {
+ Object.assign(model, props.rowData);
+ const { error, data } = await fetchGetRoleDeptTreeSelect(props.rowData.roleId!);
+ if (error) return;
+ deptOptions.value = data.depts;
+ model.deptIds = data.checkedKeys;
+ }
+ endDeptLoading();
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { roleId, roleName, roleKey, roleSort, dataScope, deptIds, menuIds } = model;
+
+ const { error } = await fetchUpdateRoleDataScope({
+ roleId,
+ roleName,
+ roleKey,
+ roleSort,
+ dataScope,
+ deptIds: dataScope === '2' ? deptIds : [],
+ menuIds
+ });
+ if (error) return;
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem label="瑙掕壊鍚嶇О" path="roleName">
+ <NInput v-model:value="model.roleName" disabled placeholder="璇疯緭鍏ヨ鑹插悕绉�" />
+ </NFormItem>
+ <NFormItem path="roleKey">
+ <template #label>
+ <div class="flex-center">
+ <FormTip content="鎺у埗鍣ㄤ腑瀹氫箟鐨勬潈闄愬瓧绗︼紝濡傦細@SaCheckRole('admin')" />
+ <span class="pl-3px">鏉冮檺瀛楃</span>
+ </div>
+ </template>
+ <NInput v-model:value="model.roleKey" disabled placeholder="璇疯緭鍏ユ潈闄愬瓧绗�" />
+ </NFormItem>
+ <NFormItem label="鏉冮檺鑼冨洿" path="dataScope">
+ <NSelect v-model:value="model.dataScope" :options="dataScopeOptions" />
+ </NFormItem>
+ <NFormItem v-if="model.dataScope === '2'" label="鏁版嵁鏉冮檺" path="deptIds" class="pr-24px">
+ <DeptTree
+ v-if="visible"
+ ref="deptTreeRef"
+ v-model:value="model.deptIds"
+ v-model:options="deptOptions"
+ v-model:loading="deptLoading"
+ v-model:cascade="model.deptCheckStrictly"
+ :immediate="false"
+ />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/role/modules/role-operate-drawer.vue b/ruoyi-plus-soybean/src/views/system/role/modules/role-operate-drawer.vue
new file mode 100755
index 0000000..36b6d98
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/role/modules/role-operate-drawer.vue
@@ -0,0 +1,198 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useLoading } from '@sa/hooks';
+import { fetchCreateRole, fetchUpdateRole } from '@/service/api/system/role';
+import { fetchGetRoleMenuTreeSelect } from '@/service/api/system';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { useDict } from '@/hooks/business/dict';
+import { $t } from '@/locales';
+import MenuTree from '@/components/custom/menu-tree.vue';
+
+defineOptions({
+ name: 'RoleOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.System.Role | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const menuTreeRef = ref<InstanceType<typeof MenuTree> | null>(null);
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { options: sysNormalDisableOptions } = useDict('sys_normal_disable', false);
+
+const menuOptions = ref<Api.System.MenuList>([]);
+
+const { loading: menuLoading, startLoading: startMenuLoading, endLoading: stopMenuLoading } = useLoading();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: '鏂板瑙掕壊',
+ edit: '缂栬緫瑙掕壊'
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.System.RoleOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ menuIds: [],
+ roleName: '',
+ roleKey: '',
+ roleSort: 1,
+ menuCheckStrictly: true,
+ status: '0',
+ remark: ''
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'roleId' | 'roleName' | 'roleKey' | 'status'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ roleId: createRequiredRule('瑙掕壊ID涓嶈兘涓虹┖'),
+ roleName: createRequiredRule('瑙掕壊鍚嶇О涓嶈兘涓虹┖'),
+ roleKey: createRequiredRule('瑙掕壊鏉冮檺瀛楃涓蹭笉鑳戒负绌�'),
+ status: createRequiredRule('瑙掕壊鐘舵�佷笉鑳戒负绌�')
+};
+
+async function handleUpdateModelWhenEdit() {
+ menuOptions.value = [];
+ model.value = createDefaultModel();
+ model.value.menuIds = [];
+
+ if (props.operateType === 'add') {
+ menuTreeRef.value?.refresh();
+ return;
+ }
+
+ if (props.operateType === 'edit' && props.rowData) {
+ startMenuLoading();
+ Object.assign(model.value, jsonClone(props.rowData));
+ const { data, error } = await fetchGetRoleMenuTreeSelect(model.value.roleId!);
+ if (error) return;
+ model.value.menuIds = data.checkedKeys;
+ menuOptions.value = data.menus;
+ stopMenuLoading();
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+ const { roleId, roleName, roleKey, roleSort, menuCheckStrictly, status, remark } = model.value;
+ const menuIds = menuTreeRef.value?.getCheckedMenuIds();
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateRole({
+ roleName,
+ roleKey,
+ roleSort,
+ menuCheckStrictly,
+ status,
+ remark,
+ menuIds
+ });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateRole({
+ roleId,
+ roleName,
+ roleKey,
+ roleSort,
+ menuCheckStrictly,
+ status,
+ remark,
+ menuIds
+ });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem label="瑙掕壊鍚嶇О" path="roleName">
+ <NInput v-model:value="model.roleName" placeholder="璇疯緭鍏ヨ鑹插悕绉�" />
+ </NFormItem>
+ <NFormItem path="roleKey">
+ <template #label>
+ <div class="flex-center">
+ <FormTip content="鎺у埗鍣ㄤ腑瀹氫箟鐨勬潈闄愬瓧绗︼紝濡傦細@SaCheckRole('admin')" />
+ <span class="pl-3px">鏉冮檺瀛楃</span>
+ </div>
+ </template>
+ <NInput v-model:value="model.roleKey" placeholder="璇疯緭鍏ユ潈闄愬瓧绗�" />
+ </NFormItem>
+ <NFormItem label="鏄剧ず椤哄簭" path="roleSort">
+ <NInputNumber v-model:value="model.roleSort" placeholder="璇疯緭鍏ユ樉绀洪『搴�" />
+ </NFormItem>
+ <NFormItem label="瑙掕壊鐘舵��" path="status">
+ <NRadioGroup v-model:value="model.status">
+ <NRadio v-for="item in sysNormalDisableOptions" :key="item.value" :value="item.value" :label="item.label" />
+ </NRadioGroup>
+ </NFormItem>
+ <NFormItem label="鑿滃崟鏉冮檺" path="menuIds" class="pr-24px">
+ <MenuTree
+ v-if="visible"
+ ref="menuTreeRef"
+ v-model:checked-keys="model.menuIds"
+ v-model:options="menuOptions"
+ v-model:cascade="model.menuCheckStrictly"
+ v-model:loading="menuLoading"
+ :immediate="operateType === 'add'"
+ />
+ </NFormItem>
+ <NFormItem label="澶囨敞" path="remark">
+ <NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/role/modules/role-search.vue b/ruoyi-plus-soybean/src/views/system/role/modules/role-search.vue
new file mode 100755
index 0000000..f4f6b86
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/role/modules/role-search.vue
@@ -0,0 +1,109 @@
+<script setup lang="ts">
+import { ref, toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { useDict } from '@/hooks/business/dict';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'RoleSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const dateRangeCreateTime = ref<[string, string] | null>(null);
+
+const model = defineModel<Api.System.RoleSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+const { options: sysNormalDisableOptions } = useDict('sys_normal_disable', false);
+
+function onDateRangeCreateTimeUpdate(value: [string, string] | null) {
+ const params = model.value.params!;
+ if (value && value.length === 2) {
+ [params.beginTime, params.endTime] = value;
+ } else {
+ params.beginTime = undefined;
+ params.endTime = undefined;
+ }
+}
+
+function resetModel() {
+ dateRangeCreateTime.value = null;
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:6" label="瑙掕壊鍚嶇О" path="roleName" class="pr-24px">
+ <NInput v-model:value="model.roleName" placeholder="璇疯緭鍏ヨ鑹插悕绉�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鏉冮檺瀛楃" path="roleKey" class="pr-24px">
+ <NInput v-model:value="model.roleKey" placeholder="璇疯緭鍏ユ潈闄愬瓧绗�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="瑙掕壊鐘舵��" path="status" class="pr-24px">
+ <NSelect
+ v-model:value="model.status"
+ placeholder="璇烽�夋嫨瑙掕壊鐘舵��"
+ :options="sysNormalDisableOptions"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鍒涘缓鏃堕棿" path="createTime" class="pr-24px">
+ <NDatePicker
+ v-model:formatted-value="dateRangeCreateTime"
+ update-value-on-close
+ class="w-full"
+ type="daterange"
+ value-format="yyyy-MM-dd"
+ clearable
+ @update:formatted-value="onDateRangeCreateTimeUpdate"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/tenant-package/index.vue b/ruoyi-plus-soybean/src/views/system/tenant-package/index.vue
new file mode 100755
index 0000000..364618f
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/tenant-package/index.vue
@@ -0,0 +1,237 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NDivider } from 'naive-ui';
+import {
+ fetchBatchDeleteTenantPackage,
+ fetchGetTenantPackageList,
+ fetchUpdateTenantPackageStatus
+} from '@/service/api/system/tenant-package';
+import { useAppStore } from '@/store/modules/app';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import TableHeaderOperation from '@/components/advanced/table-header-operation.vue';
+import StatusSwitch from '@/components/custom/status-switch.vue';
+import TenantPackageSearch from './modules/tenant-package-search.vue';
+import TenantPackageOperateDrawer from './modules/tenant-package-operate-drawer.vue';
+
+defineOptions({
+ name: 'TenantPackageList'
+});
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+
+useDict('sys_normal_disable', false);
+
+const searchParams = ref<Api.System.TenantPackageSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ packageName: null,
+ status: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetTenantPackageList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64
+ },
+ {
+ key: 'packageName',
+ title: $t('page.system.tenantPackage.packageName'),
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'status',
+ title: $t('page.system.tenantPackage.status'),
+ align: 'center',
+ minWidth: 120,
+ render: row => {
+ return (
+ <StatusSwitch
+ v-model:value={row.status}
+ info={row.packageName}
+ onSubmitted={(value, callback) => handleStatusChange(row, value, callback)}
+ />
+ );
+ }
+ },
+ {
+ key: 'remark',
+ title: $t('page.system.tenantPackage.remark'),
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 130,
+ render: row => {
+ const divider = () => {
+ if (!hasAuth('system:tenantPackage:edit') || !hasAuth('system:tenantPackage:remove')) {
+ return null;
+ }
+ return <NDivider vertical />;
+ };
+
+ const editBtn = () => {
+ if (!hasAuth('system:tenantPackage:edit')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.packageId!)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ if (!hasAuth('system:tenantPackage:remove')) {
+ return null;
+ }
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.packageId!)}
+ />
+ );
+ };
+
+ return (
+ <div class="flex-center gap-8px">
+ {editBtn()}
+ {divider()}
+ {deleteBtn()}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'packageId', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteTenantPackage(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(packageId: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteTenantPackage([packageId]);
+ if (error) return;
+ onDeleted();
+}
+
+function edit(packageId: CommonType.IdType) {
+ handleEdit(packageId);
+}
+
+function handleExport() {
+ download(
+ '/system/tenant/package/export',
+ searchParams,
+ `${$t('page.system.tenantPackage.title')}_${new Date().getTime()}.xlsx`
+ );
+}
+
+/** 澶勭悊鐘舵�佸垏鎹� */
+async function handleStatusChange(
+ row: Api.System.TenantPackage,
+ value: Api.Common.EnableStatus,
+ callback: (flag: boolean) => void
+) {
+ const { error } = await fetchUpdateTenantPackageStatus({
+ packageId: row.packageId,
+ status: value
+ });
+
+ callback(!error);
+
+ if (!error) {
+ window.$message?.success($t('page.system.tenantPackage.statusChangeSuccess'));
+ getData();
+ }
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <TenantPackageSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard
+ :title="$t('page.system.tenantPackage.title')"
+ :bordered="false"
+ size="small"
+ class="card-wrapper sm:flex-1-hidden"
+ >
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('system:tenantPackage:add')"
+ :show-delete="hasAuth('system:tenantPackage:remove')"
+ :show-export="hasAuth('system:tenantPackage:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ />
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.packageId"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <TenantPackageOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getData"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/tenant-package/modules/tenant-package-operate-drawer.vue b/ruoyi-plus-soybean/src/views/system/tenant-package/modules/tenant-package-operate-drawer.vue
new file mode 100755
index 0000000..44e2784
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/tenant-package/modules/tenant-package-operate-drawer.vue
@@ -0,0 +1,174 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useLoading } from '@sa/hooks';
+import { fetchCreateTenantPackage, fetchUpdateTenantPackage } from '@/service/api/system/tenant-package';
+import { fetchGetTenantPackageMenuTreeSelect } from '@/service/api/system/menu';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import MenuTree from '@/components/custom/menu-tree.vue';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'TenantPackageOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.System.TenantPackage | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const menuTreeRef = ref<InstanceType<typeof MenuTree> | null>(null);
+
+const menuOptions = ref<Api.System.MenuList>([]);
+
+const { loading: menuLoading, startLoading: startMenuLoading, endLoading: stopMenuLoading } = useLoading();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: $t('page.system.tenantPackage.addTenantPackage'),
+ edit: $t('page.system.tenantPackage.editTenantPackage')
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.System.TenantPackageOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ packageName: '',
+ menuIds: [],
+ remark: '',
+ menuCheckStrictly: true
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'packageId' | 'packageName'>;
+
+const rules: Record<RuleKey, App.Global.FormRule> = {
+ packageId: createRequiredRule($t('page.system.tenantPackage.form.packageName.invalid')),
+ packageName: createRequiredRule($t('page.system.tenantPackage.form.packageName.required'))
+};
+
+async function handleUpdateModelWhenEdit() {
+ menuOptions.value = [];
+ model.value = createDefaultModel();
+ model.value.menuIds = [];
+
+ if (props.operateType === 'add') {
+ const { data, error } = await fetchGetTenantPackageMenuTreeSelect(0);
+ if (error) return;
+ model.value.menuIds = data.checkedKeys || [];
+ menuOptions.value = data.menus;
+ return;
+ }
+
+ if (props.operateType === 'edit' && props.rowData) {
+ startMenuLoading();
+ Object.assign(model.value, jsonClone(props.rowData));
+ const { data, error } = await fetchGetTenantPackageMenuTreeSelect(model.value.packageId!);
+ if (error) return;
+ model.value.menuIds = data.checkedKeys || [];
+ menuOptions.value = data.menus;
+ stopMenuLoading();
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { packageId, packageName, remark, menuCheckStrictly } = model.value;
+
+ const menuIds = menuTreeRef.value?.getCheckedMenuIds();
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateTenantPackage({ packageName, menuIds, remark, menuCheckStrictly });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateTenantPackage({
+ packageId,
+ packageName,
+ menuIds,
+ remark,
+ menuCheckStrictly
+ });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.saveSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem :label="$t('page.system.tenantPackage.packageName')" path="packageName">
+ <NInput
+ v-model:value="model.packageName"
+ :placeholder="$t('page.system.tenantPackage.form.packageName.required')"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.tenantPackage.menuIds')" path="menuIds">
+ <MenuTree
+ v-if="visible"
+ ref="menuTreeRef"
+ v-model:checked-keys="model.menuIds"
+ v-model:options="menuOptions"
+ v-model:cascade="model.menuCheckStrictly"
+ v-model:loading="menuLoading"
+ :immediate="false"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.tenantPackage.remark')" path="remark">
+ <NInput
+ v-model:value="model.remark"
+ :placeholder="$t('page.system.tenantPackage.form.remark.required')"
+ type="textarea"
+ />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/tenant-package/modules/tenant-package-search.vue b/ruoyi-plus-soybean/src/views/system/tenant-package/modules/tenant-package-search.vue
new file mode 100755
index 0000000..baa6217
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/tenant-package/modules/tenant-package-search.vue
@@ -0,0 +1,95 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useDict } from '@/hooks/business/dict';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'TenantPackageSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const { options: sysNormalDisableOptions } = useDict('sys_normal_disable', false);
+
+const model = defineModel<Api.System.TenantPackageSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi
+ span="24 s:12 m:8"
+ :label="$t('page.system.tenantPackage.packageName')"
+ path="packageName"
+ class="pr-24px"
+ >
+ <NInput
+ v-model:value="model.packageName"
+ :placeholder="$t('page.system.tenantPackage.form.packageName.required')"
+ />
+ </NFormItemGi>
+ <NFormItemGi
+ span="24 s:12 m:8"
+ :label="$t('page.system.tenantPackage.status')"
+ path="status"
+ class="pr-24px"
+ >
+ <NSelect
+ v-model:value="model.status"
+ :placeholder="$t('page.system.tenantPackage.form.status.required')"
+ :options="sysNormalDisableOptions"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:24 m:8" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/tenant/index.vue b/ruoyi-plus-soybean/src/views/system/tenant/index.vue
new file mode 100755
index 0000000..0a3afce
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/tenant/index.vue
@@ -0,0 +1,302 @@
+<script setup lang="tsx">
+import { computed, ref } from 'vue';
+import { NButton, NDivider } from 'naive-ui';
+import {
+ fetchBatchDeleteTenant,
+ fetchGetTenantList,
+ fetchSyncTenantConfig,
+ fetchSyncTenantDict,
+ fetchSyncTenantPackage
+} from '@/service/api/system/tenant';
+import { useAppStore } from '@/store/modules/app';
+import { useAuthStore } from '@/store/modules/auth';
+import { useAuth } from '@/hooks/business/auth';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDownload } from '@/hooks/business/download';
+import { useDict } from '@/hooks/business/dict';
+import DictTag from '@/components/custom/dict-tag.vue';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import TenantOperateDrawer from './modules/tenant-operate-drawer.vue';
+import TenantSearch from './modules/tenant-search.vue';
+
+defineOptions({
+ name: 'TenantList'
+});
+
+useDict('sys_normal_disable');
+
+const appStore = useAppStore();
+const { download } = useDownload();
+const { hasAuth } = useAuth();
+const { userInfo } = useAuthStore();
+
+const isSuperAdmin = computed(() => {
+ return userInfo.user?.userId === 1;
+});
+
+const searchParams = ref<Api.System.TenantSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ tenantId: null,
+ contactUserName: null,
+ contactPhone: null,
+ companyName: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetTenantList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'tenantId',
+ title: '绉熸埛缂栧彿',
+ align: 'center',
+ minWidth: 80
+ },
+ {
+ key: 'contactUserName',
+ title: '鑱旂郴浜�',
+ align: 'center',
+ minWidth: 80
+ },
+ {
+ key: 'contactPhone',
+ title: '鑱旂郴鐢佃瘽',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'companyName',
+ title: '浼佷笟鍚嶇О',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'expireTime',
+ title: '杩囨湡鏃堕棿',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'status',
+ title: '绉熸埛鐘舵��',
+ align: 'center',
+ minWidth: 120,
+ render(row) {
+ return <DictTag size="small" value={row.status} dictCode="sys_normal_disable" />;
+ }
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 180,
+ render: row => {
+ if (row.tenantId === '000000') return null;
+
+ const editBtn = () => {
+ return (
+ <ButtonIcon
+ type="primary"
+ text
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.id!)}
+ />
+ );
+ };
+
+ const syncBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:sync-outline"
+ tooltipContent="鍚屾濂楅"
+ popconfirmContent={`纭鍚屾[${row.companyName}]鐨勫椁愬悧?`}
+ onPositiveClick={() => handleSyncTenantPackage(row)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.id!)}
+ />
+ );
+ };
+
+ const buttons = [];
+
+ if (hasAuth('system:tenant:edit')) buttons.push(editBtn());
+ if (hasAuth('system:tenant:edit')) buttons.push(syncBtn());
+ if (hasAuth('system:tenant:delete')) buttons.push(deleteBtn());
+
+ return (
+ <div class="flex-center gap-8px">
+ {buttons.map((btn, index) => (
+ <>
+ {index !== 0 && <NDivider vertical />}
+ {btn}
+ </>
+ ))}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'id', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteTenant(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteTenant([id]);
+ if (error) return;
+ onDeleted();
+}
+
+async function edit(id: CommonType.IdType) {
+ handleEdit(id);
+}
+
+async function handleSyncTenantDict() {
+ window.$dialog?.warning({
+ title: '鍚屾绉熸埛瀛楀吀',
+ content: '纭鍚屾绉熸埛瀛楀吀鍚�?',
+ positiveText: '纭',
+ negativeText: '鍙栨秷',
+ onPositiveClick: async () => {
+ const { error } = await fetchSyncTenantDict();
+ if (error) return;
+ window.$message?.success('鍚屾绉熸埛瀛楀吀鎴愬姛');
+ await getData();
+ }
+ });
+}
+
+async function handleSyncTenantConfig() {
+ window.$dialog?.warning({
+ title: '鍚屾绉熸埛鍙傛暟閰嶇疆',
+ content: '纭鍚屾绉熸埛鍙傛暟閰嶇疆鍚�?',
+ positiveText: '纭',
+ negativeText: '鍙栨秷',
+ onPositiveClick: async () => {
+ const { error } = await fetchSyncTenantConfig();
+ if (error) return;
+ window.$message?.success('鍚屾绉熸埛鍙傛暟閰嶇疆鎴愬姛');
+ await getData();
+ }
+ });
+}
+
+async function handleSyncTenantPackage(row: Api.System.Tenant) {
+ const params: Api.System.TenantPackageSyncParams = {
+ tenantId: row.tenantId,
+ packageId: row.packageId
+ };
+ const { error } = await fetchSyncTenantPackage(params);
+ if (error) return;
+ window.$message?.success('鍚屾绉熸埛濂楅鎴愬姛');
+ await getData();
+}
+
+async function handleExport() {
+ download('/system/tenant/export', searchParams.value, `绉熸埛鍒楄〃_${new Date().getTime()}.xlsx`);
+}
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <TenantSearch v-model:model="searchParams" @search="getDataByPage" />
+ <NCard title="绉熸埛鍒楄〃" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('system:tenant:add')"
+ :show-delete="hasAuth('system:tenant:delete')"
+ :show-export="hasAuth('system:tenant:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ >
+ <template #prefix>
+ <NButton v-if="isSuperAdmin" ghost size="small" @click="handleSyncTenantDict">
+ <template #icon>
+ <icon-material-symbols-sync-outline />
+ </template>
+ 鍚屾绉熸埛瀛楀吀
+ </NButton>
+ <NButton v-if="isSuperAdmin" ghost size="small" @click="handleSyncTenantConfig">
+ <template #icon>
+ <icon-material-symbols-sync-outline />
+ </template>
+ 鍚屾绉熸埛鍙傛暟閰嶇疆
+ </NButton>
+ </template>
+ </TableHeaderOperation>
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.id"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <TenantOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ @submitted="getData"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped>
+:deep(.n-card-header__main) {
+ min-width: 36px !important;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/system/tenant/modules/tenant-operate-drawer.vue b/ruoyi-plus-soybean/src/views/system/tenant/modules/tenant-operate-drawer.vue
new file mode 100755
index 0000000..e937f83
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/tenant/modules/tenant-operate-drawer.vue
@@ -0,0 +1,302 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { useLoading } from '@sa/hooks';
+import { jsonClone } from '@sa/utils';
+import { fetchCreateTenant, fetchUpdateTenant } from '@/service/api/system/tenant';
+import { fetchGetTenantPackageSelectList } from '@/service/api/system/tenant-package';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'TenantOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.System.Tenant | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule, patternRules } = useFormRules();
+const { loading: packageLoading, startLoading: startPackageLoading, endLoading: endPackageLoading } = useLoading();
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: '鏂板绉熸埛',
+ edit: '缂栬緫绉熸埛'
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.System.TenantOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ tenantId: '',
+ contactUserName: '',
+ contactPhone: '',
+ companyName: '',
+ licenseNumber: '',
+ address: '',
+ intro: '',
+ domain: '',
+ remark: '',
+ packageId: null,
+ expireTime: null,
+ accountCount: null,
+ status: '0',
+ username: '',
+ password: ''
+ };
+}
+
+type RuleKey = Extract<
+ keyof Model,
+ 'id' | 'contactUserName' | 'contactPhone' | 'companyName' | 'packageId' | 'accountCount' | 'username' | 'password'
+>;
+
+const rules: Record<RuleKey, App.Global.FormRule | App.Global.FormRule[]> = {
+ id: createRequiredRule('id涓嶈兘涓虹┖'),
+ contactUserName: createRequiredRule('鑱旂郴浜轰笉鑳戒负绌�'),
+ contactPhone: [createRequiredRule('鑱旂郴鐢佃瘽涓嶈兘涓虹┖'), { ...patternRules.phone, trigger: ['blur', 'change'] }],
+ companyName: createRequiredRule('浼佷笟鍚嶇О涓嶈兘涓虹┖'),
+ packageId: createRequiredRule('绉熸埛濂楅涓嶈兘涓虹┖'),
+ accountCount: createRequiredRule('鐢ㄦ埛鏁伴噺涓嶈兘涓虹┖'),
+ username: [
+ createRequiredRule('绠$悊鍛樿处鍙蜂笉鑳戒负绌�'),
+ {
+ min: 2,
+ max: 20,
+ message: '璐﹀彿闀垮害蹇呴』浠嬩簬2-20涔嬮棿',
+ trigger: ['blur', 'change']
+ }
+ ],
+ password: [
+ createRequiredRule('绠$悊鍛樺瘑鐮佷笉鑳戒负绌�'),
+ {
+ min: 5,
+ max: 20,
+ message: '瀵嗙爜闀垮害蹇呴』浠嬩簬5-20涔嬮棿',
+ trigger: ['blur', 'change']
+ }
+ ]
+};
+
+/** the enabled package options */
+const packageOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
+async function getPackageOptions() {
+ startPackageLoading();
+ const { error, data } = await fetchGetTenantPackageSelectList();
+ if (!error) {
+ packageOptions.value = data.map(item => ({
+ label: item.packageName,
+ value: item.packageId
+ }));
+ }
+ endPackageLoading();
+}
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'edit' && props.rowData) {
+ Object.assign(model.value, jsonClone(props.rowData));
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const {
+ id,
+ tenantId,
+ contactUserName,
+ contactPhone,
+ companyName,
+ username,
+ password,
+ licenseNumber,
+ address,
+ intro,
+ domain,
+ remark,
+ packageId,
+ expireTime,
+ accountCount,
+ status
+ } = model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateTenant({
+ contactUserName,
+ contactPhone,
+ companyName,
+ username,
+ password,
+ licenseNumber,
+ address,
+ intro,
+ domain,
+ remark,
+ packageId,
+ expireTime,
+ accountCount,
+ status
+ });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateTenant({
+ id,
+ tenantId,
+ contactUserName,
+ contactPhone,
+ companyName,
+ licenseNumber,
+ address,
+ intro,
+ domain,
+ remark,
+ packageId,
+ expireTime,
+ accountCount,
+ status
+ });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ getPackageOptions();
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NDivider>鍩烘湰淇℃伅</NDivider>
+ <NFormItem label="浼佷笟鍚嶇О" path="companyName">
+ <NInput v-model:value="model.companyName" placeholder="璇疯緭鍏ヤ紒涓氬悕绉�" />
+ </NFormItem>
+ <NFormItem label="鑱旂郴浜�" path="contactUserName">
+ <NInput v-model:value="model.contactUserName" placeholder="璇疯緭鍏ヨ仈绯讳汉" />
+ </NFormItem>
+ <NFormItem label="鑱旂郴鐢佃瘽" path="contactPhone">
+ <NInput v-model:value="model.contactPhone" placeholder="璇疯緭鍏ヨ仈绯荤數璇�" />
+ </NFormItem>
+ <div v-if="props.operateType === 'add'">
+ <NDivider>绠$悊鍛樹俊鎭�</NDivider>
+ <NFormItem label="绠$悊鍛樿处鍙�" path="username">
+ <NInput v-model:value="model.username" placeholder="璇疯緭鍏ョ鐞嗗憳璐﹀彿" />
+ </NFormItem>
+ <NFormItem label="绠$悊鍛樺瘑鐮�" path="password">
+ <NInput
+ v-model:value="model.password"
+ type="password"
+ show-password-on="click"
+ placeholder="璇疯緭鍏ョ鐞嗗憳瀵嗙爜"
+ />
+ </NFormItem>
+ </div>
+ <NDivider>绉熸埛璁剧疆</NDivider>
+ <NFormItem label="绉熸埛濂楅" path="packageId">
+ <NSelect
+ v-model:value="model.packageId"
+ clearable
+ :disabled="props.operateType === 'edit'"
+ placeholder="璇烽�夋嫨绉熸埛濂楅"
+ :options="packageOptions"
+ :loading="packageLoading"
+ />
+ </NFormItem>
+ <NFormItem label="杩囨湡鏃堕棿" path="expireTime">
+ <NDatePicker
+ v-model:formatted-value="model.expireTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ class="w-full"
+ />
+ </NFormItem>
+ <NFormItem path="accountCount">
+ <template #label>
+ <div class="flex-center">
+ <FormTip content="-1涓嶉檺鍒剁敤鎴锋暟閲�" />
+ <span>鐢ㄦ埛鏁伴噺</span>
+ </div>
+ </template>
+ <NInputNumber v-model:value="model.accountCount" placeholder="璇疯緭鍏ョ敤鎴锋暟閲�" min="-1" class="w-full" />
+ </NFormItem>
+ <NFormItem path="domain">
+ <template #label>
+ <div class="flex-center">
+ <FormTip
+ content="鍙~鍐欏煙鍚�/绔彛 濉啓鍩熷悕濡�: www.test.com 鎴栬�� www.test.com:8080 濉啓ip:绔彛濡�: 127.0.0.1:8080"
+ />
+ <span>缁戝畾鍩熷悕</span>
+ </div>
+ </template>
+ <NInputGroup>
+ <NInputGroupLabel>http(s)://</NInputGroupLabel>
+ <NInput v-model:value="model.domain" placeholder="璇疯緭鍏�" />
+ </NInputGroup>
+ </NFormItem>
+ <NFormItem label="绉熸埛鐘舵��" path="status">
+ <DictRadio v-model:value="model.status" dict-code="sys_normal_disable" />
+ </NFormItem>
+ <NDivider>浼佷笟淇℃伅</NDivider>
+ <NFormItem label="浼佷笟鍦板潃" path="address">
+ <NInput v-model:value="model.address" placeholder="璇疯緭鍏ヤ紒涓氬湴鍧�" />
+ </NFormItem>
+ <NFormItem label="浼佷笟浠g爜" path="licenseNumber">
+ <NInput v-model:value="model.licenseNumber" placeholder="璇疯緭鍏ヤ紒涓氫唬鐮�" />
+ </NFormItem>
+ <NFormItem label="浼佷笟绠�浠�" path="intro">
+ <NInput v-model:value="model.intro" type="textarea" placeholder="璇疯緭鍏ヤ紒涓氱畝浠�" />
+ </NFormItem>
+ <NFormItem label="澶囨敞" path="remark">
+ <NInput v-model:value="model.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
+@/service/api/system/tenant-package
diff --git a/ruoyi-plus-soybean/src/views/system/tenant/modules/tenant-search.vue b/ruoyi-plus-soybean/src/views/system/tenant/modules/tenant-search.vue
new file mode 100755
index 0000000..debb84b
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/tenant/modules/tenant-search.vue
@@ -0,0 +1,80 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'TenantSearch'
+});
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.System.TenantSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:6" label="绉熸埛缂栧彿" path="tenantId" class="pr-24px">
+ <NInput v-model:value="model.tenantId as string" placeholder="璇疯緭鍏ョ鎴风紪鍙�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鑱旂郴浜�" path="contactUserName" class="pr-24px">
+ <NInput v-model:value="model.contactUserName" placeholder="璇疯緭鍏ヨ仈绯讳汉" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鑱旂郴鐢佃瘽" path="contactPhone" class="pr-24px">
+ <NInput v-model:value="model.contactPhone" placeholder="璇疯緭鍏ヨ仈绯荤數璇�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="浼佷笟鍚嶇О" path="companyName" class="pr-24px">
+ <NInput v-model:value="model.companyName" placeholder="璇疯緭鍏ヤ紒涓氬悕绉�" />
+ </NFormItemGi>
+ <NFormItemGi span="24" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/user/index.vue b/ruoyi-plus-soybean/src/views/system/user/index.vue
new file mode 100755
index 0000000..0c559ef
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/user/index.vue
@@ -0,0 +1,442 @@
+<script setup lang="tsx">
+import { computed, ref } from 'vue';
+import { NAvatar, NButton, NDivider, NEllipsis } from 'naive-ui';
+import { useBoolean, useLoading } from '@sa/hooks';
+import { jsonClone } from '@sa/utils';
+import { fetchBatchDeleteUser, fetchGetDeptTree, fetchGetUserList, fetchUpdateUserStatus } from '@/service/api/system';
+import { useAppStore } from '@/store/modules/app';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDict } from '@/hooks/business/dict';
+import { useAuth } from '@/hooks/business/auth';
+import { useDownload } from '@/hooks/business/download';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import { $t } from '@/locales';
+import StatusSwitch from '@/components/custom/status-switch.vue';
+import DictTag from '@/components/custom/dict-tag.vue';
+import UserOperateDrawer from './modules/user-operate-drawer.vue';
+import UserImportModal from './modules/user-import-modal.vue';
+import UserPasswordDrawer from './modules/user-password-drawer.vue';
+import UserSearch from './modules/user-search.vue';
+
+defineOptions({
+ name: 'UserList'
+});
+
+useDict('sys_user_sex');
+useDict('sys_normal_disable');
+
+const { hasAuth } = useAuth();
+const appStore = useAppStore();
+const { download } = useDownload();
+
+const { bool: importVisible, setTrue: openImportModal } = useBoolean();
+const { bool: passwordVisible, setTrue: openPasswordDrawer } = useBoolean();
+
+const searchParams = ref<Api.System.UserSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ deptId: null,
+ userName: null,
+ nickName: null,
+ phonenumber: null,
+ status: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetUserList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'userName',
+ title: $t('page.system.user.userName'),
+ align: 'left',
+ width: 220,
+ ellipsis: true,
+ render: row => {
+ return (
+ <div class="flex items-center justify-center gap-2">
+ <NAvatar src={row.avatar} class="bg-primary">
+ {row.avatar ? undefined : row.nickName.charAt(0)}
+ </NAvatar>
+ <div class="max-w-160px flex flex-col">
+ <NEllipsis>{row.userName}</NEllipsis>
+ <NEllipsis>{row.nickName}</NEllipsis>
+ </div>
+ </div>
+ );
+ }
+ },
+ {
+ key: 'sex',
+ title: $t('page.system.user.sex'),
+ align: 'center',
+ width: 80,
+ ellipsis: true,
+ render(row) {
+ return <DictTag value={row.sex} dictCode="sys_user_sex" />;
+ }
+ },
+ {
+ key: 'deptName',
+ title: $t('page.system.user.deptName'),
+ align: 'center',
+ width: 120,
+ ellipsis: true
+ },
+ {
+ key: 'email',
+ title: $t('page.system.user.email'),
+ align: 'center',
+ width: 120,
+ ellipsis: true
+ },
+ {
+ key: 'phonenumber',
+ title: $t('page.system.user.phonenumber'),
+ align: 'center',
+ width: 120,
+ ellipsis: true
+ },
+ {
+ key: 'status',
+ title: $t('page.system.user.status'),
+ align: 'center',
+ width: 80,
+ render(row) {
+ return (
+ <StatusSwitch
+ v-model:value={row.status}
+ disabled={row.userId === 1}
+ info={row.userName}
+ onSubmitted={(value, callback) => handleStatusChange(row, value, callback)}
+ />
+ );
+ }
+ },
+ {
+ key: 'createTime',
+ title: $t('page.system.user.createTime'),
+ align: 'center',
+ width: 120
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 150,
+ render: row => {
+ if (row.userId === 1) return null;
+
+ const editBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.userId)}
+ />
+ );
+ };
+
+ const passwordBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="primary"
+ icon="material-symbols:key-vertical-outline"
+ tooltipContent="閲嶇疆瀵嗙爜"
+ onClick={() => handleResetPwd(row.userId)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ return (
+ <ButtonIcon
+ text
+ type="error"
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.userId)}
+ />
+ );
+ };
+
+ const buttons = [];
+ if (hasAuth('system:user:edit')) buttons.push(editBtn());
+ if (hasAuth('system:user:resetPwd')) buttons.push(passwordBtn());
+ if (hasAuth('system:user:remove')) buttons.push(deleteBtn());
+
+ return (
+ <div class="flex-center gap-8px">
+ {buttons.map((btn, index) => (
+ <>
+ {index !== 0 && <NDivider vertical />}
+ {btn}
+ </>
+ ))}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
+ useTableOperate(data, 'userId', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteUser(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(userId: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteUser([userId]);
+ if (error) return;
+ onDeleted();
+}
+
+async function edit(userId: CommonType.IdType) {
+ handleEdit(userId);
+}
+
+async function handleResetPwd(userId: CommonType.IdType) {
+ const findItem = data.value.find(item => item.userId === userId) || null;
+ editingData.value = jsonClone(findItem);
+ openPasswordDrawer();
+}
+
+const { loading: treeLoading, startLoading: startTreeLoading, endLoading: endTreeLoading } = useLoading();
+const deptPattern = ref<string>();
+const deptData = ref<Api.Common.CommonTreeRecord>([]);
+const selectedKeys = ref<string[]>([]);
+
+async function getTreeData() {
+ startTreeLoading();
+ const { data: tree, error } = await fetchGetDeptTree();
+ if (!error) {
+ deptData.value = tree;
+ }
+ endTreeLoading();
+}
+
+getTreeData();
+
+function handleClickTree(keys: string[]) {
+ searchParams.value.deptId = keys.length ? keys[0] : null;
+ checkedRowKeys.value = [];
+ getDataByPage();
+}
+
+function handleResetTreeData() {
+ deptPattern.value = undefined;
+ getTreeData();
+}
+
+function handleImport() {
+ openImportModal();
+}
+
+/** 澶勭悊鐘舵�佸垏鎹� */
+async function handleStatusChange(
+ row: Api.System.User,
+ value: Api.Common.EnableStatus,
+ callback: (flag: boolean) => void
+) {
+ const { error } = await fetchUpdateUserStatus({
+ userId: row.userId,
+ status: value
+ });
+
+ callback(!error);
+
+ if (!error) {
+ window.$message?.success($t('page.system.user.statusChangeSuccess'));
+ getData();
+ }
+}
+
+function handleExport() {
+ download('/system/user/export', searchParams.value, `${$t('page.system.user.title')}_${new Date().getTime()}.xlsx`);
+}
+
+const expandedKeys = ref<CommonType.IdType[]>([100]);
+
+const selectable = computed(() => {
+ return !loading.value;
+});
+
+function handleResetSearch() {
+ selectedKeys.value = [];
+ getDataByPage();
+}
+</script>
+
+<template>
+ <TableSiderLayout :sider-title="$t('page.system.dept.title')">
+ <template #header-extra>
+ <NButton size="small" text class="h-18px" @click.stop="() => handleResetTreeData()">
+ <template #icon>
+ <SvgIcon icon="ic:round-refresh" />
+ </template>
+ </NButton>
+ </template>
+ <template #sider>
+ <NInput v-model:value="deptPattern" clearable :placeholder="$t('common.keywordSearch')" />
+ <NSpin class="dept-tree" :show="treeLoading">
+ <NTree
+ v-model:expanded-keys="expandedKeys"
+ v-model:selected-keys="selectedKeys"
+ block-node
+ show-line
+ :data="deptData as []"
+ :show-irrelevant-nodes="false"
+ :pattern="deptPattern"
+ class="infinite-scroll h-full min-h-200px py-3"
+ key-field="id"
+ label-field="label"
+ virtual-scroll
+ :selectable="selectable"
+ @update:selected-keys="handleClickTree"
+ >
+ <template #empty>
+ <NEmpty :description="$t('page.system.dept.empty')" class="h-full min-h-200px justify-center" />
+ </template>
+ </NTree>
+ </NSpin>
+ </template>
+ <div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto">
+ <UserSearch v-model:model="searchParams" @reset="handleResetSearch" @search="getDataByPage" />
+ <TableRowCheckAlert v-model:checked-row-keys="checkedRowKeys" />
+ <NCard :title="$t('page.system.user.title')" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="hasAuth('system:user:add')"
+ :show-delete="hasAuth('system:user:remove')"
+ :show-export="hasAuth('system:user:export')"
+ @add="handleAdd"
+ @delete="handleBatchDelete"
+ @export="handleExport"
+ @refresh="getData"
+ >
+ <template #after>
+ <NButton v-if="hasAuth('system:user:import')" size="small" ghost @click="handleImport">
+ <template #icon>
+ <icon-material-symbols-upload-rounded class="text-icon" />
+ </template>
+ {{ $t('common.import') }}
+ </NButton>
+ </template>
+ </TableHeaderOperation>
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.userId"
+ :pagination="mobilePagination"
+ class="h-full"
+ />
+ <UserImportModal v-model:visible="importVisible" @submitted="getData" />
+ <UserOperateDrawer
+ v-model:visible="drawerVisible"
+ :operate-type="operateType"
+ :row-data="editingData"
+ :dept-data="deptData"
+ :dept-id="searchParams.deptId"
+ @submitted="getDataByPage"
+ />
+ <UserPasswordDrawer v-model:visible="passwordVisible" :row-data="editingData" />
+ </NCard>
+ </div>
+ </TableSiderLayout>
+</template>
+
+<style scoped lang="scss">
+.dept-tree {
+ .n-button {
+ --n-padding: 8px !important;
+ }
+
+ :deep(.n-tree__empty) {
+ height: 100%;
+ justify-content: center;
+ }
+
+ :deep(.n-spin-content) {
+ height: 100%;
+ }
+
+ :deep(.infinite-scroll) {
+ height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
+ max-height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
+ }
+
+ @media screen and (max-width: 1024px) {
+ :deep(.infinite-scroll) {
+ height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
+ max-height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
+ }
+ }
+
+ :deep(.n-tree-node) {
+ height: 30px;
+ }
+
+ :deep(.n-tree-node-switcher) {
+ height: 30px;
+ }
+
+ :deep(.n-tree-node-switcher__icon) {
+ font-size: 16px !important;
+ height: 16px !important;
+ width: 16px !important;
+ }
+}
+
+:deep(.n-data-table-wrapper),
+:deep(.n-data-table-base-table),
+:deep(.n-data-table-base-table-body) {
+ height: 100%;
+}
+
+@media screen and (max-width: 800px) {
+ :deep(.n-data-table-base-table-body) {
+ max-height: calc(100vh - 400px - var(--calc-footer-height, 0px));
+ }
+}
+
+@media screen and (max-width: 802px) {
+ :deep(.n-data-table-base-table-body) {
+ max-height: calc(100vh - 473px - var(--calc-footer-height, 0px));
+ }
+}
+
+:deep(.n-card-header__main) {
+ min-width: 69px !important;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/system/user/modules/user-import-modal.vue b/ruoyi-plus-soybean/src/views/system/user/modules/user-import-modal.vue
new file mode 100755
index 0000000..06050d7
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/user/modules/user-import-modal.vue
@@ -0,0 +1,162 @@
+<script setup lang="ts">
+import { h, ref, watch } from 'vue';
+import type { UploadFileInfo } from 'naive-ui';
+import { getToken } from '@/store/modules/auth/shared';
+import { useDownload } from '@/hooks/business/download';
+import { getServiceBaseURL } from '@/utils/service';
+import type FileUpload from '@/components/custom/file-upload.vue';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'UserImportModal'
+});
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const { download } = useDownload();
+
+const { baseURL } = getServiceBaseURL(import.meta.env);
+
+const headers: Record<string, string> = {
+ Authorization: `Bearer ${getToken()}`,
+ clientid: import.meta.env.VITE_APP_CLIENT_ID!
+};
+
+const emit = defineEmits<Emits>();
+
+const uploadRef = ref<typeof FileUpload>();
+const message = ref<string>('');
+const success = ref<boolean>(false);
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const data = ref<Record<string, any>>({
+ updateSupport: false
+});
+
+const fileList = ref<UploadFileInfo[]>([]);
+
+function closeDrawer() {
+ visible.value = false;
+ if (success.value) {
+ emit('submitted');
+ }
+}
+
+async function handleSubmit() {
+ fileList.value.forEach(item => {
+ item.status = 'pending';
+ });
+ uploadRef.value?.submit();
+}
+
+function isErrorState(xhr: XMLHttpRequest) {
+ const responseText = xhr?.responseText;
+ const response = JSON.parse(responseText);
+ return response.code !== 200;
+}
+
+function handleFinish(options: { file: UploadFileInfo; event?: ProgressEvent }) {
+ const { file, event } = options;
+ // @ts-expect-error Ignore type errors
+ const responseText = event?.target?.responseText;
+ const response = JSON.parse(responseText);
+ message.value = response.msg;
+ window.$message?.success($t('common.importSuccess'));
+ success.value = true;
+ return file;
+}
+
+function handleError(options: { file: UploadFileInfo; event?: ProgressEvent }) {
+ const { event } = options;
+ // @ts-expect-error Ignore type errors
+ const responseText = event?.target?.responseText;
+ const msg = JSON.parse(responseText).msg;
+ message.value = msg;
+ window.$message?.error(() => h('div', { innerHTML: msg || $t('common.importFail') }));
+ success.value = false;
+}
+
+function handleDownloadTemplate() {
+ download(
+ '/system/user/importTemplate',
+ {},
+ `${$t('page.system.user.title')}_${$t('common.importTemplate')}_${new Date().getTime()}.xlsx`
+ );
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ data.value.updateSupport = false;
+ fileList.value = [];
+ success.value = false;
+ message.value = '';
+ }
+});
+</script>
+
+<template>
+ <NModal
+ v-model:show="visible"
+ :title="$t('common.import')"
+ preset="card"
+ :bordered="false"
+ display-directive="show"
+ class="max-w-90% w-600px"
+ @close="closeDrawer"
+ >
+ <NUpload
+ ref="uploadRef"
+ v-model:file-list="fileList"
+ :action="`${baseURL}/system/user/importData`"
+ :headers="headers"
+ :data="data"
+ :max="1"
+ :file-size="50"
+ accept=".xls,.xlsx"
+ :multiple="false"
+ directory-dnd
+ :default-upload="false"
+ list-type="text"
+ :is-error-state="isErrorState"
+ @finish="handleFinish"
+ @error="handleError"
+ >
+ <NUploadDragger>
+ <div class="mb-12px flex-center">
+ <SvgIcon icon="material-symbols:unarchive-outline" class="text-58px color-#d8d8db dark:color-#a1a1a2" />
+ </div>
+ <NText class="text-16px">{{ $t('common.importTip') }}</NText>
+ <NP depth="3" class="mt-8px text-center">
+ {{ $t('common.importSize') }}
+ <b class="text-red-500">50MB</b>
+ {{ $t('common.importFormat') }}
+ <b class="text-red-500">xls/xlsx</b>
+ {{ $t('common.importEnd') }}
+ </NP>
+ </NUploadDragger>
+ </NUpload>
+ <div class="flex-center">
+ <NCheckbox v-model:checked="data.updateSupport">{{ $t('common.updateExisting') }}</NCheckbox>
+ </div>
+
+ <NAlert v-if="message" :title="$t('common.importResult')" :type="success ? 'success' : 'error'" :bordered="false">
+ <NScrollbar class="max-h-200px">
+ <!-- eslint-disable-next-line vue/no-v-html -->
+ <span v-html="message" />
+ </NScrollbar>
+ </NAlert>
+ <template #footer>
+ <NSpace justify="end" :size="16">
+ <NButton @click="handleDownloadTemplate">{{ $t('common.downloadTemplate') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.import') }}</NButton>
+ </NSpace>
+ </template>
+ </NModal>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/user/modules/user-operate-drawer.vue b/ruoyi-plus-soybean/src/views/system/user/modules/user-operate-drawer.vue
new file mode 100755
index 0000000..7700477
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/user/modules/user-operate-drawer.vue
@@ -0,0 +1,248 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useLoading } from '@sa/hooks';
+import { fetchCreateUser, fetchGetUserInfo, fetchUpdateUser } from '@/service/api/system';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'UserOperateDrawer'
+});
+
+interface Props {
+ /** the type of operation */
+ operateType: NaiveUI.TableOperateType;
+ /** the edit row data */
+ rowData?: Api.System.User | null;
+ /** the dept tree data */
+ deptData?: Api.Common.CommonTreeRecord;
+ /** the dept id */
+ deptId?: CommonType.IdType | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { loading, startLoading, endLoading } = useLoading();
+const { loading: deptLoading, startLoading: startDeptLoading, endLoading: endDeptLoading } = useLoading();
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { createRequiredRule, patternRules } = useFormRules();
+
+const title = computed(() => {
+ const titles: Record<NaiveUI.TableOperateType, string> = {
+ add: $t('page.system.user.addUser'),
+ edit: $t('page.system.user.editUser')
+ };
+ return titles[props.operateType];
+});
+
+type Model = Api.System.UserOperateParams;
+
+const model = ref<Model>(createDefaultModel());
+
+const roleOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
+
+function createDefaultModel(): Model {
+ return {
+ deptId: null,
+ userName: '',
+ nickName: '',
+ email: '',
+ phonenumber: '',
+ sex: '0',
+ password: '',
+ status: '0',
+ roleIds: [],
+ postIds: [],
+ remark: ''
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'userName' | 'nickName' | 'password' | 'status' | 'phonenumber' | 'roleIds'>;
+
+const rules: Record<RuleKey, App.Global.FormRule[]> = {
+ userName: [createRequiredRule($t('page.system.user.form.userName.required'))],
+ nickName: [createRequiredRule($t('page.system.user.form.nickName.required'))],
+ password: [{ ...patternRules.pwd, required: props.operateType === 'add' }],
+ phonenumber: [patternRules.phone],
+ status: [createRequiredRule($t('page.system.user.form.status.required'))],
+ roleIds: [{ ...createRequiredRule('璇烽�夋嫨瑙掕壊'), type: 'array' }]
+};
+
+async function getUserInfo(id: CommonType.IdType = '') {
+ startLoading();
+ const { error, data } = await fetchGetUserInfo(id);
+ if (!error) {
+ model.value.roleIds = data.roleIds;
+ model.value.postIds = data.postIds;
+ roleOptions.value = data.roles.map(role => ({
+ label: role.roleName,
+ value: role.roleId
+ }));
+ }
+ endLoading();
+}
+
+function handleUpdateModelWhenEdit() {
+ model.value = createDefaultModel();
+
+ if (props.operateType === 'add') {
+ getUserInfo();
+ model.value.deptId = props.deptId;
+ return;
+ }
+
+ if (props.operateType === 'edit' && props.rowData) {
+ startDeptLoading();
+ Object.assign(model.value, jsonClone(props.rowData));
+ model.value.password = '';
+ getUserInfo(props.rowData.userId);
+ endDeptLoading();
+ }
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { userId, deptId, userName, nickName, email, phonenumber, sex, password, status, roleIds, postIds, remark } =
+ model.value;
+
+ // request
+ if (props.operateType === 'add') {
+ const { error } = await fetchCreateUser({
+ deptId,
+ userName,
+ password,
+ nickName,
+ email,
+ phonenumber,
+ sex,
+ status,
+ roleIds,
+ postIds,
+ remark
+ });
+ if (error) return;
+ }
+
+ if (props.operateType === 'edit') {
+ const { error } = await fetchUpdateUser({
+ userId,
+ deptId,
+ userName,
+ nickName,
+ email,
+ phonenumber,
+ sex,
+ status,
+ roleIds,
+ postIds,
+ remark
+ });
+ if (error) return;
+ }
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent :title="title" :native-scrollbar="false" closable>
+ <NSpin :show="loading">
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem :label="$t('page.system.user.nickName')" path="nickName">
+ <NInput v-model:value="model.nickName" :placeholder="$t('page.system.user.form.nickName.required')" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.user.deptName')" path="deptId">
+ <NTreeSelect
+ v-model:value="model.deptId"
+ :loading="deptLoading"
+ clearable
+ :options="deptData as []"
+ label-field="label"
+ key-field="id"
+ :default-expanded-keys="deptData?.length ? [deptData[0].id] : []"
+ :placeholder="$t('page.system.user.form.deptId.required')"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.user.phonenumber')" path="phonenumber">
+ <NInput v-model:value="model.phonenumber" :placeholder="$t('page.system.user.form.phonenumber.required')" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.user.email')" path="email">
+ <NInput v-model:value="model.email" :placeholder="$t('page.system.user.form.email.required')" />
+ </NFormItem>
+ <NFormItem v-if="operateType === 'add'" :label="$t('page.system.user.userName')" path="userName">
+ <NInput v-model:value="model.userName" :placeholder="$t('page.system.user.form.userName.required')" />
+ </NFormItem>
+ <NFormItem v-if="operateType === 'add'" :label="$t('page.system.user.password')" path="password">
+ <NInput
+ v-model:value="model.password"
+ type="password"
+ show-password-on="click"
+ :input-props="{ autocomplete: 'off' }"
+ :placeholder="$t('page.system.user.form.password.required')"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.user.sex')" path="sex">
+ <DictRadio
+ v-model:value="model.sex"
+ dict-code="sys_user_sex"
+ :placeholder="$t('page.system.user.form.sex.required')"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.user.postIds')" path="postIds">
+ <PostSelect v-model:value="model.postIds" :dept-id="model.deptId" multiple clearable />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.user.roleIds')" path="roleIds">
+ <NSelect
+ v-model:value="model.roleIds"
+ :loading="loading"
+ :options="roleOptions"
+ multiple
+ clearable
+ placeholder="璇烽�夋嫨瑙掕壊"
+ />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.user.status')" path="status">
+ <DictRadio v-model:value="model.status" dict-code="sys_normal_disable" />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.user.remark')" path="remark">
+ <NInput v-model:value="model.remark" :placeholder="$t('page.system.user.form.remark.required')" />
+ </NFormItem>
+ </NForm>
+ </NSpin>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.save') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/user/modules/user-password-drawer.vue b/ruoyi-plus-soybean/src/views/system/user/modules/user-password-drawer.vue
new file mode 100755
index 0000000..ae48465
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/user/modules/user-password-drawer.vue
@@ -0,0 +1,115 @@
+<script setup lang="ts">
+import { reactive, watch } from 'vue';
+import { fetchResetUserPassword } from '@/service/api/system';
+import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'UserPasswordDrawer'
+});
+
+interface Props {
+ /** the edit row data */
+ rowData?: Api.System.User | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const { patternRules } = useFormRules();
+
+type Model = Api.System.UserOperateParams & { deptName: string };
+
+const model: Model = reactive(createDefaultModel());
+
+function createDefaultModel(): Model {
+ return {
+ deptId: null,
+ userName: '',
+ nickName: '',
+ deptName: '',
+ password: ''
+ };
+}
+
+type RuleKey = Extract<keyof Model, 'password'>;
+
+const rules: Record<RuleKey, App.Global.FormRule[]> = {
+ password: [{ ...patternRules.pwd }]
+};
+
+function handleUpdateModelWhenEdit() {
+ Object.assign(model, props.rowData);
+ model.password = '';
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ await validate();
+
+ const { userId, password } = model;
+
+ // request
+ const { error } = await fetchResetUserPassword(userId!, password!);
+ if (error) return;
+
+ window.$message?.success($t('common.updateSuccess'));
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ handleUpdateModelWhenEdit();
+ restoreValidation();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent title="閲嶇疆瀵嗙爜" :native-scrollbar="false" closable>
+ <NForm ref="formRef" :model="model" :rules="rules">
+ <NFormItem :label="$t('page.system.user.nickName')" path="nickName">
+ <NInput v-model:value="model.nickName" disabled />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.user.deptName')" path="deptName">
+ <NInput v-model:value="model.deptName" disabled />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.user.userName')" path="userName">
+ <NInput v-model:value="model.userName" disabled />
+ </NFormItem>
+ <NFormItem :label="$t('page.system.user.password')" path="password">
+ <NInput
+ v-model:value="model.password"
+ type="password"
+ show-password-on="click"
+ :input-props="{ autocomplete: 'off' }"
+ :placeholder="$t('page.system.user.form.password.required')"
+ />
+ </NFormItem>
+ </NForm>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.save') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/system/user/modules/user-search.vue b/ruoyi-plus-soybean/src/views/system/user/modules/user-search.vue
new file mode 100755
index 0000000..874dd21
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/system/user/modules/user-search.vue
@@ -0,0 +1,122 @@
+<script setup lang="ts">
+import { ref, toRaw } from 'vue';
+import { NDatePicker } from 'naive-ui';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'UserSearch'
+});
+
+interface Emits {
+ (e: 'reset'): void;
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const dateRangeCreateTime = ref<[string, string] | null>(null);
+
+const model = defineModel<Api.System.UserSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function onDateRangeCreateTimeUpdate(value: [string, string] | null) {
+ const params = model.value.params!;
+ if (value && value.length === 2) {
+ [params.beginTime, params.endTime] = value;
+ } else {
+ params.beginTime = undefined;
+ params.endTime = undefined;
+ }
+}
+
+function resetModel() {
+ dateRangeCreateTime.value = null;
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('reset');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="table-search card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:6" :label="$t('page.system.user.userName')" path="userName" class="pr-24px">
+ <NInput v-model:value="model.userName" :placeholder="$t('page.system.user.form.userName.required')" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" :label="$t('page.system.user.nickName')" path="nickName" class="pr-24px">
+ <NInput v-model:value="model.nickName" :placeholder="$t('page.system.user.form.nickName.required')" />
+ </NFormItemGi>
+ <NFormItemGi
+ span="24 s:12 m:6"
+ :label="$t('page.system.user.phonenumber')"
+ path="phonenumber"
+ class="pr-24px"
+ >
+ <NInput
+ v-model:value="model.phonenumber"
+ :placeholder="$t('page.system.user.form.phonenumber.required')"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" :label="$t('page.system.user.status')" path="status" class="pr-24px">
+ <DictSelect
+ v-model:value="model.status"
+ :placeholder="$t('page.system.user.form.status.required')"
+ dict-code="sys_normal_disable"
+ clearable
+ />
+ </NFormItemGi>
+ <NFormItemGi
+ span="24 s:12 m:12"
+ :label="$t('page.system.user.createTime')"
+ path="createTime"
+ class="pr-24px"
+ >
+ <NDatePicker
+ v-model:formatted-value="dateRangeCreateTime"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ @update:formatted-value="onDateRangeCreateTimeUpdate"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:12" class="pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/tool/gen/index.vue b/ruoyi-plus-soybean/src/views/tool/gen/index.vue
new file mode 100755
index 0000000..ba22b06
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/tool/gen/index.vue
@@ -0,0 +1,317 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NButton, NDivider } from 'naive-ui';
+import { useBoolean } from '@sa/hooks';
+import { jsonClone } from '@sa/utils';
+import {
+ fetchBatchDeleteGenTable,
+ fetchGenCode,
+ fetchGetGenDataNames,
+ fetchGetGenTableList,
+ fetchSynchGenDbList
+} from '@/service/api/tool';
+import { useAppStore } from '@/store/modules/app';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { useDownload } from '@/hooks/business/download';
+import { useAuth } from '@/hooks/business/auth';
+import { $t } from '@/locales';
+import ButtonIcon from '@/components/custom/button-icon.vue';
+import GenTableSearch from './modules/gen-table-search.vue';
+import GenTableImportDrawer from './modules/gen-table-import-drawer.vue';
+import GenTableOperateDrawer from './modules/gen-table-operate-drawer.vue';
+import GenTablePreviewDrawer from './modules/gen-table-preview-drawer.vue';
+
+const { hasAuth } = useAuth();
+const appStore = useAppStore();
+const { zip } = useDownload();
+const { bool: importVisible, setTrue: openImportVisible } = useBoolean();
+const { bool: previewVisible, setTrue: openPreviewVisible } = useBoolean();
+
+const searchParams = ref<Api.Tool.GenTableSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ dataName: null,
+ tableName: null,
+ tableComment: null,
+ params: {}
+});
+
+const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
+ useNaivePaginatedTable({
+ api: () => fetchGetGenTableList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 48
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64,
+ render: (_, index) => index + 1
+ },
+ {
+ key: 'dataName',
+ title: '鏁版嵁婧�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tableName',
+ title: '琛ㄥ悕绉�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tableComment',
+ title: '琛ㄦ弿杩�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'className',
+ title: '瀹炰綋',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ align: 'center',
+ minWidth: 150
+ },
+ {
+ key: 'updateTime',
+ title: '鏇存柊鏃堕棿',
+ align: 'center',
+ minWidth: 150
+ },
+ {
+ key: 'operate',
+ title: $t('common.operate'),
+ align: 'center',
+ width: 300,
+ render: row => {
+ const previewBtn = () => {
+ return (
+ <ButtonIcon
+ type="primary"
+ text
+ icon="material-symbols:visibility-outline"
+ tooltipContent="棰勮"
+ onClick={() => handlePreview(row.tableId!)}
+ />
+ );
+ };
+
+ const editBtn = () => {
+ return (
+ <ButtonIcon
+ type="primary"
+ text
+ icon="material-symbols:drive-file-rename-outline-outline"
+ tooltipContent={$t('common.edit')}
+ onClick={() => edit(row.tableId!)}
+ />
+ );
+ };
+
+ const refreshBtn = () => {
+ return (
+ <ButtonIcon
+ type="primary"
+ text
+ icon="material-symbols:sync-outline"
+ tooltipContent="鍚屾"
+ onClick={() => refresh(row.tableId!)}
+ />
+ );
+ };
+
+ const genCodeBtn = () => {
+ return (
+ <ButtonIcon
+ type="primary"
+ text
+ icon="material-symbols:download-rounded"
+ tooltipContent="鐢熸垚浠g爜"
+ onClick={() => handleGenCode(row)}
+ />
+ );
+ };
+
+ const deleteBtn = () => {
+ return (
+ <ButtonIcon
+ type="error"
+ text
+ icon="material-symbols:delete-outline"
+ tooltipContent={$t('common.delete')}
+ popconfirmContent={$t('common.confirmDelete')}
+ onPositiveClick={() => handleDelete(row.tableId!)}
+ />
+ );
+ };
+
+ const buttons = [];
+ if (hasAuth('tool:gen:preview')) buttons.push(previewBtn());
+ if (hasAuth('tool:gen:edit')) buttons.push(editBtn());
+ if (hasAuth('tool:gen:refresh')) buttons.push(refreshBtn());
+ if (hasAuth('tool:gen:genCode')) buttons.push(genCodeBtn());
+ if (hasAuth('tool:gen:delete')) buttons.push(deleteBtn());
+
+ return (
+ <div class="flex-center gap-8px">
+ {buttons.map((btn, index) => (
+ <>
+ {index !== 0 && <NDivider vertical />}
+ {btn}
+ </>
+ ))}
+ </div>
+ );
+ }
+ }
+ ]
+ });
+
+const {
+ drawerVisible,
+ editingData,
+ handleEdit,
+ checkedRowKeys,
+ onBatchDeleted,
+ onDeleted
+ // closeDrawer
+} = useTableOperate(data, 'tableId', getData);
+
+async function handleBatchDelete() {
+ // request
+ const { error } = await fetchBatchDeleteGenTable(checkedRowKeys.value);
+ if (error) return;
+ onBatchDeleted();
+}
+
+async function handleDelete(id: CommonType.IdType) {
+ // request
+ const { error } = await fetchBatchDeleteGenTable([id]);
+ if (error) return;
+ onDeleted();
+}
+
+function edit(id: CommonType.IdType) {
+ handleEdit(id);
+}
+
+async function refresh(id: CommonType.IdType) {
+ // request
+ const { error } = await fetchSynchGenDbList(id);
+ if (error) return;
+ window.$message?.success('鍚屾鎴愬姛');
+}
+
+function handleImport() {
+ openImportVisible();
+}
+
+function handlePreview(id: CommonType.IdType) {
+ const findItem = data.value.find(item => item.tableId === id) || null;
+ editingData.value = jsonClone(findItem);
+ openPreviewVisible();
+}
+
+async function handleGenCode(row?: Api.Tool.GenTable) {
+ const tableIds = row?.tableId || checkedRowKeys.value.join(',');
+ if (!tableIds || tableIds === '') {
+ window.$message?.error('璇烽�夋嫨瑕佺敓鎴愮殑鏁版嵁');
+ return;
+ }
+ // request
+ if (row?.genType === '1') {
+ const { error } = await fetchGenCode(row.tableId!);
+ if (error) return;
+ window.$message?.success('鐢熸垚鎴愬姛');
+ } else {
+ zip(`/tool/gen/batchGenCode?tableIdStr=${tableIds}`, `RuoYi-${row?.tableId ? `${row.className}` : Date.now()}.zip`);
+ }
+}
+
+const dataNameOptions = ref<CommonType.Option[]>([]);
+
+async function getDataNames() {
+ const { error, data: dataNames } = await fetchGetGenDataNames();
+ if (error) return;
+ dataNameOptions.value = dataNames.map(item => ({ label: item, value: item }));
+}
+
+getDataNames();
+</script>
+
+<template>
+ <div class="min-h-500px flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto">
+ <GenTableSearch v-model:model="searchParams" :options="dataNameOptions" @search="getDataByPage" />
+ <TableRowCheckAlert v-model:checked-row-keys="checkedRowKeys" />
+ <NCard title="浠g爜鐢熸垚" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+ <template #header-extra>
+ <TableHeaderOperation
+ v-model:columns="columnChecks"
+ :disabled-delete="checkedRowKeys.length === 0"
+ :loading="loading"
+ :show-add="false"
+ @delete="handleBatchDelete"
+ @refresh="getData"
+ >
+ <template #prefix>
+ <NButton
+ :disabled="checkedRowKeys.length === 0"
+ size="small"
+ ghost
+ type="primary"
+ @click="() => handleGenCode()"
+ >
+ <template #icon>
+ <icon-material-symbols-download-rounded class="text-icon" />
+ </template>
+ 鐢熸垚浠g爜
+ </NButton>
+ <NButton size="small" ghost type="primary" @click="handleImport">
+ <template #icon>
+ <icon-material-symbols-upload-rounded class="text-icon" />
+ </template>
+ 瀵煎叆
+ </NButton>
+ </template>
+ </TableHeaderOperation>
+ </template>
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.tableId"
+ :pagination="mobilePagination"
+ class="sm:h-full"
+ />
+ <GenTableImportDrawer v-model:visible="importVisible" :options="dataNameOptions" @submitted="getData" />
+ <GenTableOperateDrawer v-model:visible="drawerVisible" :row-data="editingData" @submitted="getData" />
+ <GenTablePreviewDrawer
+ v-model:visible="previewVisible"
+ :row-data="editingData"
+ @submitted="() => handleGenCode(editingData!)"
+ />
+ </NCard>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-db-search.vue b/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-db-search.vue
new file mode 100755
index 0000000..0ad37f1
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-db-search.vue
@@ -0,0 +1,76 @@
+<script setup lang="ts">
+import { toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'GenTableDbSearch'
+});
+
+interface Props {
+ options: CommonType.Option[];
+}
+
+defineProps<Props>();
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Tool.GenTableDbSearchParams>('model', { required: true });
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12" label="鏁版嵁婧�" label-width="auto" path="dataName" class="pr-24px">
+ <NSelect v-model:value="model.dataName" :options="options" placeholder="璇烽�夋嫨鏁版嵁婧�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12" label="琛ㄥ悕绉�" label-width="auto" path="tableName" class="pr-24px">
+ <NInput v-model:value="model.tableName" placeholder="璇疯緭鍏ヨ〃鍚嶇О" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12" label="琛ㄦ弿杩�" label-width="auto" path="tableComment" class="pr-24px">
+ <NInput v-model:value="model.tableComment" placeholder="璇疯緭鍏ヨ〃鎻忚堪" />
+ </NFormItemGi>
+ <NFormItemGi :show-feedback="false" span="24 s:12" class="pb-6px pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-import-drawer.vue b/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-import-drawer.vue
new file mode 100755
index 0000000..794ed50
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-import-drawer.vue
@@ -0,0 +1,148 @@
+<script setup lang="tsx">
+import { ref, watch } from 'vue';
+import { fetchGetGenDataNames, fetchGetGenDbList, fetchImportGenTable } from '@/service/api/tool';
+import { useAppStore } from '@/store/modules/app';
+import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import { $t } from '@/locales';
+import GenTableDbSearch from './gen-table-db-search.vue';
+
+defineOptions({
+ name: 'GenTableImportDrawer'
+});
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const appStore = useAppStore();
+
+const searchParams = ref<Api.Tool.GenTableDbSearchParams>({
+ pageNum: 1,
+ pageSize: 10,
+ dataName: null,
+ tableName: null,
+ tableComment: null
+});
+
+const { columns, data, getData, getDataByPage, loading, mobilePagination, scrollX } = useNaivePaginatedTable({
+ api: () => fetchGetGenDbList(searchParams.value),
+ transform: response => defaultTransform(response),
+ onPaginationParamsChange: params => {
+ searchParams.value.pageNum = params.page;
+ searchParams.value.pageSize = params.pageSize;
+ },
+ columns: () => [
+ {
+ type: 'selection',
+ align: 'center',
+ width: 52
+ },
+ {
+ key: 'index',
+ title: $t('common.index'),
+ align: 'center',
+ width: 64
+ },
+ {
+ key: 'tableName',
+ title: '琛ㄥ悕绉�',
+ align: 'center',
+ minWidth: 120
+ },
+ {
+ key: 'tableComment',
+ title: '琛ㄦ弿杩�',
+ align: 'center',
+ minWidth: 120
+ }
+ ]
+});
+
+const { checkedRowKeys } = useTableOperate(data, 'tableName', getData);
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ if (checkedRowKeys.value.length > 0) {
+ // request
+ const { error } = await fetchImportGenTable(checkedRowKeys.value as string[], searchParams.value.dataName!);
+ if (error) return;
+ window.$message?.success('瀵煎叆鎴愬姛');
+ emit('submitted');
+ }
+ closeDrawer();
+}
+
+const dataNameOptions = ref<CommonType.Option[]>([]);
+
+async function handleResetSearchParams() {
+ searchParams.value.dataName = dataNameOptions.value.length ? dataNameOptions.value[0].value : null;
+ searchParams.value.tableName = null;
+ searchParams.value.tableComment = null;
+ data.value = [];
+ checkedRowKeys.value = [];
+ await getDataByPage();
+}
+
+async function getDataNames() {
+ const { error, data: dataNames } = await fetchGetGenDataNames();
+ if (error) return;
+ dataNameOptions.value = dataNames.map(item => ({ label: item, value: item }));
+}
+
+watch(visible, async () => {
+ if (visible.value) {
+ await getDataNames();
+ await handleResetSearchParams();
+ }
+});
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" display-directive="show" :width="800" class="max-w-90%">
+ <NDrawerContent title="瀵煎叆琛�" :native-scrollbar="false" closable>
+ <div class="h-full flex-col">
+ <GenTableDbSearch
+ v-model:model="searchParams"
+ :options="dataNameOptions"
+ @reset="handleResetSearchParams"
+ @search="getDataByPage"
+ />
+ <TableRowCheckAlert v-model:checked-row-keys="checkedRowKeys" class="mb-16px" />
+ <NDataTable
+ v-model:checked-row-keys="checkedRowKeys"
+ :columns="columns"
+ :data="data"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="scrollX"
+ :loading="loading"
+ remote
+ :row-key="row => row.tableName"
+ :pagination="mobilePagination"
+ class="flex-1"
+ />
+ </div>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped>
+:deep(.n-drawer-body-content-wrapper) {
+ height: 100%;
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-operate-drawer.vue b/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-operate-drawer.vue
new file mode 100755
index 0000000..2f8171c
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-operate-drawer.vue
@@ -0,0 +1,507 @@
+<script setup lang="tsx">
+import { ref, watch } from 'vue';
+import type { FormInst, SelectOption } from 'naive-ui';
+import { NCheckbox, NInput, NSelect, NTabs } from 'naive-ui';
+import { useLoading } from '@sa/hooks';
+import { jsonClone } from '@sa/utils';
+import {
+ genHtmlTypeOptions,
+ genJavaTypeOptions,
+ genQueryTypeOptions,
+ genTplCategoryOptions,
+ genTypeOptions
+} from '@/constants/business';
+import { fetchGetDictTypeOption } from '@/service/api/system';
+import { fetchGetGenTableInfo, fetchUpdateGenTable } from '@/service/api/tool';
+import { useAppStore } from '@/store/modules/app';
+import { useFormRules } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'GenTableOperateDrawer'
+});
+
+interface Props {
+ /** the edit row data */
+ rowData?: Api.Tool.GenTable | null;
+}
+
+const props = defineProps<Props>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const appStore = useAppStore();
+const { defaultRequiredRule } = useFormRules();
+const { loading, startLoading, endLoading } = useLoading();
+const genTableInfo = ref<Api.Tool.GenTableInfo>();
+
+const tab = ref<'basic' | 'dragTable' | 'genInfo'>('dragTable');
+const basicFormRef = ref<FormInst | null>(null);
+type BasicRuleKey = Extract<keyof Api.Tool.GenTable, 'tableName' | 'tableComment' | 'className' | 'functionAuthor'>;
+
+const basicRules: Record<BasicRuleKey, App.Global.FormRule> = {
+ tableName: defaultRequiredRule,
+ tableComment: defaultRequiredRule,
+ className: defaultRequiredRule,
+ functionAuthor: defaultRequiredRule
+};
+
+const infoFormRef = ref<FormInst | null>(null);
+type InfoRuleKey = Extract<
+ keyof Api.Tool.GenTable,
+ | 'tplCategory'
+ | 'packageName'
+ | 'moduleName'
+ | 'businessName'
+ | 'functionName'
+ | 'parentMenuId'
+ | 'genType'
+ | 'genPath'
+ | 'treeCode'
+ | 'treeParentCode'
+ | 'treeName'
+>;
+
+const infoRules: Record<InfoRuleKey, App.Global.FormRule> = {
+ tplCategory: defaultRequiredRule,
+ packageName: defaultRequiredRule,
+ moduleName: defaultRequiredRule,
+ businessName: defaultRequiredRule,
+ functionName: defaultRequiredRule,
+ parentMenuId: defaultRequiredRule,
+ genType: defaultRequiredRule,
+ genPath: defaultRequiredRule,
+ treeCode: defaultRequiredRule,
+ treeParentCode: defaultRequiredRule,
+ treeName: defaultRequiredRule
+};
+
+async function getGenTableInfo() {
+ if (!props.rowData?.tableId) return;
+ startLoading();
+ // request
+ const { error, data } = await fetchGetGenTableInfo(props.rowData.tableId);
+ if (error) return;
+ genTableInfo.value = data;
+ endLoading();
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ try {
+ await basicFormRef.value?.validate();
+ } catch {
+ tab.value = 'basic';
+ window.$message?.error('琛ㄥ崟鏍¢獙鏈�氳繃锛岃閲嶆柊妫�鏌ユ彁浜ゅ唴瀹�');
+ return;
+ }
+
+ try {
+ await infoFormRef.value?.validate();
+ } catch {
+ tab.value = 'genInfo';
+ window.$message?.error('琛ㄥ崟鏍¢獙鏈�氳繃锛岃閲嶆柊妫�鏌ユ彁浜ゅ唴瀹�');
+ return;
+ }
+
+ const info = genTableInfo.value!.info;
+ const genTable: Api.Tool.GenTable = jsonClone(info);
+ genTable.params = {
+ treeCode: info?.treeCode,
+ treeName: info?.treeName,
+ treeParentCode: info?.treeParentCode,
+ parentMenuId: info?.parentMenuId
+ };
+ genTable.columns = genTableInfo.value?.rows;
+
+ // request
+ const { error } = await fetchUpdateGenTable(genTable);
+ if (error) return;
+ window.$message?.success('淇敼鎴愬姛');
+
+ closeDrawer();
+ emit('submitted');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ genTableInfo.value = undefined;
+ tab.value = 'dragTable';
+ getDictOptions();
+ getGenTableInfo();
+ }
+});
+
+const dictOptions = ref<SelectOption[]>([]);
+const { loading: dictLoading, startLoading: startDictLoading, endLoading: endDictLoading } = useLoading();
+
+async function getDictOptions() {
+ startDictLoading();
+ const { error, data } = await fetchGetDictTypeOption();
+ if (error) return;
+ dictOptions.value = data.map(dict => ({
+ value: dict.dictType!,
+ class: 'gen-dict-select',
+ label: dict.dictName
+ }));
+ endDictLoading();
+}
+
+const columns: NaiveUI.TableColumn<Api.Tool.GenTableColumn>[] = [
+ {
+ key: 'sort',
+ title: $t('common.index'),
+ align: 'center',
+ width: 80
+ },
+ {
+ key: 'columnName',
+ title: '瀛楁鍒楀悕',
+ align: 'left',
+ minWidth: 120
+ },
+ {
+ key: 'columnComment',
+ title: '瀛楁鎻忚堪',
+ align: 'left',
+ minWidth: 120,
+ render: row => <NInput v-model:value={row.columnComment} placeholder="璇疯緭鍏ュ瓧娈垫弿杩�" />
+ },
+ {
+ key: 'columnType',
+ title: '鐗╃悊绫诲瀷',
+ align: 'left',
+ width: 120
+ },
+ {
+ key: 'javaType',
+ title: 'Java 绫诲瀷',
+ align: 'left',
+ width: 136,
+ render: row => <NSelect v-model:value={row.javaType} placeholder="璇烽�夋嫨 Java 绫诲瀷" options={genJavaTypeOptions} />
+ },
+ {
+ key: 'javaField',
+ title: 'Java 灞炴��',
+ align: 'left',
+ minWidth: 120,
+ render: row => <NInput v-model:value={row.javaField} placeholder="璇疯緭鍏� Java 灞炴��" />
+ },
+ {
+ key: 'isInsert',
+ title: '鎻掑叆',
+ align: 'center',
+ width: 64,
+ render: row => <NCheckbox checked-value="1" unchecked-value="0" v-model:checked={row.isInsert} />
+ },
+ {
+ key: 'isEdit',
+ title: '缂栬緫',
+ align: 'center',
+ width: 64,
+ render: row => <NCheckbox checked-value="1" unchecked-value="0" v-model:checked={row.isEdit} />
+ },
+ {
+ key: 'isList',
+ title: '鍒楄〃',
+ align: 'center',
+ width: 64,
+ render: row => <NCheckbox checked-value="1" unchecked-value="0" v-model:checked={row.isList} />
+ },
+ {
+ key: 'isQuery',
+ title: '鏌ヨ',
+ align: 'center',
+ width: 64,
+ render: row => <NCheckbox checked-value="1" unchecked-value="0" v-model:checked={row.isQuery} />
+ },
+ {
+ key: 'queryType',
+ title: '鏌ヨ鏂瑰紡',
+ align: 'left',
+ width: 130,
+ render: row => <NSelect v-model:value={row.queryType} placeholder="璇烽�夋嫨鏌ヨ鏂瑰紡" options={genQueryTypeOptions} />
+ },
+ {
+ key: 'isRequired',
+ title: '蹇呭~',
+ align: 'center',
+ width: 64,
+ render: row => <NCheckbox checked-value="1" unchecked-value="0" v-model:checked={row.isRequired} />
+ },
+ {
+ key: 'htmlType',
+ title: '鏄剧ず绫诲瀷',
+ align: 'left',
+ width: 130,
+ render: row => <NSelect v-model:value={row.htmlType} placeholder="璇烽�夋嫨鏄剧ず绫诲瀷" options={genHtmlTypeOptions} />
+ },
+ {
+ key: 'dictType',
+ title: '瀛楀吀绫诲瀷',
+ align: 'left',
+ width: 150,
+ render: row => {
+ if (row.dictType === '') {
+ row.dictType = undefined;
+ }
+
+ const renderLabel = (option: CommonType.Option) => (
+ <div class="w-full flex justify-between gap-12px">
+ <span>{option.label}</span>
+ <span class="flex-1 text-end text-13px text-#8492a6">{option.value}</span>
+ </div>
+ );
+
+ const renderTag = ({ option }: { option: CommonType.Option }) => <>{option.label}</>;
+
+ return (
+ <NSelect
+ v-model:value={row.dictType}
+ loading={dictLoading.value}
+ options={dictOptions.value}
+ clear-filter-after-select={false}
+ consistent-menu-width={false}
+ render-label={renderLabel}
+ render-tag={renderTag}
+ clearable
+ />
+ );
+ }
+ }
+];
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" display-directive="show" width="100%">
+ <NDrawerContent title="缂栬緫琛�" :native-scrollbar="false" closable>
+ <NSpin :show="loading" class="h-full" content-class="h-full">
+ <NTabs v-model:value="tab" type="segment" animated class="h-full" pane-class="h-full">
+ <NTabPane name="basic" tab="鍩烘湰淇℃伅" display-directive="show">
+ <NForm
+ v-if="genTableInfo?.info"
+ ref="basicFormRef"
+ class="mx-auto max-w-800px"
+ :model="genTableInfo.info"
+ :rules="basicRules"
+ >
+ <NGrid :x-gap="16" responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12" label="琛ㄥ悕绉�" path="tableName">
+ <NInput v-model:value="genTableInfo.info.tableName" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12" label="琛ㄦ弿杩�" path="tableComment">
+ <NInput v-model:value="genTableInfo.info.tableComment" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12" label="瀹炰綋绫诲悕绉�" path="className">
+ <NInput v-model:value="genTableInfo.info.className" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12" label="浣滆��" path="functionAuthor">
+ <NInput v-model:value="genTableInfo.info.functionAuthor" />
+ </NFormItemGi>
+ <NFormItemGi span="24" label="澶囨敞" path="remark">
+ <NInput v-model:value="genTableInfo.info.remark" type="textarea" />
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NTabPane>
+ <NTabPane name="dragTable" tab="瀛楁淇℃伅" display-directive="show">
+ <div class="h-full flex-col">
+ <NDataTable
+ :columns="columns"
+ :data="genTableInfo?.rows"
+ size="small"
+ :flex-height="!appStore.isMobile"
+ :scroll-x="1800"
+ remote
+ class="flex-1"
+ />
+ </div>
+ </NTabPane>
+ <NTabPane name="genInfo" tab="鐢熸垚淇℃伅" display-directive="show">
+ <NForm
+ v-if="genTableInfo?.info"
+ ref="infoFormRef"
+ class="mx-auto max-w-800px"
+ :model="genTableInfo.info"
+ :rules="infoRules"
+ >
+ <NGrid :x-gap="16" responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12" label="鐢熸垚妯℃澘" path="tplCategory">
+ <NSelect
+ v-model:value="genTableInfo.info.tplCategory"
+ :options="genTplCategoryOptions"
+ placeholder="璇烽�夋嫨鐢熸垚妯℃澘"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12" path="packageName">
+ <template #label>
+ <div class="flex-center">
+ <FormTip content="鐢熸垚鍦ㄥ摢涓猨ava鍖呬笅锛屼緥濡� com.ruoyi.system" />
+ <span class="pl-3px">鐢熸垚鍖呰矾寰�</span>
+ </div>
+ </template>
+ <NInput v-model:value="genTableInfo.info.packageName" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12" path="moduleName">
+ <template #label>
+ <div class="flex-center">
+ <FormTip content="鍙悊瑙d负瀛愮郴缁熷悕锛屼緥濡� system锛宖low-instance銆傞伩鍏嶉┘宄板懡鍚�" />
+ <span class="pl-3px">鐢熸垚妯″潡鍚�</span>
+ </div>
+ </template>
+ <NInput v-model:value="genTableInfo.info.moduleName" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12" label="鐢熸垚涓氬姟鍚�" path="businessName">
+ <template #label>
+ <div class="flex-center">
+ <FormTip content="鍙悊瑙d负鍔熻兘鑻辨枃鍚嶏紝渚嬪 user" />
+ <span class="pl-3px">鐢熸垚涓氬姟鍚�</span>
+ </div>
+ </template>
+ <NInput v-model:value="genTableInfo.info.businessName" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12" label="鐢熸垚鍔熻兘鍚�" path="functionName">
+ <template #label>
+ <div class="flex-center">
+ <FormTip content="鐢ㄤ綔绫绘弿杩帮紝渚嬪 鐢ㄦ埛" />
+ <span class="pl-3px">鐢熸垚鍔熻兘鍚�</span>
+ </div>
+ </template>
+ <NInput v-model:value="genTableInfo.info.functionName" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12" label="涓婄骇鑿滃崟" path="parentMenuId">
+ <template #label>
+ <div class="flex-center">
+ <FormTip content="鍒嗛厤鍒版寚瀹氳彍鍗曚笅锛屼緥濡� 绯荤粺绠$悊" />
+ <span class="pl-3px">涓婄骇鑿滃崟</span>
+ </div>
+ </template>
+ <MenuTreeSelect v-model:value="genTableInfo.info.parentMenuId" :data-name="rowData?.dataName" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12" label="鐢熸垚浠g爜鏂瑰紡" path="genType">
+ <template #label>
+ <div class="flex-center">
+ <FormTip content="榛樿涓簔ip鍘嬬缉鍖呬笅杞斤紝涔熷彲浠ヨ嚜瀹氫箟鐢熸垚璺緞" />
+ <span class="pl-3px">鐢熸垚浠g爜鏂瑰紡</span>
+ </div>
+ </template>
+ <NRadioGroup v-model:value="genTableInfo.info.genType">
+ <NSpace :span="16">
+ <NRadio
+ v-for="option in genTypeOptions"
+ :key="option.value"
+ :label="option.label"
+ :value="option.value"
+ />
+ </NSpace>
+ </NRadioGroup>
+ </NFormItemGi>
+ <NFormItemGi v-if="genTableInfo.info.genType === '1'" span="24 s:12" label="鑷畾涔夎矾寰�" path="genPath">
+ <NInput v-model:value="genTableInfo.info.genPath" />
+ </NFormItemGi>
+ </NGrid>
+
+ <template v-if="genTableInfo.info.tplCategory === 'tree'">
+ <NDivider>鍏朵粬淇℃伅</NDivider>
+
+ <NGrid :x-gap="16" responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12" path="treeCode">
+ <template #label>
+ <div class="flex-center">
+ <FormTip content="鏍戞樉绀虹殑缂栫爜瀛楁鍚嶏紝 濡傦細dept_id" />
+ <span>鏍戠紪鐮佸瓧娈�</span>
+ </div>
+ </template>
+ <NSelect
+ v-model:value="genTableInfo.info.treeCode"
+ placeholder="璇烽�夋嫨鏍戠紪鐮佸瓧娈�"
+ :options="
+ genTableInfo.rows.map(column => ({
+ value: column.columnName,
+ label: column.columnName + '锛�' + column.columnComment
+ }))
+ "
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12" path="treeParentCode">
+ <template #label>
+ <div class="flex-center">
+ <FormTip content="鏍戞樉绀虹殑鐖剁紪鐮佸瓧娈靛悕锛� 濡傦細parent_Id" />
+ <span>鏍戠埗缂栫爜瀛楁</span>
+ </div>
+ </template>
+ <NSelect
+ v-model:value="genTableInfo.info.treeParentCode"
+ placeholder="璇烽�夋嫨鏍戠埗缂栫爜瀛楁"
+ :options="
+ genTableInfo.rows.map(column => ({
+ value: column.columnName,
+ label: column.columnName + '锛�' + column.columnComment
+ }))
+ "
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12" path="treeName">
+ <template #label>
+ <div class="flex-center">
+ <FormTip content="鏍戣妭鐐圭殑鏄剧ず鍚嶇О瀛楁鍚嶏紝 濡傦細dept_name" />
+ <span>鏍戝悕绉板瓧娈�</span>
+ </div>
+ </template>
+ <NSelect
+ v-model:value="genTableInfo.info.treeName"
+ placeholder="璇烽�夋嫨鏍戝悕绉板瓧娈�"
+ :options="
+ genTableInfo.rows.map(column => ({
+ value: column.columnName,
+ label: column.columnName + '锛�' + column.columnComment
+ }))
+ "
+ />
+ </NFormItemGi>
+ </NGrid>
+ </template>
+ </NForm>
+ </NTabPane>
+ </NTabs>
+ </NSpin>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton :disabled="loading" type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped lang="scss">
+:deep(.n-drawer-body-content-wrapper) {
+ height: 100%;
+}
+
+:deep(.n-tabs-pane-wrapper) {
+ height: 100%;
+}
+</style>
+
+<style>
+.gen-dict-select {
+ width: 100%;
+
+ .n-base-select-option__content {
+ width: 100%;
+ }
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-preview-drawer.vue b/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-preview-drawer.vue
new file mode 100755
index 0000000..f33ecc4
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-preview-drawer.vue
@@ -0,0 +1,240 @@
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+import { useClipboard } from '@vueuse/core';
+import { useLoading } from '@sa/hooks';
+import hljs from 'highlight.js/lib/core';
+import java from 'highlight.js/lib/languages/java';
+import xml from 'highlight.js/lib/languages/xml';
+import sql from 'highlight.js/lib/languages/sql';
+import typescript from 'highlight.js/lib/languages/typescript';
+import html from 'highlight.js/lib/languages/vbscript-html';
+import plaintext from 'highlight.js/lib/languages/plaintext';
+import { fetchGetGenPreview } from '@/service/api/tool';
+import { $t } from '@/locales';
+
+hljs.registerLanguage('java', java);
+hljs.registerLanguage('xml', xml);
+hljs.registerLanguage('sql', sql);
+hljs.registerLanguage('typescript', typescript);
+hljs.registerLanguage('html', html);
+hljs.registerLanguage('plaintext', plaintext);
+
+defineOptions({
+ name: 'GenTablePreviewDrawer'
+});
+
+interface Props {
+ /** the edit row data */
+ rowData?: Api.Tool.GenTable | null;
+}
+
+const props = defineProps<Props>();
+
+interface Emits {
+ (e: 'submitted'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const visible = defineModel<boolean>('visible', {
+ default: false
+});
+
+const tab = ref('vm/java/domain.java.vm');
+const previewData = ref<Api.Tool.GenTablePreview>({});
+const { loading, startLoading, endLoading } = useLoading();
+
+async function getGenPreview() {
+ if (!props.rowData?.tableId) return;
+ startLoading();
+ const { data, error } = await fetchGetGenPreview(props.rowData?.tableId);
+ if (error) {
+ endLoading();
+ closeDrawer();
+ return;
+ }
+ previewData.value = data;
+ endLoading();
+}
+
+function closeDrawer() {
+ visible.value = false;
+}
+
+async function handleSubmit() {
+ closeDrawer();
+ emit('submitted');
+}
+
+const { copy, isSupported } = useClipboard();
+
+async function handleCopyCode() {
+ if (!isSupported) {
+ window.$message?.error('鎮ㄧ殑娴忚鍣ㄤ笉鏀寔Clipboard API');
+ return;
+ }
+
+ const code = previewData.value[tab.value];
+
+ if (!previewData.value[tab.value]) {
+ return;
+ }
+
+ await copy(code);
+ window.$message?.success('浠g爜澶嶅埗鎴愬姛');
+}
+
+watch(visible, () => {
+ if (visible.value) {
+ previewData.value = {};
+ tab.value = 'vm/java/domain.java.vm';
+ getGenPreview();
+ }
+});
+
+const genMap: Api.Tool.GenTablePreview = {
+ 'vm/java/domain.java.vm': 'domain.java',
+ 'vm/java/vo.java.vm': 'vo.java',
+ 'vm/java/bo.java.vm': 'bo.java',
+ 'vm/java/mapper.java.vm': 'mapper.java',
+ 'vm/java/service.java.vm': 'service.java',
+ 'vm/java/serviceImpl.java.vm': 'serviceImpl.java',
+ 'vm/java/controller.java.vm': 'controller.java',
+ 'vm/xml/mapper.xml.vm': 'mapper.xml',
+ 'vm/sql/sql.vm': 'sql',
+ 'vm/soy/api/api.ts.vm': 'api.ts',
+ 'vm/soy/typings/api.d.ts.vm': 'type.d.ts',
+ 'vm/soy/index.vue.vm': 'index.vue',
+ 'vm/soy/index-tree.vue.vm': 'index-tree.vue',
+ 'vm/soy/modules/search.vue.vm': 'search.vue',
+ 'vm/soy/modules/operate-drawer.vue.vm': 'operate-drawer.vue'
+};
+
+function getGenLanguage(name: string) {
+ if (name.endsWith('.java')) {
+ return 'java';
+ }
+
+ if (name.endsWith('.xml')) {
+ return 'xml';
+ }
+
+ if (name.endsWith('sql')) {
+ return 'sql';
+ }
+
+ if (name.endsWith('.ts')) {
+ return 'typescript';
+ }
+
+ if (name.endsWith('.vue')) {
+ return 'vue';
+ }
+
+ return 'plaintext';
+}
+
+function splitVueSFC(source: string) {
+ const lineNumbers = source.split('\n').length;
+ const scriptOpenTag = '<script setup';
+ const scriptCloseTag = `<${'/'}script>`;
+
+ // 鎵惧埌 <script setup ...> 鐨勮捣濮嬩綅缃�
+ const startIdx = source.indexOf(scriptOpenTag);
+ if (startIdx === -1) throw new Error('鏈壘鍒� <script setup>');
+
+ // 鎵惧埌 <script ...> 鏍囩鐨勭粨鏉熶綅缃紙鍗崇涓�涓� >锛�
+ const scriptTagEndIdx = source.indexOf('>', startIdx);
+ const part1 = source.slice(startIdx, scriptTagEndIdx + 1);
+
+ // 鎵惧埌 <\/script>
+ const endIdx = source.indexOf(scriptCloseTag);
+ if (endIdx === -1) throw new Error(`鏈壘鍒� <${'/'}script>`);
+
+ // 涓棿鑴氭湰鍐呭
+ const part2 = source.slice(scriptTagEndIdx + 1, endIdx).trim();
+
+ // script 鍚庣殑鍐呭
+ const part3 = source.slice(endIdx).trim();
+
+ return { part1, part2, part3, lineNumbers };
+}
+</script>
+
+<template>
+ <NDrawer v-model:show="visible" display-directive="show" width="100%">
+ <NDrawerContent title="浠g爜棰勮" :native-scrollbar="false" closable>
+ <NSpin :show="loading" class="h-full" content-class="h-full">
+ <div class="flex flex-col">
+ <NTabs
+ v-model:value="tab"
+ type="card"
+ class="sticky top-0 h-full bg-white dark:bg-#2f2e32"
+ pane-class="h-full"
+ >
+ <NTab v-for="(gen, index) in Object.keys(previewData)" :key="index" :name="gen" display-directive="show">
+ {{ genMap[gen] }}
+ </NTab>
+ </NTabs>
+ <div v-if="tab.endsWith('.vue.vm')" class="flex p-16px">
+ <pre class="code-line-numbers">{{
+ Array.from({ length: splitVueSFC(previewData[tab]).lineNumbers }, (_, i) => i + 1).join('\n')
+ }}</pre>
+ <div>
+ <NCode :hljs="hljs" :code="splitVueSFC(previewData[tab]).part1" language="xml" />
+ <NCode :hljs="hljs" :code="splitVueSFC(previewData[tab]).part2" language="typescript" />
+ <NCode :hljs="hljs" :code="splitVueSFC(previewData[tab]).part3" language="xml" />
+ </div>
+ </div>
+ <NCode
+ v-else
+ class="p-16px"
+ :hljs="hljs"
+ :code="previewData[tab]"
+ :language="getGenLanguage(genMap[tab])"
+ show-line-numbers
+ />
+ <div class="position-absolute right-42px top-58px">
+ <NButton text :focusable="false" class="flex-center" @click="handleCopyCode">
+ <template #icon>
+ <icon-ep-copy-document class="text-14px" />
+ </template>
+ <span>澶嶅埗</span>
+ </NButton>
+ </div>
+ </div>
+ </NSpin>
+ <template #footer>
+ <NSpace :size="16">
+ <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
+ <NButton :disabled="loading" type="primary" @click="handleSubmit">鐢熸垚浠g爜</NButton>
+ </NSpace>
+ </template>
+ </NDrawerContent>
+ </NDrawer>
+</template>
+
+<style scoped>
+:deep(.n-drawer-body-content-wrapper) {
+ height: 100%;
+}
+
+:deep(.n-tabs) {
+ width: unset !important;
+}
+
+:deep(.n-tabs.n-tabs--left .n-tabs-bar) {
+ width: 5px !important;
+}
+
+.code-line-numbers {
+ margin: 0;
+ line-height: inherit;
+ font-size: inherit;
+ user-select: none;
+ padding-right: 12px;
+ text-align: right;
+ transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ color: rgb(118, 124, 130);
+}
+</style>
diff --git a/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-search.vue b/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-search.vue
new file mode 100755
index 0000000..c2900a8
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/tool/gen/modules/gen-table-search.vue
@@ -0,0 +1,103 @@
+<script setup lang="ts">
+import { ref, toRaw } from 'vue';
+import { jsonClone } from '@sa/utils';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'GenTableSearch'
+});
+
+interface Props {
+ options: CommonType.Option[];
+}
+
+defineProps<Props>();
+
+interface Emits {
+ (e: 'search'): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+
+const model = defineModel<Api.Tool.GenTableSearchParams>('model', { required: true });
+
+const dateRange = ref<[string, string]>();
+
+function onDateRangeUpdate(value: [string, string] | null) {
+ if (value?.length) {
+ model.value.params!.beginTime = value[0];
+ model.value.params!.endTime = value[1];
+ }
+}
+
+const defaultModel = jsonClone(toRaw(model.value));
+
+function resetModel() {
+ model.value.params!.beginTime = null;
+ model.value.params!.endTime = null;
+ Object.assign(model.value, defaultModel);
+}
+
+async function reset() {
+ await restoreValidation();
+ resetModel();
+ emit('search');
+}
+
+async function search() {
+ await validate();
+ emit('search');
+}
+</script>
+
+<template>
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse>
+ <NCollapseItem :title="$t('common.search')" name="user-search">
+ <NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:6" label="鏁版嵁婧�" label-width="auto" path="dataName" class="pr-24px">
+ <NSelect v-model:value="model.dataName" :options="options" placeholder="璇烽�夋嫨鏁版嵁婧�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="琛ㄥ悕绉�" label-width="auto" path="tableName" class="pr-24px">
+ <NInput v-model:value="model.tableName" placeholder="璇疯緭鍏ヨ〃鍚嶇О" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="琛ㄦ弿杩�" label-width="auto" path="tableComment" class="pr-24px">
+ <NInput v-model:value="model.tableComment" placeholder="璇疯緭鍏ヨ〃鎻忚堪" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:6" label="鍒涘缓鏃堕棿" label-width="auto" class="pr-24px">
+ <NDatePicker
+ v-model:formatted-value="dateRange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ type="daterange"
+ clearable
+ @update:formatted-value="onDateRangeUpdate"
+ />
+ </NFormItemGi>
+ <NFormItemGi :show-feedback="false" span="24" class="pb-6px pr-24px">
+ <NSpace class="w-full" justify="end">
+ <NButton @click="reset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" ghost @click="search">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ {{ $t('common.search') }}
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+</template>
+
+<style scoped></style>
diff --git a/ruoyi-plus-soybean/tsconfig.json b/ruoyi-plus-soybean/tsconfig.json
new file mode 100755
index 0000000..3c34dcb
--- /dev/null
+++ b/ruoyi-plus-soybean/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "jsx": "preserve",
+ "jsxImportSource": "vue",
+ "lib": ["DOM", "ESNext"],
+ "baseUrl": ".",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "paths": {
+ "@/*": ["./src/*"],
+ "~/*": ["./*"]
+ },
+ "resolveJsonModule": true,
+ "types": ["vite/client", "node", "unplugin-icons/types/vue", "naive-ui/volar"],
+ "strict": true,
+ "strictNullChecks": true,
+ "noUnusedLocals": false,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": true
+ },
+ "include": ["./**/*.ts", "./**/*.tsx", "./**/*.vue"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/ruoyi-plus-soybean/uno.config.ts b/ruoyi-plus-soybean/uno.config.ts
new file mode 100755
index 0000000..0259efd
--- /dev/null
+++ b/ruoyi-plus-soybean/uno.config.ts
@@ -0,0 +1,30 @@
+import { defineConfig } from '@unocss/vite';
+import transformerDirectives from '@unocss/transformer-directives';
+import transformerVariantGroup from '@unocss/transformer-variant-group';
+import presetWind3 from '@unocss/preset-wind3';
+import type { Theme } from '@unocss/preset-uno';
+import { presetSoybeanAdmin } from '@sa/uno-preset';
+import { themeVars } from './src/theme/vars';
+
+export default defineConfig<Theme>({
+ content: {
+ pipeline: {
+ exclude: ['node_modules', 'dist']
+ }
+ },
+ theme: {
+ ...themeVars,
+ fontSize: {
+ 'icon-xs': '0.875rem',
+ 'icon-small': '1rem',
+ icon: '1.125rem',
+ 'icon-large': '1.5rem',
+ 'icon-xl': '2rem'
+ }
+ },
+ shortcuts: {
+ 'card-wrapper': 'rd-8px shadow-sm'
+ },
+ transformers: [transformerDirectives(), transformerVariantGroup()],
+ presets: [presetWind3({ dark: 'class' }), presetSoybeanAdmin()]
+});
diff --git a/ruoyi-plus-soybean/vite.config.ts b/ruoyi-plus-soybean/vite.config.ts
new file mode 100755
index 0000000..45c2f97
--- /dev/null
+++ b/ruoyi-plus-soybean/vite.config.ts
@@ -0,0 +1,51 @@
+import process from 'node:process';
+import { URL, fileURLToPath } from 'node:url';
+import { defineConfig, loadEnv } from 'vite';
+import { setupVitePlugins } from './build/plugins';
+import { createViteProxy, getBuildTime } from './build/config';
+
+export default defineConfig(configEnv => {
+ const viteEnv = loadEnv(configEnv.mode, process.cwd()) as unknown as Env.ImportMeta;
+
+ const buildTime = getBuildTime();
+
+ const enableProxy = configEnv.command === 'serve' && !configEnv.isPreview;
+
+ return {
+ base: viteEnv.VITE_BASE_URL,
+ resolve: {
+ alias: {
+ '~': fileURLToPath(new URL('./', import.meta.url)),
+ '@': fileURLToPath(new URL('./src', import.meta.url))
+ }
+ },
+ css: {
+ preprocessorOptions: {
+ scss: {
+ api: 'modern-compiler',
+ additionalData: `@use "@/styles/scss/global.scss" as *;`
+ }
+ }
+ },
+ plugins: setupVitePlugins(viteEnv, buildTime),
+ define: {
+ BUILD_TIME: JSON.stringify(buildTime)
+ },
+ server: {
+ host: '0.0.0.0',
+ port: 9527,
+ open: true,
+ proxy: createViteProxy(viteEnv, enableProxy)
+ },
+ preview: {
+ port: 9725
+ },
+ build: {
+ reportCompressedSize: false,
+ sourcemap: viteEnv.VITE_SOURCE_MAP === 'Y',
+ commonjsOptions: {
+ ignoreTryCatch: false
+ }
+ }
+ };
+});
--
Gitblit v1.9.3