kgdxpr 3 months ago
commit bc8ffedd2c

12
.env

@ -0,0 +1,12 @@
# title
VITE_APP_TITLE='Sz Admin'
# 本地运行端口号
VITE_PORT=9848
# 启动时自动打开浏览器
VITE_OPEN=true
# clientId
VITE_APP_CLIENT_ID ="195da9fcce574852b850068771cde034"
# 请求最大超时时间ms120s
VITE_APP_HTTP_TIMEOUT=120000

@ -0,0 +1,13 @@
# 本地环境
VITE_USER_NODE_ENV=development
# 公共基础路径
VITE_PUBLIC_PATH=/base
# 开发环境接口地址
# VITE_API_URL=/api
# VITE_API_URL=http://10.10.21.20:8001
# VITE_SOCKET_URL=ws://127.0.0.1:9993/socket
VITE_APP_CLIENT_ID ="195da9fcce574852b850068771cde034"
# 是否对admin超管用户放行前端按钮权限验证默认放行
VITE_ADMIN_BYPASS_PERMISSION=true

@ -0,0 +1,13 @@
# 线上环境
VITE_USER_NODE_ENV=production
# 公共基础路径
VITE_PUBLIC_PATH=/
# 线上环境接口地址
VITE_API_URL="/api"
VITE_APP_CLIENT_ID ="195da9fcce574852b850068771cde034"
# 是否对admin超管用户放行前端按钮权限验证默认放行
VITE_ADMIN_BYPASS_PERMISSION=true
# 是否启用websocket如不需要请注释
VITE_SOCKET_URL="/socket"

28
.gitignore vendored

@ -0,0 +1,28 @@
# 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
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,9 @@
/dist/*
.local
/node_modules/**
**/*.svg
**/*.sh
/public/*
stats.html

@ -0,0 +1,41 @@
// @see: https://www.prettier.cn
module.exports = {
// 指定最大换行长度
printWidth: 130,
// 缩进制表符宽度 | 空格数
tabWidth: 2,
// 使用制表符而不是空格缩进行 (true制表符false空格)
useTabs: false,
// 结尾不用分号 (truefalse没有)
semi: true,
// 使用单引号 (true单引号false双引号)
singleQuote: true,
// 在对象字面量中决定是否将属性名用引号括起来 可选值 "<as-needed|consistent|preserve>"
quoteProps: 'as-needed',
// 在JSX中使用单引号而不是双引号 (true单引号false双引号)
jsxSingleQuote: false,
// 多行时尽可能打印尾随逗号 可选值"<none|es5|all>"
trailingComma: 'none',
// 在对象,数组括号与文字之间加空格 "{ foo: bar }" (truefalse没有)
bracketSpacing: true,
// 将 > 多行元素放在最后一行的末尾,而不是单独放在下一行 (true放末尾false单独一行)
bracketSameLine: false,
// (x) => {} 箭头函数参数只有一个时是否要有小括号 (avoid省略括号always不省略括号)
arrowParens: 'avoid',
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 可以在文件顶部插入一个特殊标记,指定该文件已使用 Prettier 格式化
insertPragma: false,
// 用于控制文本是否应该被换行以及如何进行换行
proseWrap: 'preserve',
// 在html中空格是否是敏感的 "css" - 遵守 CSS 显示属性的默认值, "strict" - 空格被认为是敏感的 "ignore" - 空格被认为是不敏感的
htmlWhitespaceSensitivity: 'css',
// 控制在 Vue 单文件组件中 <script> 和 <style> 标签内的代码缩进方式
vueIndentScriptAndStyle: false,
// 换行符使用 lf 结尾是 可选值 "<auto|lf|crlf|cr>"
endOfLine: 'lf',
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码 (rangeStart开始rangeEnd结束)
rangeStart: 0,
rangeEnd: Infinity
};

@ -0,0 +1,606 @@
# 更新日志
## v1.2.0-beta 20250422
> [!NOTE]
>
> [升级指南](https://szadmin.cn/md/Help/doc/other/upgrade.html#v1-2-0-beta)
- sz-boot-parent
- 优化:[代码生成器] - 为生成代码增加是否忽略表前缀的功能。PR[#140](https://github.com/feiyuchuixue/sz-boot-parent/pull/140)(感谢[crash](https://github.com/processcrash))。
- 优化:重构**数据权限**核心逻辑,修复部分问题。
- 修复:[数据填充]-deptScope属性失败问题
- 优化Websocket相关简化SocketMessage和TransferMessage类的泛型使用
- 新增:系统消息功能
- 新增:[演示] 消息发送接口
- 新增:[CICD] Docker Sz-Socket CI Prod.yml
- 优化:提高用户元数据变更性能
- sz-admin
- 修复:滑块验证码在某些浏览器无法滑动的问题
- 新增:分类筛选器组件
- 优化:滑块验证码的耗时计算逻辑
- 新增系统消息功能搭配Websocket可体验完整功能
- 新增:功能演示
- 新增:关于项目
- 新增:全局菜单:[消息、功能演示菜、关于项目]
## v1.1.0-beta 20250406
> [!NOTE]
>
> [升级指南](https://szadmin.cn/md/Help/doc/other/upgrade.html#v1-1-0-beta)
- sz-boot-parent
- 依赖升级:
- spring-boot-starter-parent3.4.2 -> 3.4.4。
- mybatis-flex.version1.10.8 -> 1.10.9。
- sa-token1.40.0 -> 1.41.0。
- swagger-annotations2.2.27 -> 2.2.29。
- aspectjweaver1.9.22.10 -> 1.9.23。
- springdoc-openapi-starter-webmvc-ui: 2.8.3 -> 2.8.6
- aws-crt 0.33.7 -> 0.37.0
- HikariCP6.2.1 -> 6.3.0
- aws.s32.29.50 -> 2.31.11
- 优化:[代码生成器] - 添加/api/types/*.ts生成模板
- sz-admin
- 升级Eslint8.x -> Eslint9
- <font color="red">可能的破坏性更新: </font>切换目录`/api/interface`至`/api/types`
- 新增:`useDictOptions` Hook
- 优化:同步`useDictOptions` Hook写法
- 修复:某些情况下部门树多选报错的问题
- 优化:@import scss 官方已不推荐使用,修改为 @use
- 更新pnpm 依赖
## v1.0.2-beta 20250302
- sz-boot-parent
- 依赖升级:
- mybatis-flex.version1.10.7 -> 1.10.8。
- 优化更新OSS配置添加协议scheme支持并**弃用isHttps字段**。
- <font color="red">可能的破坏性更新: </font>请切换`isHttps=true/false"` 为` scheme="https/http"`
- 优化重构isNotNull方法支持更广泛的集合类型。
- 优化重构BeanCopyUtils以使用单例ModelMapper实例。
- 优化【代码生成器】添加将bigint类型映射成long Java类型处理。
- 优化在EntityChangeListener onInsert事件中添加对updateTime和updateId的初始设置。
- 修复Excel导出时Long类型在某些情况下报错的问题。
- 新增:系统字典查询-根据类型查询接口。
- sz-admin
- 新增:[Hook] useDict 方法。可使用此方法更新指定typeCode的字典缓存
- 新增:[Hook] useDict 的演示案例。
- 修改:删除字典接口注释。**Issue**[#11](https://github.com/feiyuchuixue/sz-admin/issues/11)(感谢[Kang-Yang](https://github.com/Kang-Yang))。
- 修改README中的地址更正。**Issue**[#12](https://github.com/feiyuchuixue/sz-admin/issues/12)(感谢[Kang-Yang](https://github.com/Kang-Yang))。
## v1.0.1-beta 20250215
- sz-boot-parent
- 依赖升级:
- spring-boot-starter-parent3.4.1 -> 3.4.2。
- mybatis-flex.version1.10.5 -> 1.10.7。
- sa-token1.39.0 -> 1.40.0。
- excel-fastexcel1.0.0 -> 1.1.0。
- mysql-connector-j9.1.0 -> 9.2.0。
- 优化:字典类型删除时同步清除缓存。
- 优化统一异常code规范追加prefix。
- 优化:指定目标账户密码修改后,触发“踢下线”功能。
- 修改:菜单树增加返回参数。
- 优化: 日志格式。**Issue**[#10](https://github.com/feiyuchuixue/sz-admin/issues/10)。(感谢[129duckflew](https://github.com/129duckflew))。
- 新增:第三方开源库和许可证文件说明
- sz-admin
- 修复无效Token响应码不一致的问题。
- 修复AES-GCM加密方法在某些场景浏览器不可用的问题行为验证码
- 优化:【代码生成器】- 生成信息中**上级菜单=目录**时与模块名的联动。
- 新增:第三方开源库和许可证文件说明
## v1.0.0-beta 20250128| 大型更新
- sz-boot-parent
- 优化sonar 代码(质量)规范化
- sz-admin
- 优化sonar 代码(质量)规范化
-
## v0.9.0 20250119
- sz-boot-parent
- 优化:无效文件清理
- 优化javadoc 注释
- 优化:[sz-service-websocket] 同步调整config路径至项目根目录下
- 优化qodana 代码(质量)规范化
- sz-admin
- 无
## v0.8.8 20250118
- sz-boot-parent
- 修改Jackson序列化添加对`MultipartFile`类型的支持。
- 修改:`router.whitelist` 属性为Set结构。
- 优化:系统用户更新时,同步更新缓存信息。
- 优化:[行为验证码-滑块验证] 增加对double精度的支持。
- 优化:接口白名单,删除非必要的放行接口。
- 修复aop日志打印的一些问题 http-topic.log
- 修复:[代码生成器] 预览时插入按钮SQL问题。
- 修复EntityChangeListener 在处理未登录用户数据初始化时的异常问题。
- 修复:验证码参数`sys.captcha.requestLimit`未启用时redis中仍然记录了次数的问题。
- 修复:[部门管理] 上级部门为`根部门`时编辑校验未通过的问题。
- 依赖升级:
- spotless-maven-plugin2.43.0 -> 2.44.1。
- mybatis-flex.version1.10.2 -> 1.10.5。
- aws.s3.version2.29.23 -> 2.29.50。
- springdoc-openapi-starter-webmvc-ui2.7.0 -> 2.8.3。
- modelmapper3.2.1 -> 3.2.2。
- swagger-annotations2.2.26 -> 2.2.27。
- sz-admin
- 修复个别浏览器Socket异常的问题。
- 优化:[行为验证码-滑块验证] 添加对移动端浏览器的支持。
## v0.8.7 20250109
- sz-boot-parent
- 新增:`sz.cors.allowedOrigins`配置项,允许用户通过配置的方式指定限定域名
- 修复springboot启动时打印logback配置信息的问题 && 优化logback配置
- 优化:接口防抖逻辑
- 当全局设置忽略GET请求的参数为true时如果GET请求的Controller未标注@Debounce注解则跳过防抖处理但若Controller标注了@Debounce注解即使是GET请求也会执行防抖逻辑。
- 新增:行为验证码-滑块验证。感谢([阳纸伞](https://github.com/1327614618))
- 新增:[演示案例] 远程搜索下拉选择组件
- sz-admin
- 新增:行为验证码-滑块验证
- 新增:远程搜索下拉选择组件
- 新增:[演示案例] 远程搜索下拉选择组件
## v0.8.6 20250102
- sz-boot-parent
- 修复: sys_config 缓存时间问题。
- 修改: 移动配置文件至项目【根目录】下。| <font color="red">可能的破坏性更新 </font>
- 修改: 移除pom镜像源配置
- 修改: Dockerfile 增加配置目录挂载的支持
- 新增: GitHub Action workflow |
- sz-admin
- 修复VITE自定义变量验证问题
- 新增gzip打包支持
- 新增Dockerfile
- 修改:.env.production 配置
- 新增GitHub Action workflow
## v0.8.5 20241229
- sz-boot-parent
- 优化commons-logging 引用冲突问题。
- 修复部门编辑时层级deep赋值不正确问题。
- 修复前端module模板文件导入excel缺少参数问题。
- 修复:部门列表节点数量展示问题。
- 优化升级EasyExcel为FastExcel。 | <font color="red">可能的破坏性更新 easyExcel -> fastExcel 的Package包切换</font>
- 优化:增强 Excel 导入异常处理,新增表头校验功能。详见[Excel导入导出](https://szadmin.cn/md/Help/doc/excel.html)
- 依赖升级:
- spring-boot-starter-parent3.4.0 -> 3.4.1。
- sz-admin
- 优化:文件上传模板组件样式。(感谢[Alex-1116](https://github.com/Alex-1116)
- 优化Excel导入组件增加上传进度的支持。
- 优化axios对全局response error的处理。
## v0.8.4 20241216
- sz-boot-parent
- 依赖升级:
- spring-boot-starter-parent3.3.5 -> 3.4.0。
- mybatis-flex.version1.9.7 -> 1.10.2。
- aws-crt0.33.0 -> 0.33.3。
- hutool-jwt5.8.32 -> 5.8.34。
- aws.s32.29.0 -> 2.29.23。
- HikariCP6.0.0 -> 6.2.1。
- common-io2.17.0 -> 2.18.0。
- lombok1.18.34 -> 1.18.36。
- jackson2.17.2 -> 2.18.2。
- swagger-annotations2.2.25 -> 2.2.26。
- mysql-connector-j9.0.0 -> 9.1.0。
- 修改SpringBoot升级3.4.0后对knife4j的兼容性处理 | <font color="red">兼容性更新springboot升级3.4.0后knife4增强默认需禁用</font>
- 删除minio dependency。
- 优化:代码生成器查询。 **PR**[#57]([https://github.com/feiyuchuixue/sz-boot-parent/pull/57)。(感谢**[AiMing317](https://github.com/AiMing317)** )。
- 优化:[代码生成器] 修复若干问题。
- 新增ossClient新增oss文件流下载方法。
- 优化FileUtils 新增对response header的处理方法。
- 优化历史MapperXml/PO的结构映射。
- 新增:模板文件管理模块。
- sz-admin
- 优化:头像框样式。
- 优化:代码生成器查询。 **PR**[#57]([https://github.com/feiyuchuixue/sz-boot-parent/pull/57)。(感谢**[AiMing317](https://github.com/AiMing317)** )。
- 优化Img、Imgs上传组件增加@change事件可获取完整的UploadResul。
- 优化:[代码生成器] 修复一些问题,样式及便利性更新。
- 优化View.DefaultParams添加参数isAdd。可用于区分新增 OR 编辑。
- 优化useDownload组件blob流式下载文件名从response中获取。
- 新增:模板文件管理模块。
- 修改:更新教师统计模板文件名。
## v0.8.3 20241126
- sz-boot-parent
- **删除minio模块。** | <font color="red">可能的破坏性更新</font>
- 新增oss模块使用**AWS S3**协议支持更多云存储厂商阿里、七牛、腾讯、minio等
- 修改切换minio模块至oss模块切换上传方法至ossClient。请将minio.yml文件切换为oss.yml。
- 优化移除冗余NotNull注解。
- sz-admin
- 新增:文件管理。
- 修改oss模块同步改动。
- 新增vite-plugin-vue-devtools插件。
- 优化完善UploadResult返回结构优化文件代码格式。
- 新增:图片上传、批量图片上传组件 (感谢 **Geeker-Admin** https://github.com/HalseySpicy/Geeker-Admin
- 修改:切换用户头像上传为新的组件。
- 文档:
- [oss存储](https://szadmin.cn/md/Help/doc/oss.html)
## v0.8.2 20241119
- sz-boot-parent
- 修复: 菜单详情页权限唯一值校验异常问题。
- 修复: [代码生成] MySql5.7 导入表异常问题。
- 新增: [代码生成] 对字典别名的支持。
- 优化: 字典类型sysDictType增加缓存同步机制。
- sz-admin
- 修复:客户端管理列表,授权类型展示问题。
- 新增:[代码生成] 对字典别名的支持。
## v0.8.1 20241106
- sz-boot-parent
- 依赖升级:
- spring-boot-starter-data-redis3.3.4 -> 3.3.5。
- spring-boot-starter-parent3.3.4 -> 3.3.5。
- 优化sz-service-socket添加启动Banner与版本号。
- 优化:部门列表-未设置部门节点用户数量查询逻辑调整
- 优化:账户登录的查询。
- 修复excel导出字典CodeName为null时的异常问题。
- 优化Excel导入导出及字典转换提升性能。
- sz-admin
- 优化: vite.config.mts更新 SCSS 预处理器配。
- 优化: 锁定sass版本为~1.79.x版本。
- 优化: tsconfig.app.json 增加package.json的支持。
- 优化: 使用TypeScript的模块扩展优化package.json 的version。
- 优化: 增强 env.d.ts 文件中的类型定义。
- 文档:
- [Excel导入导出](https://szadmin.cn/md/Help/doc/excel.html)
## v0.8.0 20241017
- sz-boot-parent
- 依赖升级:
- spring-boot-starter-data-redis3.3.3 -> 3.3.4。
- 修复sql预览模版结尾多余空格问题。
- 修复:无法正常解锁用户问题 **PR**[#26](https://github.com/feiyuchuixue/sz-boot-parent/issues/26#issue-2577509320)。(感谢[andyjia](https://github.com/andyjia)
- 优化:重构异常处理,引入错误前缀枚举,增强代码可读性和可维护。
- 新增spotless maven 格式化插件,全局格式化。
- 新增CHANGE.md 更新日志。
- 修改将数据表中的自增ID字段类型升级为**bigint**,以支持更大的数据范围和避免潜在的溢出问题。| <font color="red">可能的破坏性更新</font>
- 优化自增ID字段升级为bigint
- 代码生成
- 系统菜单
- 系统字典
- 角色
- 用户
- 教师统计(演示)
- 新增自增ID字段升级为bigint DDL脚本。
- 修复:部分页面批量删除异常问题。
- 优化:[代码生成] 前端模版添加对(string | number)[] 类型的支持。
- 优化:验证权限标识唯一性接口。
- 优化接口防抖增加GET请求忽略的支持。
- 优化通用返回接口对额外参数param的处理。
- 优化:[代码生成] 模版对菜单预览SQL的调整。
- 优化:数据库迁移脚本 --业务脚本 示例修改。
- sz-admin
- 依赖升级:
- **vite****4.5.3 -> 5.4.8**。
- **axios** 1.7.2 -> 1.7.7
- **vue**3.4.21 -> 3.5.12
- pinia2.1.7 -> 2.2.4
- **vue-router****4.4.0 -> 4.4.5**
- sortablejs1.15.2 -> 1.15.3
- pinia-plugin-persistedstate 3.2.1 -> 3.2.3
- @vueuse/core10.11.0 -> 10.11.1
- prettier3.3.2 -> 3.3.3
- @types/node18.19.39 -> 18.19.55
- **element-plus****2.7.6 -> 2.8.5**
- **sass****1.77.7 -> 1.79.5**
- @vue/tsconfig0.4.0 -> 0.5.1
- vue-tsc1.8.27 -> 2.1.6
- 修复:菜单管理、字典管理 预览SQL的格式问题。
- 修复:代码预览组件行号展示问题。
- 优化: 更新API状态码常量。
- 优化:提取请求超时时间参数**VITE_APP_HTTP_TIMEOUT** 单位ms默认超时时间60s
- 优化: useSelection组件添加对number数组的支持。
- 优化: 修改API参数类型以支持数字和字符串。
- 优化: 角色标识展示样式。
- 优化: vite5.x 使用ESM语法 vite.config.ts 升级为vite.config.mts。
- 优化: eslintrc.cjs 规则。
- 优化: prettier format配置。
- 优化:代码格式化,代码清理。
## v0.7.11 20241009
- sz-boot-parent
- 依赖升级:
- spring-boot-starter-data-redis3.3.3 -> 3.3.4。
- 修复sql预览模版结尾多余空格问题。
- 修复:无法正常解锁用户问题 **PR**[#26](https://github.com/feiyuchuixue/sz-boot-parent/issues/26#issue-2577509320)。(感谢[andyjia](https://github.com/andyjia)
- 优化:重构异常处理,引入错误前缀枚举,增强代码可读性和可维护。
- 新增spotless maven 格式化插件,全局格式化。
- 新增CHANGE.md 更新日志。
- 修改将数据表中的自增ID字段类型升级为**bigint**,以支持更大的数据范围和避免潜在的溢出问题。| <font color="red">可能的破坏性更新</font>
- 优化自增ID字段升级为bigint
- 代码生成
- 系统菜单
- 系统字典
- 角色
- 用户
- 教师统计(演示)
- 新增自增ID字段升级为bigint DDL脚本。
- 修复:部分页面批量删除异常问题。
- 优化:[代码生成] 前端模版添加对(string | number)[] 类型的支持。
- 优化:验证权限标识唯一性接口。
- 优化接口防抖增加GET请求忽略的支持。
- 优化通用返回接口对额外参数param的处理。
- 优化:[代码生成] 模版对菜单预览SQL的调整。
- 优化:数据库迁移脚本 --业务脚本 示例修改。
## v0.7.10 20240919
- sz-boot-parent
- 依赖升级:
- com.alibaba:easyexcel4.0.2 -> 4.0.3。
- 修复:某些情况下账户列表查询的分页问题。
- 修复数据权限组合拼接条件时OR、AND的优先级问题。
- 新增:接口防抖功能。
- 优化sz自定义配置格式统一化。
- sz-admin
- 优化WebSocket验证逻辑。
- 优化env环境变量。
- 修复dict接口登录时请求两次的问题。
## v0.7.9 20240913
- sz-boot-parent
- 依赖升级:
- org.apache.commons:commons-lang33.16.0 -> 3.17.0
- swagger-annotations 2.2.21 -> 2.2.23.
- org.aspectj:aspectjweaver1.9.22 -> 1.9.22.1
- mysql-connector-j 8.4.0 -> 9.0.0
- hutool-jwt 5.8.31 -> 5.8.32
- 新增BeanCopyUtils 增加copy, copyNotIgnoreNull方法。
- 优化:**数据权限**映射表名的获取规则:优先使用@Table注解名称属性。
- 修复:角色管理标识空值校验问题。
- 优化:[代码生成] 前端搜索项独立enum Select的支持。
- 修复: @SaIgnore注解失效问题。
- 优化: @SaIgnore的使用场景,[详见文档](https://szadmin.cn/md/Help/doc/code-standard.html#_9-%E6%8E%A5%E5%8F%A3%E9%89%B4%E6%9D%83) 。
- 优化: 账户detail详情查询接口排除敏感信息。
- sz-admin
- 优化改造SearchForm组件添加对enum的独立支持。感谢[Alex-1116](https://github.com/Alex-1116)
- 修改:[教师统计] searchColumns enum演示。
## v0.7.8 20240902
- sz-boot-parent
- 依赖升级:
- spring-boot-starter-parent3.2.5 -> 3.3.3
- jackson2.16.1 -> 2.17.2
- hutool-jwt5.8.27 -> 5.8.31
- easyexcel 3.3.4 -> 4.0.2
- aspectjweaver 1.9.22 -> 1.9.22.1
- commons-lang33.14.0 -> 3.16.0
- mybatis-flex-spring-boot3-starter1.9.3 -> 1.9.7
- modelmapper3.2.0 -> 3.2.1
- minio8.5.10 -> 8.5.12
- sa-token1.38.0 -> 1.39.0
- 修复代码生成zip生成时流未关闭的问题。
- 修复ftl sql模板int类型参数自动转换千分位的问题。
- 修复:字典更新问题。
- 优化:类名、包结构。
- 修改MybatisFlex**禁用全局null值自动忽略**。代码生成器模版、部分业务查询拼接同步改动。|增强逻辑清晰和确定性。
- sz-admin
- 修改客户端管理Form项描述错误
- 优化:[代码生成] 编辑Form字典类型下拉展示。区分静态字典和动态字典。感谢[Alex-1116](https://github.com/Alex-1116)
## v0.7.7 20240823
- sz-boot-parent
- 优化:包结构及代码。
- 修复字典sql导出ID格式问题。
- 修复:[代码生成] LocalDateTime范围查询Search区域展示异常问题。
- 新增:字典改造,对静态字典、动态字典提供支持。
- 优化DictVO对象完善字典查询sql增加对逻辑删除的支持。
- 修改SysUser 动态字典:用户信息的支持。
- 优化TeacherStatics演示示例@DictFormat注解isSelected()的调整等。感谢[阳纸伞](https://github.com/1327614618)的贡献代码。
- sz-admin
- 优化:账户管理-切换部门列表时list接口请求两次额问题。
## v0.7.6 20240814
- sz-boot-parent
- 修复:[代码生成] excel导出条数与列表查询数不符的问题。
- 修复:[代码生成] 关联字典的查询条件生成类型为input而非select的问题。
- 修复:[代码生成] 新增菜单类型【菜单】时deep层级赋值不正确问题。
- 优化:[代码生成] 代码生成逻辑修复一些bug。
- 优化添加business业务Flyway README描述文件。
- 优化遵循jdk21建议使用@Serial注解标识serialVersionUID。
- 优化角色增加is_lock, permissions属性。
- 修复字典菜单SQL查看时某些原因无法展示的问题。
- sz-admin
- 优化:代码生成字段类型添加描述。
- 优化角色管理增加标识属性permissions、锁定状态is_lock
## v0.7.5 20240812
- sz-boot-parent
- 优化:账户新增时,如果选择了可用的部门,将账户创建到指定部门。
- 新增字典管理SQL导出功能。
- 优化:代码生成模板格式。
- sz-admin
- 修复Header结构Avatar组件 默认头像展示问题。(感谢[Alex-1116](https://github.com/Alex-1116)
- 优化: 代码生成HighCode组件 增加行号展示。(感谢[Alex-1116](https://github.com/Alex-1116)
- 修复:账号管理 新增用户后 与部门列表的通讯问题。(感谢[Alex-1116](https://github.com/Alex-1116)
- 优化:代码生成编辑操作 根据导入导出checkbox 动态展示对应columns。感谢[Alex-1116](https://github.com/Alex-1116)
- 优化:账户新增时,如果选择了可用的部门,将账户创建到指定部门下。
- 新增字典管理SQL导出功能。
- 修复:代码生成组件 行号 头部标题固定。(感谢[Alex-1116](https://github.com/Alex-1116)
## v0.7.4 20240730
- sz-boot-parent
- 优化业务与框架Flyway迁移脚本分离
- 新增多 Flyway 实例配置,分别管理业务和框架迁移脚本。
- 修改 `flyway.yml` 配置项,实现配置的清晰划分。
- 利用 `@ConfigurationProperties` 自动绑定配置项,创建独立的 Flyway 实例
- 各自使用独立的历史版本表t_db_version 和 t_db_version_business以避免相互干扰
- 优化迁移管理,提高数据库版本管理的灵活性和扩展性
- 更新了flyway的ddl脚本路径在classpath:/db 路径下新增framework、business路径并**将原 classpath:/db 路径下 的DDL文件移动至 classpath:/db/framework下**。| <font color="red">可能的破坏性更新</font>
> [!IMPORTANT]
>
> - **重要通知:数据库迁移操作**
>
> 为了确保数据库迁移的顺利进行,我们特别提醒您注意以下步骤:
>
> 1. **迁移DDL脚本**请将所有使用Flyway编写的自定义DDL非框架提供的v1.1~v1.8版本)迁移脚本移动至`classpath:/db/business`目录下。
> 2. **重新规划DDL版本**在迁移脚本完成后重新规划并更新DDL的版本号确保版本控制的一致性和可追溯性。
> 3. **数据备份****务必在执行迁移操作之前**,对现有数据库进行全面的数据备份。这是保障数据安全的关键步骤,避免在迁移过程中发生数据丢失或损坏的风险。
> 4. **执行迁移操作**在确认数据备份无误后按照既定的迁移计划执行DDL迁移操作。
## v0.7.3 20240728
- sz-boot-parent
- 修复部门逻辑删除后account数据展示问题。
- 优化permission唯一性校验。
- 新增逻辑删除自动填充支持delete_id,delete_time属性。
- 优化:代码生成器,提取配置文件。
- 优化代码生成器menu生成Sort逻辑。
- 修复:预览时检查菜单重复的问题。
- sz-admin
- 优化permission唯一性校验。
- 优化码生成器编辑Form表单的交互体验。
- 官网文档:
- 新增:[代码生成器](https://szadmin.cn/md/Help/gen/generator-tools.html)文档。
## v0.7.2 20240719
> [!WARNING]
>
> **数据权限的session存储结构部分发生了改变移除了customUserIds、customDeptIds。启用了userRuleMap、deptRuleMap来配合灵活的自定义规则。**
- sz-boot-parent
- 修改:数据权限,逻辑优化。减少用户操作,提升用户体验。
- sz-admin
- 修改数据权限Form优化交互逻辑。
- 修复数据权限编辑后再新增操作项disable的问题。
- 官网文档:
- 修改:[数据权限文档](https://szadmin.cn/md/Help/doc/data-scope.html) 对部分逻辑进行了简化。
## v0.7.1 20240717
- sz-boot-parent
- 修复代码生成pojo类注释名称问题
- 优化:用户管理功能,代码清理
- 修复TreeUtils constructTreeRecursiveExcludeNode方法忽略指定节点不生效的问题
- 修复AOP url参数解析异常问题
- 优化:文件清理,命名规范化
- 新增StringUtils toSnakeCase方法
- **修改:字典管理,添加业务类型,区分系统字典、业务字典** | <font color="red">可能的破坏性更新</font>
> [!IMPORTANT] !重要
>
> 1000号段为业务性字典如有新增的字典请迁移至2000号段。受影响的字典类型值为“1006”、“1007”。
- 新增:数据权限实现
- sz-admin
- 优化账户添加Form tooltip
- 修复:菜单缓存失效问题 [#IA8QI1](https://gitee.com/feiyuchuixue/sz-admin/issues/IA8QI1) | Gitee
- 优化角色权限Form禁止esc关闭及点击其他区域关闭
- 修复socket参数不存在时仍连接socket的问题
- 新增:数据字典-业务类型
- 修复:默认头像无法展示问题
- 修复home页切换“分栏”布局报错问题
- 修复:查询条件项在某些条件下无法展示全的问题
- 优化:代码清理,规范化命名
- 新增:数据权限
- 官网文档:
- 新增:[数据权限文档](https://szadmin.cn/md/Help/doc/data-scope.html)
## v0.6.5 20240619
- sz-boot-parent
- 修复:未登录状态接口自动数据填充异常问题
- 修复:代码生成,批量删除异常问题
- 升级升级Mybatis-Flex版本v1.9.3
## v0.6.4 20240612
- sz-boot-parent
- 修复权限问题orRole不生效的问题 [#IA4F9Z](https://gitee.com/feiyuchuixue/sz-boot-parent/issues/IA4F9Z)
- 新增:查询用户角色接口
- sz-admin
- 新增权限校验v-auth指令 对超管角色的处理。env 新增 VITE_ADMIN_BYPASS_PERMISSION属性。
## v0.6.3 20240609
- sz-boot-parent
- 修复:代码生成器拖拽排序丢失问题
- 优化:代码生成器-字段信息编辑模板调整,增加数据库模板
- 优化:代码生成菜单唯一性检查逻辑
- 修改: 升级Mybatis-Flex版本v1.9.2
- sz-admin
- 优化:代码生成器-字段信息编辑、生成信息编辑的操作逻辑
- 优化:代码生成器-字段信息编辑 选择模板功能区,增加数据库模板选项,完善提示信息
- 修复ElMessage提示信息展示不在顶层的问题
- 优化:页脚增加版本号展示
## v0.6.2 20240605
- sz-boot-parent
- 优化:将密码错误次数、错误冻结时间等魔法值提取到参数管理中
- sz-admin
- 优化优化nginx推荐配置解决网络原因导致socket频繁断开的问题
- 官网文档:
- 新增:代码规范,前端代码提交前检查
- 修改:序言,商业用途免费。
- 优化:技术栈增加技术依赖超链接
- 新增websocket Nginx配置建议
- 修改感谢对前端贡献者s1990218yao增加描述
- 修改:页脚,增加友链
## v0.6.1 20240603
- sz-boot-parent
- 修改admin账户初始密码为**sz123456**
- 优化更新README文档
- sz-admin
- 修改登陆页面placeholder
- 优化更新README文档
----
## v0.6.0 20240602
- sz-boot-parent
- init
- sz-admin
- init
- sz-deploy
- init

@ -0,0 +1,12 @@
# 使用官方的 Nginx 镜像作为基础镜像
FROM nginx:1.27.3
LABEL authors="sz"
# 将前端项目静态文件复制到容器中
COPY ./dist /usr/share/nginx/sz-admin
# 暴露 9800 端口
EXPOSE 9800
# 启动 Nginx 服务
CMD ["nginx", "-g", "daemon off;"]

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2024] [升职哦sz]
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.

@ -0,0 +1,112 @@
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sz-Admin</h1>
<h4 align="center">基于SpringBoot3、Vue3的轻量级脚手架</h4>
<p align="center">
<a href="https://github.com/feiyuchuixue/sz-boot-parent/stargazers"><img src="https://img.shields.io/github/stars/feiyuchuixue/sz-boot-parent?style=flat-square&logo=GitHub"></a>
<a href="https://github.com/feiyuchuixue/sz-boot-parent/network/members"><img src="https://img.shields.io/github/forks/feiyuchuixue/sz-boot-parent?style=flat-square&logo=GitHub"></a>
<a href='https://gitee.com/feiyuchuixue/sz-boot-parent/stargazers'><img src='https://gitee.com/feiyuchuixue/sz-boot-parent/badge/star.svg?theme=dark' alt='star'></img></a>
<a href='https://gitee.com/feiyuchuixue/sz-boot-parent/members'><img src='https://gitee.com/feiyuchuixue/sz-boot-parent/badge/fork.svg?theme=dark' alt='fork'></img></a>
<a href="https://github.com/feiyuchuixue/sz-boot-parent/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache_2.0-blue.svg"></a>
</p>
# 简介
> 接触了很多优秀的开源和闭源项目,在使用过程中也发现一些问题,不甘满足的我遂产生了想法:于是利用休息时间编写了一套后台管理系统,它**灵活、简洁、高效**,拥抱最新的技术,因此**Sz-Admin**便诞生了也意为升职Admin升职加薪节节高。
**[Sz Admin](https://szadmin.cn/)** ,一个基于 Spring Boot 3、Vue 3 和 Element-Plus 的开源中后台管理框架,致力于为您提供一个流畅、直观且功能强大的开发框架。它不仅融合了最新的技术趋势,而且通过精心设计,确保了系统的简洁性和高效,让使用者可以专注业务。
## 在线体验
- 官网地址https://szadmin.cn
- 文档地址https://szadmin.cn/md/Help/doc/info/start.html
- 预览地址https://preview.szadmin.cn
- 代码仓库:
- 前端:
- **Github****[sz-admin](https://github.com/feiyuchuixue/sz-admin.git)**
- **Gitee****[sz-admin](https://gitee.com/feiyuchuixue/sz-admin.git)**
- 后端:
- **Github****[sz-boot-parent](https://github.com/feiyuchuixue/sz-boot-parent.git)**
- **Gitee****[sz-boot-parent](https://gitee.com/feiyuchuixue/sz-boot-parent.git)**
- 部署:
- **Github****[sz-deploy](https://github.com/feiyuchuixue/sz-deploy.git)**
- **Gitee****[sz-deploy](https://gitee.com/feiyuchuixue/sz-deploy.git)**
## 系统要求
- JDK >= 21
- MySQL >= 8.0.34
- Maven >= 3.8
- Node >= 18.x
## 核心技术
- **SpringBoot 3.x** 最新的Spring Boot版本提供更优的性能和更丰富的特性。
- **Sa-Token**:一个轻量级 Java 权限认证框架,简化权限认证,保障应用的安全性。
- **Mybatis Flex**:一个优雅的 `MyBatis` 增强框架,它非常轻量、同时拥有极高的性能与灵活性。
- **Flyway**`数据库版本控制`工具,确保数据库迁移的可靠性。
- **Knife4j**:一个为 `Swagger` 接口文档增强的工具,提供了更直观的 API 文档展示和更便捷的接口测试体验。
- ~~**Minio**:一个开源的对象存储服务,提供高性能、分布式存储解决方案,兼容 S3 API。~~
- **AWS S3** 一个广泛兼容的存储解决方案。通过采用 AWS S3 协议,我们的服务现在能够无缝集成并兼容多种对象存储服务,包括但不限于 MinIO、阿里云OSS和腾讯云OSS等。
- **HikariCP**:选择 `HikariCP` 作为 JDBC 连接池,提供快速且高效的数据库连接管理。
- **Vue 3.x**:采用 `Vue 3.x`Vue.js 的最新稳定版本,提供更强的性能和更丰富的功能,构建响应式用户界面。
- **Vite 5.x**:使用 `Vite 5`.x 作为前端开发和构建工具,它利用现代浏览器的原生 ES 模块导入特性,提供了快速的冷启动和即时模块热更新。
- **TypeScript**:通过 `TypeScript` 的集成,引入静态类型检查,增强了代码的可维护性和可读性,提前避免潜在的错误。
- **Pinia**:状态管理采用 `Pinia`,这是 Vue 3 的解构式状态管理库,它简单、灵活且易于使用,优化了应用的状态管理。
- **Element-Plus**:一个基于 Vue 3 的组件库,提供了一系列高质量的 UI 组件,帮助开发者快速构建美观、功能完备的用户界面。
## 功能列表
- **账户管理**:负责管理系统用户的创建、配置及权限分配,确保用户身份的合法性和操作的合规性。
- **角色管理**:实现角色与权限的精细绑定,通过角色分配简化用户权限管理,提高系统安全性和灵活性。
- **菜单管理**:定制化系统导航结构,通过权限细分确保用户仅访问授权的操作界面,增强操作的直观性和可控性。
- **字典管理**:维护系统内静态数据字典,如配置项、枚举值等,以统一管理和优化数据的一致性。
- **参数管理**:动态调整系统运行参数,无需重启即可实时生效,提升系统响应速度和运维效率。
- **客户端管理**:监管客户端接入,确保客户端的合法性和安全性,维护系统的整体稳定性。
- **部门管理**:构建组织架构,通过树状结构展示,支持数据权限的层级化管理,加强信息的有序性和安全性。
- **代码生成器**自动化生成前后端代码模板支持CRUD操作加速开发周期提升开发效率。
- **WebSocket**提供WebSocket支持。
- **数据权限支持**:通过精细控制和灵活配置,确保用户仅访问授权的数据,强化数据安全性和系统响应性。
- **接口防抖**:通过限制短时间内的重复请求,防止脏数据产生,确保数据的准确性和系统稳定性。
## 系统美照
<table>
<tr>
<td><img alt="登录页" src="https://minioapi.szadmin.cn/public/img/login.webp"/></td>
<td><img alt="home页" src="https://minioapi.szadmin.cn/public/img/home.webp"/></td>
</tr>
<tr>
<td><img alt="账户管理" src="https://minioapi.szadmin.cn/public/img/account.webp"/></td>
<td><img alt="角色管理" src="https://minioapi.szadmin.cn/public/img/role.webp"/></td>
</tr>
<tr>
<td><img alt="菜单管理" src="https://minioapi.szadmin.cn/public/img/menu.webp"/></td>
<td><img alt="字典管理" src="https://minioapi.szadmin.cn/public/img/dict.webp"/></td>
</tr>
<tr>
<td><img alt="配置管理" src="https://minioapi.szadmin.cn/public/img/config.webp"/></td>
<td><img alt="客户端管理" src="https://minioapi.szadmin.cn/public/img/client.webp"/></td>
</tr>
<tr>
<td><img alt="部门管理" src="https://minioapi.szadmin.cn/public/img/dept.webp"/></td>
<td><img alt="代码预览" src="https://minioapi.szadmin.cn/public/img/gen-preview.webp"/></td>
</tr>
<tr>
<td><img alt="代码生成配置1" src="https://minioapi.szadmin.cn/public/img/gen-editor.webp"/></td>
<td><img alt="代码生成配置2" src="https://minioapi.szadmin.cn/public/img/gen-editor2.webp"/></td>
</tr>
</table>
----
## 参与讨论
<img alt="加入群聊" src="https://minioapi.szadmin.cn/public/img/wechat.webp"/>
------
**邮箱feiyuchuixue@163.com**

@ -0,0 +1,697 @@
# 第三方库和许可证
本项目中引用了以下第三方开源项目及其许可证信息:
## 项目列表
### **[Geeker-Admin](https://github.com/HalseySpicy/Geeker-Admin)**
- **仓库地址**: https://github.com/HalseySpicy/Geeker-Admin
- **许可证类型**: MIT License
- **许可证内容:**
```
MIT License
Copyright (c) 2022 Halsey
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.
```
### [Sa-Token](https://github.com/dromara/Sa-Token)
- **仓库地址**: https://github.com/dromara/Sa-Token
- **许可证类型**: Apache License 2.0
- **许可证内容:**
```
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2011-2019 hubin.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
### **[mybatis-flex](https://github.com/mybatis-flex/mybatis-flex)**
- **仓库地址**: https://github.com/mybatis-flex/mybatis-flex
- **许可证类型**: Apache License 2.0
- **许可证内容:**
```
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.
```
### [knife4j](https://github.com/xiaoymin/knife4j)
- **仓库地址**: https://github.com/xiaoymin/knife4j
- **许可证类型**: Apache License 2.0
- **许可证内容:**
```
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "{}" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright 2017 小明
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.
```
### [fastexcel](https://github.com/fast-excel/fastexcel)
- **仓库地址**: https://github.com/fast-excel/fastexcel
- **许可证类型**: MIT License
- **许可证内容:**
```
MIT License
Copyright (c) 2024 CodePhiliaX
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.
```
## 其他
请确保您遵循每个项目的许可证要求,并在必要时进行相应的说明和归属。

10
env.d.ts vendored

@ -0,0 +1,10 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_CLIENT_ID: string;
readonly VITE_APP_HTTP_TIMEOUT: number;
readonly VITE_SOCKET_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

@ -0,0 +1,227 @@
import globals from 'globals';
import pluginJs from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginVue from 'eslint-plugin-vue';
import vueParser from 'vue-eslint-parser';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
/** @type {import('eslint').Linter.Config[]} */
export default [
{
// ignores: ['**/node_modules', '**/dist', '*/*/types', '.gitignore', '.npmrc', '.DS_Store']
ignores: ['**/*']
},
// {languageOptions: { globals: {...globals.browser, ...globals.node} }},
/** js推荐配置 */
pluginJs.configs.recommended,
/** ts推荐配置 */
...tseslint.configs.recommended,
/** vue推荐配置 */
...pluginVue.configs['flat/essential'],
/** prettier 配置 */
eslintPluginPrettierRecommended,
/** javascript 规则 */
{
files: ['**/*.{js,mjs,cjs,ts,vue,jsx,tsx}'],
rules: {
// 对象结尾逗号
'comma-dangle': 'off',
// 关闭未定义变量
'no-undef': 'off',
// 确保 Prettier 的行为不会被 ESLint 覆盖
quotes: ['error', 'single', { allowTemplateLiterals: true }],
// 关闭对未定义变量的警告
'no-undefined': 'off',
//不使用的变量不报错
'no-unused-vars': 'off',
// 禁止使用不规范的空格
'no-irregular-whitespace': 'off',
// 函数括号前的空格
'space-before-function-paren': 0,
// 箭头函数的空格
'arrow-spacing': [
2,
{
before: true,
after: true
}
],
// 代码块的空格
'block-spacing': [2, 'always'],
// 大括号风格
'brace-style': [
2,
'1tbs',
{
allowSingleLine: true
}
],
// 对象属性换行
'object-property-newline': 'off',
// JSX 引号风格 <'prefer-double', 'prefer-single'>
'jsx-quotes': [2, 'prefer-double'],
// 对象键值对之间的空格
'key-spacing': [
2,
{
beforeColon: false,
afterColon: true
}
],
// 关键字之间的空格
'keyword-spacing': [
2,
{
before: true,
after: true
}
],
// 构造函数首字母大写
'new-cap': [
2,
{
newIsCap: true,
capIsNew: false
}
],
// new 操作符使用时需要括号
'new-parens': 2,
// 禁止使用 Array 构造函数
'no-array-constructor': 2,
// 禁止调用 caller 和 callee
'no-caller': 2,
// 禁止重新分配类名
'no-class-assign': 2,
// 禁止条件中的赋值操作
'no-cond-assign': 2,
// 禁止 const 重新分配
'no-const-assign': 2,
// 正则表达式中的控制字符
'no-control-regex': 0,
// 禁止删除变量
'no-delete-var': 2,
// 禁止在函数参数中使用重复名称
'no-dupe-args': 2,
// 禁止在类中使用重复名称的成员
'no-dupe-class-members': 2,
// 禁止在对象字面量中使用重复的键
'no-dupe-keys': 2,
// 禁止重复的 case 标签
'no-duplicate-case': 2,
// 禁止空的字符类
'no-empty-character-class': 2,
// 禁止空的解构模式
'no-empty-pattern': 2,
// 禁止使用 eval
'no-eval': 2,
// 不允许出现空的代码块
'no-empty': 2,
// 禁止不必要的布尔转换
'no-extra-boolean-cast': 2,
// 禁止不必要的括号
'no-extra-parens': [2, 'functions'],
// 禁止 case 语句落空
'no-fallthrough': 2,
// 禁止在数字后面添加小数点
'no-floating-decimal': 2,
// 禁止对函数声明重新赋值
'no-func-assign': 2,
// 禁止出现歧义多行表达式
'no-unexpected-multiline': 2,
// 禁止不需要的转义
'no-useless-escape': 0,
// 数组的括号前后的间距
'array-bracket-spacing': [2, 'never']
}
},
/** vue 规则 */
{
files: ['**/*.vue'],
languageOptions: {
parser: vueParser,
globals: { ...globals.browser, ...globals.node },
parserOptions: {
/** typescript项目需要用到这个 */
parser: tseslint.parser,
ecmaVersion: 'latest',
/** 允许在.vue 文件中使用 JSX */
ecmaFeatures: {
jsx: true
}
}
},
rules: {
'vue/component-definition-name-casing': 'off',
'vue/singleline-html-element-content-newline': ['off'],
'vue/no-mutating-props': [
'error',
{
shallowOnly: true
}
],
// 要求组件名称始终为 “-” 链接的单词
'vue/multi-word-component-names': 'off',
// 关闭 index.html 文件报 clear 错误
'vue/comment-directive': 'off',
// 关闭对 defineProps 的有效性检查
'vue/valid-define-props': 'off',
// 允许在一个文件中定义多个组件
'vue/one-component-per-file': 'off',
// 关闭 Prop 类型要求的警告
'vue/require-prop-types': 'off',
// 关闭属性顺序要求
'vue/attributes-order': 'off',
// 关闭对默认 Prop 的要求
'vue/require-default-prop': 'off',
// 关闭连字符命名检验
'vue/attribute-hyphenation': 'off',
// 关闭自闭合标签的要求
'vue/html-self-closing': 'off',
// 禁止在关闭的括号前有换行
'vue/html-closing-bracket-newline': 'off',
// 允许使用 v-html 指令
'vue/no-v-html': 'off'
}
},
{
/**
* "off" 0 ==> 关闭规则
* "warn" 1 ==> 打开的规则作为警告不影响代码执行
* "error" 2 ==> 规则作为一个错误代码不能执行界面报错
*/
rules: {
// eslint (http://eslint.cn/docs/rules)
// 'no-var': 'error', // 要求使用 let 或 const 而不是 var
// 'no-multiple-empty-lines': ['error', { max: 1 }], // 不允许多个空行
// 'prefer-const': 'off', // 使用 let 关键字声明但在初始分配后从未重新分配的变量,要求使用 const
// 'no-use-before-define': 'off', // 禁止在 函数/类/变量 定义之前使用它们
// typeScript (https://typescript-eslint.io/rules)
// '@typescript-eslint/no-unused-vars': 'error', // 禁止定义未使用的变量
// '@typescript-eslint/prefer-ts-expect-error': 'error', // 禁止使用 @ts-ignore
// '@typescript-eslint/ban-ts-comment': 'error', // 禁止 @ts-<directive> 使用注释或要求在指令后进行描述
// '@typescript-eslint/no-inferrable-types': 'off', // 可以轻松推断的显式类型可能会增加不必要的冗长
// '@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间
'@typescript-eslint/no-explicit-any': 'off' // 禁止使用 any 类型
// '@typescript-eslint/ban-types': 'off', // 禁止使用特定类型
// '@typescript-eslint/no-var-requires': 'off', // 允许使用 require() 函数导入模块
// '@typescript-eslint/no-empty-function': 'off', // 禁止空函数
// '@typescript-eslint/no-non-null-assertion': 'off', // 不允许使用后缀运算符的非空断言(!)
// vue (https://eslint.vuejs.org/rules)
// 'vue/script-setup-uses-vars': 'error', // 防止<script setup>使用的变量<template>被标记为未使用此规则仅在启用该no-unused-vars规则时有效
// 'vue/v-slot-style': 'error', // 强制执行 v-slot 指令样式
// 'vue/no-mutating-props': 'error', // 不允许改变组件 prop
// 'vue/custom-event-name-casing': 'error', // 为自定义事件名称强制使用特定大小写
// 'vue/html-closing-bracket-newline': 'error', // 在标签的右括号之前要求或禁止换行
// 'vue/attribute-hyphenation': 'error', // 对模板中的自定义组件强制执行属性命名样式my-prop="prop"
// 'vue/attributes-order': 'off', // vue api使用顺序强制执行属性顺序
// 'vue/no-v-html': 'off', // 禁止使用 v-html
// 'vue/require-default-prop': 'off', // 此规则要求为每个 prop 为必填时,必须提供默认值
// 'vue/multi-word-component-names': 'off' // 要求组件名称始终为 “-” 链接的单词
// 'vue/no-setup-props-destructure': 'off' // 禁止解构 props 传递给 setup
}
}
];

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sz-Admin</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

@ -0,0 +1,41 @@
server {
listen 9800;
listen [::]:9800;
server_name localhost;
access_log /var/log/nginx/default.access.log;
error_log /var/log/nginx/default.error.log;
location / {
root /usr/share/nginx/sz-admin;
try_files $uri $uri/ /index.html;
index index.html;
}
location /api {
client_max_body_size 200M; # 设置最大文件上传大小
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_pass $NGINX_PROXY_PASS;
proxy_pass http://sz-service-admin:9991/api;
}
# websocket 支持,如不需要请手动注释
location /socket {
proxy_pass http://sz-service-websocket:9993; # 后端 WebSocket 服务器的地址
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 3600s; # 设置为1小时
proxy_send_timeout 3600s; # 设置为1小时
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/sz-admin;
}
}

14052
package-lock.json generated

File diff suppressed because it is too large Load Diff

7
package.d.ts vendored

@ -0,0 +1,7 @@
// 在 package.d.ts 文件中
declare module 'package.json' {
const content: {
version: string;
};
export = content;
}

@ -0,0 +1,63 @@
{
"name": "sz-admin",
"version": "1.2.0-beta",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
"lint": "eslint --fix",
"format": "prettier --write src/"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@eslint/js": "^9.24.0",
"@highlightjs/vue-plugin": "^2.1.0",
"@rushstack/eslint-patch": "^1.11.0",
"@vueuse/core": "^10.11.1",
"axios": "^1.8.4",
"element-plus": "^2.9.7",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-vue": "^9.0.0",
"globals": "^16.0.0",
"highlight.js": "^11.11.1",
"mitt": "^3.0.1",
"nprogress": "^0.2.0",
"pinia": "^2.3.1",
"pinia-plugin-persistedstate": "^3.2.3",
"screenfull": "^6.0.2",
"sortablejs": "^1.15.6",
"typescript-eslint": "^8.29.0",
"vite-plugin-svg-icons": "^2.0.1",
"vue": "^3.5.13",
"vue-eslint-parser": "^10.1.3",
"vue-i18n": "^9.14.4",
"vue-router": "^4.5.0",
"jsencrypt": "^3.3.2"
},
"devDependencies": {
"@tsconfig/node18": "^18.2.4",
"@types/node": "^18.19.86",
"@types/node-forge": "^1.3.11",
"@types/nprogress": "^0.2.3",
"@types/sortablejs": "^1.15.8",
"@vitejs/plugin-vue": "^4.6.2",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/tsconfig": "^0.5.1",
"eslint": "^8.57.0",
"node-forge": "^1.3.1",
"npm-run-all2": "^6.2.6",
"prettier": "^3.5.3",
"sass": "~1.79.6",
"typescript": "~5.2.2",
"vite": "^5.4.17",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-vue-devtools": "^7.7.2",
"vue-tsc": "^2.2.8"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,43 @@
<template>
<el-config-provider :locale="locale" :size="assemblySize" :button="buttonConfig">
<RouterView />
</el-config-provider>
</template>
<script setup lang="ts">
import { useTheme } from '@/hooks/useTheme';
import { useAppStore } from '@/stores/modules/app';
import { useI18n } from 'vue-i18n';
import { getBrowserLang } from '@/utils';
import en from 'element-plus/es/locale/lang/en';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import type { LanguageType } from '@/stores/interface/app';
import { computed, onMounted, reactive } from 'vue';
const appStore = useAppStore();
// init theme
const { initTheme } = useTheme();
initTheme();
// init language
const i18n = useI18n();
onMounted(() => {
const language = appStore.language ?? getBrowserLang();
i18n.locale.value = language;
appStore.changeLanguage(language as LanguageType);
});
// element language
const locale = computed(() => {
if (appStore.language === 'zh') return zhCn;
if (appStore.language === 'en') return en;
return getBrowserLang() === 'zh' ? zhCn : en;
});
// element assemblySize
const assemblySize = computed(() => appStore.assemblySize);
// element button config
const buttonConfig = reactive({ autoInsertSpace: false });
</script>

@ -0,0 +1,61 @@
import { ElMessage } from 'element-plus';
/**
*
* @type {string}
*/
export const CODE_SUCCESS: number = 200;
/**
* Token
* @type {string}
*/
export const CODE_TOKEN_FAIL: string = 'C105';
/**
* @description:
* @param {Number} status
* @return void
*/
export const checkStatus = (status: number, message?: string) => {
switch (status) {
case 400:
ElMessage.error(message || '请求失败!请您稍后重试');
break;
case 401:
ElMessage.error(message || '登录失效!请您重新登录');
break;
case 403:
ElMessage.error(message || '当前账号无权限访问!');
break;
case 404:
ElMessage.error(message || '你所访问的资源不存在!');
break;
case 405:
ElMessage.error(message || '请求方式错误!请您稍后重试');
break;
case 408:
ElMessage.error(message || '请求超时!请您稍后重试');
break;
case 422:
ElMessage.error(message || '请求参数异常!');
break;
case 500:
ElMessage.error(message || '服务异常!');
break;
case 502:
ElMessage.error(message || '网关错误!');
break;
case 503:
ElMessage.error(message || '服务不可用!');
break;
case 504:
ElMessage.error(message || '网关超时!');
break;
// 自定义响应码,处理 blob流异常时的返回信息
case 1004:
ElMessage.error(message || '模板文件不存在!');
break;
default:
ElMessage.error(message || '请求失败!');
}
};

@ -0,0 +1 @@
export const ADMIN_MODULE: string = '/admin';

@ -0,0 +1,152 @@
import axios from 'axios';
import type { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios';
import router from '@/router';
import { LOGIN_URL } from '@/config';
import { checkStatus, CODE_SUCCESS, CODE_TOKEN_FAIL } from '@/api/helper';
import type { IResultData } from '@/api/types';
import { useUserStore } from '@/stores/modules/user';
import { useAuthStore } from '@/stores/modules/auth';
import { useSocketStore } from '@/stores/modules/socket';
import { ElMessage } from 'element-plus';
export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
loading?: boolean;
cancel?: boolean;
}
const config = {
// 默认地址请求地址,可在 .env.** 文件中修改
baseURL: import.meta.env.VITE_API_URL as string,
// 设置超时时间默认超时时间60s
timeout: import.meta.env.VITE_APP_HTTP_TIMEOUT || 60000
// 跨域时候允许携带凭证
// withCredentials: true
};
class RequestHttp {
instance: AxiosInstance;
constructor(config: AxiosRequestConfig) {
// instantiation
this.instance = axios.create(config);
/**
* @description
* -> [] ->
* token(JWT) : token, vuex/pinia/
*/
this.instance.interceptors.request.use(
(config: CustomAxiosRequestConfig) => {
const userStore = useUserStore();
// 当前请求不需要显示 loading在 api 服务中通过指定的第三个参数: { loading: false } 来控制
// config.loading !== false && showFullScreenLoading()
if (config.headers && typeof config.headers.set === 'function') {
config.headers.set('Authorization', 'Bearer ' + userStore.token);
}
if (config.method === "post") {
config.headers.set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
}
return config;
},
error => {
return Promise.reject(error);
}
);
/**
* @description
* -> [] -> JS
*/
this.instance.interceptors.response.use(
response => {
const { data } = response;
const userStore = useUserStore();
const socketStore = useSocketStore();
const authStore = useAuthStore();
// 如果是文件流,直接返回整个响应对象
if (response.config.responseType === 'blob') {
return response;
}
//tryHideFullScreenLoading()
// 登陆失效
if (data.code === CODE_TOKEN_FAIL) {
userStore.clear();
authStore.clear();
// 关闭socket
socketStore.close();
router.replace(LOGIN_URL);
ElMessage.error(data.msg);
return Promise.reject(data);
}
// 全局错误信息拦截(防止下载文件的时候返回数据流,没有 code 直接报错)
if (data.code && data.code !== CODE_SUCCESS) {
ElMessage.error({ message: data.msg, dangerouslyUseHTMLString: true });
return Promise.reject(data);
}
// 成功请求(在页面上除非特殊情况,否则不用处理失败逻辑)
return data;
},
async error => {
const { response } = error;
// tryHideFullScreenLoading()
// 请求超时 && 网络错误单独判断,没有 response
if (error.message.indexOf('timeout') !== -1) {
ElMessage.error('请求超时!请您稍后重试');
}
if (error.message.indexOf('Network Error') !== -1) {
ElMessage.error('网络错误!请您稍后重试');
}
// 根据服务器响应的错误状态码,做不同的处理
if (response) {
checkStatus(response?.status, response?.data?.message);
}
// 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
if (!window.navigator.onLine) {
router.replace('/500');
}
return Promise.reject(error);
}
);
}
/**
* @description
*/
get<T>(url: string, params: any = {}, config?: AxiosRequestConfig<any> | undefined): Promise<IResultData<T>> {
return this.instance.get(url, { params, ...config });
}
post<T>(url: string, params: any = {}, config?: AxiosRequestConfig<any> | undefined): Promise<IResultData<T>> {
return this.instance.post(url, params, config);
}
put<T>(url: string, params: any = {}, config?: AxiosRequestConfig<any> | undefined): Promise<IResultData<T>> {
return this.instance.put(url, params, config);
}
delete<T>(url: string, data: any = {}, config?: AxiosRequestConfig<any> | undefined): Promise<IResultData<T>> {
return this.instance.delete(url, { data, ...config });
}
download(url: string, params = {}, config?: AxiosRequestConfig<any> | undefined): Promise<BlobPart> {
return this.instance.post(url, params, { ...config, responseType: 'blob' });
}
template(url: string, params = {}, config?: AxiosRequestConfig<any> | undefined): Promise<BlobPart> {
return this.instance.get(url, { params, ...config, responseType: 'blob' });
}
upload<T>(url: string, params: any = {}, config?: AxiosRequestConfig<any> | undefined): Promise<IResultData<T>> {
return this.instance.post(url, params, {
...config,
headers: {
'Content-Type': 'multipart/form-data'
}
});
}
}
export default new RequestHttp(config);

@ -0,0 +1,12 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { Message } from '@/api/types/system/message';
/**
*
* @param params
* @returns {*}
*/
export const sendMessageApi = (params: Message) => {
return http.post(ADMIN_MODULE + `/www/message/send`, params);
};

@ -0,0 +1,16 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { CaptchaInfo, CaptchaVerifyImageParams } from '@/api/types/system/captcha';
// 获取验证码是否启用
export const getCaptchaStatus = () => {
return http.get<boolean>(`/dsBase/captcha/status`, {});
};
// 获取滑块拼图验证码
export const getImageCodeApi = () => {
return http.post<CaptchaInfo>(ADMIN_MODULE + `/captcha/get`, {});
};
// 校验滑块拼图验证码
export const verifyImageCodeApi = (params: CaptchaVerifyImageParams) => {
return http.post(ADMIN_MODULE + `/captcha/check`, params);
};

@ -0,0 +1,49 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { IPage } from '@/api/types';
import type { SysClientForm, SysClientQuery, SysClientRow } from '@/api/types/system/client';
/**
*
* @param params
* @returns {*}
*/
export const getSysClientListApi = (params: SysClientQuery) => {
return http.get<IPage<SysClientRow>>(ADMIN_MODULE + `/sys-client`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const createSysClientApi = (params: SysClientForm) => {
return http.post(ADMIN_MODULE + `/sys-client`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const updateSysClientApi = (params: SysClientForm) => {
return http.put(ADMIN_MODULE + `/sys-client`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const removeSysClientApi = (params: { ids: number[] }) => {
return http.delete(ADMIN_MODULE + `/sys-client`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const getSysClientDetailApi = (params: { id: number }) => {
const { id } = params;
return http.get<SysClientRow>(ADMIN_MODULE + `/sys-client/${id}`);
};

@ -0,0 +1,10 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { CommonTemplateDown } from '@/api/types/system/common';
/**
*
*/
export const downloadTemplate = (params: CommonTemplateDown) => {
return http.template(ADMIN_MODULE + `/common/download/templates`, params);
};

@ -0,0 +1,40 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { ConfigForm, ConfigInfo, ConfigQuery } from '@/api/types/system/config';
import type { IPage } from '@/api/types';
/**
*
* @param params
* @returns {*}
*/
export const getConfigList = (params: ConfigQuery) => {
return http.get<IPage<ConfigInfo>>(ADMIN_MODULE + `/sys-config`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const addConfig = (params: ConfigForm) => {
return http.post(ADMIN_MODULE + `/sys-config`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const editConfig = (params: ConfigForm) => {
return http.put(ADMIN_MODULE + `/sys-config`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const deleteConfig = (params: { ids: number[] }) => {
return http.delete(ADMIN_MODULE + `/sys-config`, params);
};

@ -0,0 +1,58 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { IPage } from '@/api/types';
import type { SysDataRoleQuery, SysDataRoleRow, SysDataRoleForm, SysDataRoleMeta } from '@/api/types/system/datarole';
/**
*
* @param params
* @returns {*}
*/
export const getSysDataRoleListApi = (params: SysDataRoleQuery) => {
return http.get<IPage<SysDataRoleRow>>(ADMIN_MODULE + `/sys-data-role`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const createSysDataRoleApi = (params: SysDataRoleForm) => {
return http.post(ADMIN_MODULE + `/sys-data-role`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const updateSysDataRoleApi = (params: SysDataRoleForm) => {
return http.put(ADMIN_MODULE + `/sys-data-role`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const removeSysDataRoleApi = (params: { ids: (string | number)[] }) => {
return http.delete(ADMIN_MODULE + `/sys-data-role`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const getSysDataRoleDetailApi = (params: { id: number }) => {
const { id } = params;
return http.get<SysDataRoleRow>(ADMIN_MODULE + `/sys-data-role/${id}`);
};
/**
*
* @param params
* @returns {*}
*/
export const getSysDataRoleMenuApi = () => {
return http.get<SysDataRoleMeta>(ADMIN_MODULE + `/sys-data-role/menu`);
};

@ -0,0 +1,77 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { IPage } from '@/api/types';
import type { SysDeptQuery, SysDeptRow, SysDeptForm, SysDeptTree, SysDeptLeaderData, SysDeptDept } from '@/api/types/system/dept';
/**
*
* @param params
* @returns {*}
*/
export const getSysDeptListApi = (params: SysDeptQuery) => {
return http.get<IPage<SysDeptRow>>(ADMIN_MODULE + `/sys-dept`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const createSysDeptApi = (params: SysDeptForm) => {
return http.post(ADMIN_MODULE + `/sys-dept`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const updateSysDeptApi = (params: SysDeptForm) => {
return http.put(ADMIN_MODULE + `/sys-dept`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const removeSysDeptApi = (params: { ids: number[] }) => {
return http.delete(ADMIN_MODULE + `/sys-dept`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const getSysDeptDetailApi = (params: { id: number }) => {
const { id } = params;
return http.get<SysDeptRow>(ADMIN_MODULE + `/sys-dept/${id}`);
};
/**
*
* @param params
* @returns {*}
*/
export const getMenuTree = (params: { excludeNodeId?: number; appendRoot?: boolean }) => {
return http.get<SysDeptTree[]>(ADMIN_MODULE + `/sys-dept/tree`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const getSysDeptLeaderApi = () => {
return http.get<SysDeptLeaderData>(ADMIN_MODULE + `/sys-dept/leader`);
};
/**
*
* @param params
* @returns {*}
*/
export const getDeptTrees = (params: { deptId?: number }) => {
return http.get<SysDeptDept>(ADMIN_MODULE + `/sys-dept/datascope`, params);
};

@ -0,0 +1,107 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { DictTypeQuery, DictType, DictQuery, Dict, DictCustom } from '@/api/types/system/dict';
import type { IPage } from '@/api/types';
/**
*
* @param params
* @returns {*}
*/
export const getDictType = (params: DictTypeQuery) => {
return http.get<IPage<DictType>>(ADMIN_MODULE + `/sys-dict-type`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const addDictType = (params: DictType) => {
return http.post(ADMIN_MODULE + `/sys-dict-type`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const editDictType = (params: DictType) => {
return http.put(ADMIN_MODULE + `/sys-dict-type`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const deleteDictType = (params: { ids: number[] }) => {
return http.delete(ADMIN_MODULE + `/sys-dict-type`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const getDictData = (params: DictQuery) => {
return http.get<IPage<Dict>>(ADMIN_MODULE + `/sys-dict`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const addDictData = (params: Dict) => {
return http.post(ADMIN_MODULE + `/sys-dict`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const editDictData = (params: Dict) => {
return http.put(ADMIN_MODULE + `/sys-dict`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const deleteDictData = (params: { ids: number[] }) => {
return http.delete(ADMIN_MODULE + `/sys-dict`, params);
};
/**
*
* @returns {*}
*/
export const getAllDict = () => {
return http.get<Record<string, DictCustom[]>>(ADMIN_MODULE + `/sys-dict/dict`, {});
};
export const getDictTypeOptions = () => {
return http.get<DictType[]>(ADMIN_MODULE + `/sys-dict-type/selectOptionsType`);
};
/**
* sql
* @param params
*/
export const exportDictSql = (params: { ids: number[] }) => {
return http.post<string>(ADMIN_MODULE + `/sys-dict/sql/export`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const getDictByCode = (params: { typeCode: string[] }) => {
const searchParams = new URLSearchParams();
params.typeCode.forEach(code => searchParams.append('typeCode', code));
return http.get<Record<string, DictCustom[]>>(ADMIN_MODULE + `/sys-dict/code?${searchParams.toString()}`);
};

@ -0,0 +1,13 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { IPage } from '@/api/types';
import type { SysFileQuery, SysFileRow } from '@/api/types/system/file';
/**
*
* @param params
* @returns {*}
*/
export const getSysFileListApi = (params: SysFileQuery) => {
return http.get<IPage<SysFileRow>>(ADMIN_MODULE + `/sys-file`, params);
};

@ -0,0 +1,32 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { LoginParams, LoginInfo } from '@/api/types/system/login';
// 用户登录
export const loginApi = (params: LoginParams) => {
return http.post(`/dsBase/loginPerson/doLogin`, params); // 正常 post json 请求 ==> application/json
// return http.post(`/login`, params, { loading: false }); // 控制当前请求不显示 loading
// return http.post(`/login`, {}, { params }); // post 请求携带 query 参数 ==> ?username=admin&password=123456
// return http.post(`/login`, qs.stringify(params)); // post 请求携带表单参数 ==> application/x-www-form-urlencoded
// return http.get(`/login?${qs.stringify(params, { arrayFormat: "repeat" })}`); // get 请求可以携带数组等复杂参数
};
// 获取菜单列表
export const getAuthMenuListApi = () => {
return http.get<Menu.MenuOptions[]>(ADMIN_MODULE + `/sys-menu/menu`, {});
};
// 获取按钮权限
export const getAuthButtonListApi = () => {
return http.get<string[]>(ADMIN_MODULE + `/sys-menu/btn/permissions`, {});
};
// 用户退出登录
export const logoutApi = () => {
return http.post(ADMIN_MODULE + `/auth/logout`);
};
// 获取用户角色
export const getAuthRoleListApi = () => {
return http.get<string[]>(ADMIN_MODULE + `/sys-menu/user/roles`, {});
};

@ -0,0 +1,85 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { MenuQuery, MenuForm, MenuTree, MenuPermissionQuery } from '@/api/types/system/menu';
/**
*
* @param params
* @returns {*}
*/
export const getMenuList = (params: MenuQuery) => {
return http.get<Menu.MenuOptions[]>(ADMIN_MODULE + `/sys-menu`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const addMenu = (params: MenuForm) => {
return http.post(ADMIN_MODULE + `/sys-menu`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const editMenu = (params: MenuForm) => {
return http.put(ADMIN_MODULE + `/sys-menu`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const deleteMenu = (params: { ids: string[] }) => {
return http.delete(ADMIN_MODULE + `/sys-menu`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const getMenuInfo = (params: { id: string }) => {
const { id } = params;
return http.get(ADMIN_MODULE + `/sys-menu/${id}`);
};
/**
*
* @param params
* @returns {*}
*/
export const getMenuTree = (params: { nodeId?: string }) => {
return http.get<MenuTree[]>(ADMIN_MODULE + `/sys-menu/tree`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const getBtnExits = (params: MenuPermissionQuery) => {
return http.get<{ permissionCount: number }>(ADMIN_MODULE + `/sys-menu/btn/exists`, params);
};
/**
* sql
* @param params
*/
export const exportMenuSql = (params: { ids: string[] }) => {
return http.post<string>(ADMIN_MODULE + `/sys-menu/sql/export`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const chaneDataRole = (params: { id: string }) => {
const { id } = params;
return http.put(ADMIN_MODULE + `/sys-menu/datarole/change/${id}`);
};

@ -0,0 +1,39 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { IPage } from '@/api/types';
import type { MessageQuery, MessageRow, UnreadMessageCount } from '@/api/types/system/message';
/**
*
*/
export const getMessageListApi = (params: MessageQuery) => {
return http.get<IPage<MessageRow>>(ADMIN_MODULE + `/sys-message`, params);
};
/**
*
*/
export const getMessageInfoApi = (id: string | number) => {
return http.get<MessageRow>(ADMIN_MODULE + `/sys-message/${id}`);
};
/**
*
*/
export const getTodoMessageListApi = () => {
return http.get<MessageRow[]>(ADMIN_MODULE + `/sys-message/list/todo`);
};
/**
*
*/
export const getNoticeMessageListApi = () => {
return http.get<MessageRow[]>(ADMIN_MODULE + `/sys-message/list/msg`);
};
/**
*
*/
export const getUnreadMessageCountApi = () => {
return http.get<UnreadMessageCount>(ADMIN_MODULE + `/sys-message/count`);
};

@ -0,0 +1,58 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { RoleQuery, RoleInfo, RoleForm, RoleMenu, RoleMenuForm } from '@/api/types/system/role';
import type { IPage } from '@/api/types';
/**
*
* @param params
* @returns {*}
*/
export const getRoleList = (params: RoleQuery) => {
return http.get<IPage<RoleInfo>>(ADMIN_MODULE + `/sys-role`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const addRole = (params: RoleForm) => {
return http.post(ADMIN_MODULE + `/sys-role`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const editRole = (params: RoleForm) => {
return http.put(ADMIN_MODULE + `/sys-role`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const deleteRole = (params: { ids: number[] }) => {
return http.delete(ADMIN_MODULE + `/sys-role`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const getRoleMenus = (params: { roleId: number }) => {
return http.get<RoleMenu>(ADMIN_MODULE + `/sys-role/menu`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const setRoleMenus = (params: RoleMenuForm) => {
return http.put(ADMIN_MODULE + `/sys-role/menu`, params);
};

@ -0,0 +1,64 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { IPage } from '@/api/types';
import type {
SysTempFileQuery,
SysTempFileRow,
SysTempFileForm,
SysTempFileHistoryQuery,
SysTempFileHistory
} from '@/api/types/system/sysTempFile';
/**
*
* @param params
* @returns {*}
*/
export const getSysTempFileListApi = (params: SysTempFileQuery) => {
return http.get<IPage<SysTempFileRow>>(ADMIN_MODULE + `/sys-temp-file`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const createSysTempFileApi = (params: SysTempFileForm) => {
return http.post(ADMIN_MODULE + `/sys-temp-file`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const updateSysTempFileApi = (params: SysTempFileForm) => {
return http.put(ADMIN_MODULE + `/sys-temp-file`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const removeSysTempFileApi = (params: { ids: (string | number)[] }) => {
return http.delete(ADMIN_MODULE + `/sys-temp-file`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const getSysTempFileDetailApi = (params: { id: number }) => {
const { id } = params;
return http.get<SysTempFileRow>(ADMIN_MODULE + `/sys-temp-file/${id}`);
};
/**
*
* @param params
*/
export const getSysTempFileHistoryListApi = (params: SysTempFileHistoryQuery) => {
return http.get<IPage<SysTempFileHistory>>(ADMIN_MODULE + `/sys-temp-file-history/history`, params);
};

@ -0,0 +1,23 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { IUploadResult, UploadFile, UploadResult } from '@/api/types/system/upload';
import type { AxiosRequestConfig } from 'axios';
/**
*
* @param params
* @returns {*}
*/
export const uploadFile = (params: UploadFile, config?: AxiosRequestConfig<any> | undefined) => {
return http.upload<UploadResult>(ADMIN_MODULE + `/sys-file/upload`, params, config);
};
/**
*
* @param params
* @param config
* @returns {*}
*/
export const uploadTmpFile = (params: UploadFile, config?: AxiosRequestConfig<any> | undefined) => {
return http.upload<IUploadResult>(ADMIN_MODULE + `/sys-temp-file/upload`, params, config);
};

@ -0,0 +1,138 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { UserQuery, UserInfo, UserForm, UserRoleData, UserRoleForm, UserPasswordForm } from '@/api/types/system/user';
import type { IPage } from '@/api/types';
import type { SysDeptDeptSetting, SysDeptTree } from '@/api/types/system/dept';
/**
*
* @param params
* @returns {*}
*/
export const getUserList = (params: UserQuery) => {
return http.get<IPage<UserInfo>>(ADMIN_MODULE + `/sys-user`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const addUser = (params: UserForm) => {
return http.post(ADMIN_MODULE + `/sys-user`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const editUser = (params: UserForm) => {
return http.put(ADMIN_MODULE + `/sys-user`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const deleteUser = (params: { ids: number[] }) => {
return http.delete(ADMIN_MODULE + `/sys-user`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const getUserRole = (params: { userId: number }) => {
return http.get<UserRoleData>(ADMIN_MODULE + `/sys-user/role`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const setUserRole = (params: UserRoleForm) => {
return http.put(ADMIN_MODULE + `/sys-user/role`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const changePassword = (params: UserPasswordForm) => {
return http.put(ADMIN_MODULE + `/sys-user/password`, params);
};
/**
*
* @param params
*/
export const resetPassword = (params: { id: number }) => {
const { id } = params;
return http.put(ADMIN_MODULE + `/sys-user/reset/password/${id}`, {});
};
/**
*
* @returns {*}
*/
export const getUserinfo = () => {
return http.get<UserInfo>(ADMIN_MODULE + `/sys-user/userinfo`);
};
/**
*
* @param params
* @returns {*}
*/
export const bindUserDeptApi = (params: SysDeptDeptSetting) => {
return http.post(ADMIN_MODULE + `/sys-user/dept/bind`, params);
};
/**
*
*/
export const getUserDeptTree = () => {
return http.get<SysDeptTree[]>(ADMIN_MODULE + `/sys-user/dept/tree`);
};
/**
*
* @param params
* @returns {*}
*/
export const unlockUser = (params: { ids: (string | number)[] }) => {
return http.post(ADMIN_MODULE + `/sys-user/unlock`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const getUserDetailApi = (params: { id: string }) => {
const { id } = params;
return http.get<UserInfo>(ADMIN_MODULE + `/sys-user/${id}`);
};
/**
*
* @param params
* @returns {*}
*/
export const getDataUserRole = (params: { userId: number }) => {
return http.get<UserRoleData>(ADMIN_MODULE + `/sys-user/datarole`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const setUserDataRole = (params: UserRoleForm) => {
return http.put(ADMIN_MODULE + `/sys-user/datarole`, params);
};

@ -0,0 +1,77 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { IPage } from '@/api/types';
import type { TeacherStatisticsQuery, TeacherStatisticsRow, TeacherStatisticsForm } from '@/api/types/teacher/teacherStatistics';
import type { UploadRawFile } from 'element-plus/es/components/upload/src/upload';
import type { AxiosRequestConfig } from 'axios';
/**
*
* @param params
* @returns {*}
*/
export const getTeacherStatisticsListApi = (params: TeacherStatisticsQuery) => {
return http.get<IPage<TeacherStatisticsRow>>(ADMIN_MODULE + `/teacher-statistics`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const createTeacherStatisticsApi = (params: TeacherStatisticsForm) => {
return http.post(ADMIN_MODULE + `/teacher-statistics`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const updateTeacherStatisticsApi = (params: TeacherStatisticsForm) => {
return http.put(ADMIN_MODULE + `/teacher-statistics`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const removeTeacherStatisticsApi = (params: { ids: (string | number)[] }) => {
return http.delete(ADMIN_MODULE + `/teacher-statistics`, params);
};
/**
*
* @param params
* @returns {*}
*/
export const getTeacherStatisticsDetailApi = (params: { id: number }) => {
const { id } = params;
return http.get<TeacherStatisticsRow>(ADMIN_MODULE + `/teacher-statistics/${id}`);
};
/**
* excel
* @param params
*/
export const importTeacherStatisticsExcelApi = (params: UploadRawFile, config?: AxiosRequestConfig<any> | undefined) => {
return http.upload(ADMIN_MODULE + `/teacher-statistics/import`, params, config);
};
/**
* excel
* @param params
* @returns {*}
*/
export const exportTeacherStatisticsExcelApi = (params: TeacherStatisticsQuery) => {
return http.download(ADMIN_MODULE + `/teacher-statistics/export`, params);
};
/**
*
* @param params
*/
export const remoteTeacherStaticsSearchApi = (params: { keyword: string }) => {
const { keyword } = params;
return http.get<string[]>(ADMIN_MODULE + `/teacher-statistics/remote/${keyword}`);
};

@ -0,0 +1,91 @@
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';
import type { IPage, IPageQuery } from '@/api/types';
import type {
GeneratorCheckInfo,
GeneratorForm,
GeneratorInfo,
GeneratorPreviewInfo,
GeneratorQuery
} from '@/api/types/toolbox/generator';
/**
*
* @param params
* @returns {*}
*/
export const getGeneratorList = (params: GeneratorQuery) => {
return http.get<IPage<GeneratorInfo>>(ADMIN_MODULE + `/generator/list`, params);
};
/**
*
* @param params
*/
export const getGeneratorSchemaList = (params: IPageQuery) => {
return http.get<IPage<GeneratorInfo>>(ADMIN_MODULE + `/generator/schema/list`, params);
};
/**
*
* @param params
*/
export const importGenerator = (params: { tableName: (string | number)[] }) => {
return http.post(ADMIN_MODULE + `/generator/import`, params);
};
/**
*
* @param params
*/
export const saveGenerator = (params: GeneratorForm) => {
return http.put(ADMIN_MODULE + `/generator`, params);
};
/**
*
* @param tableName
*/
export const getGeneratorInfo = (tableName: string) => {
return http.get<GeneratorForm>(ADMIN_MODULE + `/generator/${tableName}`);
};
/**
*
* @param params
*/
export const codeGenerator = (tableName: string) => {
return http.post<string[]>(ADMIN_MODULE + `/generator/generator/${tableName}`);
};
/**
*
* @param params
*/
export const deleteGenerator = (params: { tableNames: (string | number)[] }) => {
return http.delete(ADMIN_MODULE + `/generator`, params);
};
/**
* zip
* @param params
* @returns {*}
*/
export const downloadZip = (params: { tableNames: string[] }) => {
return http.download(ADMIN_MODULE + `/generator/zip`, params);
};
/**
*
* @param tableName
*/
export const previewCode = (tableName: string) => {
return http.get<GeneratorPreviewInfo[]>(ADMIN_MODULE + `/generator/preview/${tableName}`);
};
/**
*
* @param tableName
*/
export const checkDisk = (tableName: string) => {
return http.get<GeneratorCheckInfo>(ADMIN_MODULE + `/generator/check/${tableName}`);
};

@ -0,0 +1,24 @@
// 请求响应参数不包含data
export type IResult = {
code: string;
message: string;
};
// 请求响应参数包含data
export type IResultData<T = any> = IResult & {
data: T;
};
export type IPage<T = any> = {
current: number;
limit: number;
totalPage: number;
total: number;
rows: T[];
param?: { [key: string]: any } | string;
};
export type IPageQuery = {
page: number;
limit: number;
};

@ -0,0 +1,16 @@
// 登录模块
export type CaptchaInfo = {
bigImageBase64: string;
bigWidth: number;
bigHeight: number;
smallImageBase64: string;
smallWidth: number;
smallHeight: number;
requestId: string;
posY: number;
secretKey: string;
};
export type CaptchaVerifyImageParams = {
requestId: string;
moveEncrypted: string;
};

@ -0,0 +1,41 @@
import type { IPageQuery } from '@/api/types';
// 查询条件
export type SysClientQuery = IPageQuery & {
clientKey?: string;
clientSecret?: string;
grantTypeCd?: string;
deviceTypeCd?: string;
activeTimeout?: number;
version?: number;
};
// 编辑form表单
export type SysClientForm = {
clientKey?: string;
clientSecret?: string;
grantTypeCdList?: string[];
grantTypeCd?: string;
deviceTypeCd?: string;
activeTimeout?: number;
timeout?: number;
clientStatusCd?: string;
version?: number;
remark?: string;
};
// list或detail返回结构
export type SysClientRow = {
clientId?: string;
clientKey?: string;
clientSecret?: string;
grantTypeCd?: string;
grantTypeCdList?: string[];
deviceTypeCd?: string;
activeTimeout?: number;
timeout?: number;
clientStatusCd?: string;
version?: number;
remark?: string;
isLock?: string;
};

@ -0,0 +1,4 @@
// 登录模块
export type CommonTemplateDown = {
templateName: string;
};

@ -0,0 +1,27 @@
import type { IPageQuery } from '@/api/types';
export type ConfigQuery = IPageQuery & {
configName?: string;
configKey?: string;
};
export type ConfigForm = {
id?: number;
configName: string;
configKey: string;
configValue: string;
remark: string;
};
export type ConfigInfo = {
id: number;
configName: string;
configKey: string;
configValue: string;
remark: string;
createId: number;
createTime: string;
updateId: number;
updateTime: string;
isLock?: string;
};

@ -0,0 +1,35 @@
import type { IPageQuery } from '@/api/types';
// 查询条件
export type SysDataRoleQuery = IPageQuery & {
roleName?: string;
isLock?: string;
};
// 编辑form表单
export type SysDataRoleForm = {
id?: number;
roleName?: string;
remark?: string;
isLock?: string;
};
// list或detail返回结构
export type SysDataRoleRow = {
id?: number;
roleName?: string;
remark?: string;
isLock?: string;
};
// 数据角色Form初始化数据菜单树、部门树、用户列等
export type SysDataRoleMeta = {
menuLists: SysDataRoleMenuTree[];
};
export type SysDataRoleMenuTree = {
id: string;
pid: string;
title: string;
children: SysDataRoleMenuTree[];
};

@ -0,0 +1,64 @@
import type { IPageQuery } from '@/api/types';
// 查询条件
export type SysDeptQuery = IPageQuery & {
name?: string;
};
// 编辑form表单
export type SysDeptForm = {
id?: number;
name?: string;
pid?: number;
sort?: number;
remark?: string;
leaders: number[];
};
// list或detail返回结构
export type SysDeptRow = {
id: number;
pid: number;
name: string;
deep?: number;
sort?: number;
remark?: string;
isLock?: string;
leaders: number[];
};
export type SysDeptTree = {
id: number;
pid: number;
name: string;
deep?: number;
sort?: number;
children: SysDeptTree[];
userTotal?: number;
};
export type SysDeptLeader = {
id: number;
nickname: string;
};
export type SysDeptLeaderData = {
leaderInfoVOS: SysDeptLeader[];
};
export type SysDeptDeptSetting = {
userIds: number[];
deptIds: number[];
};
export type SysDeptDept = {
deptLists: SysDeptDeptTree[];
selectIds: number[];
};
export type SysDeptDeptTree = {
id: number;
pid: number;
name: string;
children: SysDeptDeptTree[];
};

@ -0,0 +1,66 @@
import type { IPageQuery } from '@/api/types';
// 获取所有字典信息
export type DictCustom = {
callbackShowStyle: string;
codeName: string;
id: string;
isLock: string;
isShow: string;
sort: number;
sysDictTypeId: number;
alias?: string;
};
// 字典类别列表查询
export type DictTypeQuery = IPageQuery & {
typeName: string;
typeCode: string;
};
// 字典类别列表
export type DictType = {
id?: number;
typeName: string;
typeCode: string;
isLock?: string;
isShow?: string;
delFlag?: string;
remark: string;
createTime?: string;
updateTime?: string;
isDynamic: boolean;
};
// 字典分类option
export type DictOption = {
value: string;
label: string;
};
// 字典分类类型
export type DictCategory = {
label: string;
options: DictOption[];
};
// 字典列表查询
export type DictQuery = IPageQuery & {
sysDictTypeId: number;
codeName: string;
};
export type Dict = {
id?: number;
sysDictTypeId?: number;
codeName: string;
alias?: string;
sort?: number;
callbackShowStyle: string;
remark: string;
isLock?: string;
isShow?: string;
delFlag?: string;
createTime?: string;
updateTime?: string;
};

@ -0,0 +1,24 @@
import type { IPageQuery } from '@/api/types';
// 查询条件
export type SysFileQuery = IPageQuery & {
filename?: string;
dirTag?: string;
objectName?: string;
};
// 编辑form表单
export type SysFileForm = {};
// list或detail返回结构
export type SysFileRow = {
id?: number;
filename?: string;
dirTag?: string;
size?: number;
url?: string;
objectName?: string;
contextType?: string;
eTag?: string;
fileId?: number;
};

@ -0,0 +1,36 @@
// 登录模块
export type LoginParams = {
username: string;
password: string;
platform: string;
};
export type LoginInfo = {
name: string;
avatar: string;
introduction: string;
accessToken: string;
refreshToken: string;
roles: string[];
userInfo: UserInfo;
permissions: string[];
};
export type UserInfo = {
id?: number;
username: string;
phone?: string;
nickname?: string;
logo?: string;
age?: number;
sex?: number;
idCard?: string;
email?: string;
accountStatusCd?: string;
userTagCd?: string;
lastLoginTime?: string;
createTime?: string;
updateTime?: string;
identityId?: number;
bureauId?: string;
};

@ -0,0 +1,36 @@
export type MenuQuery = {
isShowButton: boolean;
};
export type MenuForm = {
id?: string;
title?: string;
pid?: string;
path?: string;
name?: string;
icon?: string;
component?: string;
redirect?: string;
sort?: number;
deep?: number;
menuTypeCd?: string;
permissions?: string;
isHidden?: string;
hasChildren?: string;
isLink?: string;
isFull?: string;
isAffix?: string;
isKeepAlive?: string;
};
export type MenuTree = {
id: string;
pid: string;
title: string;
children: MenuTree[];
};
export type MenuPermissionQuery = {
id: string;
permissions: string;
};

@ -0,0 +1,30 @@
import type { IPageQuery } from '@/api/types';
export type MessageQuery = IPageQuery & {
messageTypeCd?: string;
readType?: string;
};
export type MessageRow = {
id: number;
messageTypeCd: string;
senderId: number;
title: string;
content: string;
isRead: string;
createTime: string;
};
export type UnreadMessageCount = {
all: number;
todo: number;
msg: number;
};
export type Message = {
messageTypeCd?: string;
senderId?: number;
title?: string;
content?: string;
receiverIds?: string[];
};

@ -0,0 +1,39 @@
import type { IPageQuery } from '@/api/types';
export type RoleQuery = IPageQuery & {
roleName?: string;
};
export type RoleForm = {
id?: number;
roleName: string;
remark: string;
};
export type RoleInfo = {
id: number;
roleName: string;
remark: string;
delFlag: string;
createTime: string;
updateTime: string;
isLock?: string;
permissions?: string;
};
export type RoleMenu = {
menuLists: RoleMenuTree[];
selectIds: string[];
};
export type RoleMenuTree = {
id: string;
pid: string;
title: string;
children: RoleMenuTree[];
};
export type RoleMenuForm = {
menuIds: string[];
roleId: number;
};

@ -0,0 +1,47 @@
import type { IPageQuery } from '@/api/types';
// 查询条件
export type SysTempFileQuery = IPageQuery & {
tempName?: string;
};
// 历史记录查询条件
export type SysTempFileHistoryQuery = IPageQuery & {
sysTempFileId: number;
};
// 编辑form表单
export type SysTempFileForm = {
id?: number;
sysFileId?: number;
tempName?: string;
url?: string;
remark?: string;
};
// list或detail返回结构
export type SysTempFileRow = {
id?: number;
sysFileId?: number;
tempName?: string;
url?: string;
remark?: string;
delFlag?: string;
createId?: number;
createTime?: string;
updateId?: number;
updateTime?: string;
};
// 历史记录
export type SysTempFileHistory = {
id?: number;
sysTempFileId?: number;
sysFileId?: number;
tempName?: string;
url?: string;
remark?: string;
delFlag?: string;
createId?: number;
createTime?: string;
};

@ -0,0 +1,19 @@
import type { UploadRawFile } from 'element-plus/es/components/upload/src/upload';
export type UploadFile = {
file: UploadRawFile;
dirTag?: string;
};
export type UploadResult = {
url: string;
filename: string;
eTag: string;
objectName: string;
dirTag: string;
contextType: string;
size: number;
fileId: number;
};
export type IUploadResult = UploadResult | null;

@ -0,0 +1,74 @@
// 登录模块
import type { IPageQuery } from '@/api/types';
export type UserQuery = IPageQuery & {
username?: string;
phone?: string;
accountStatusCd?: string;
startDate?: string;
endDate?: string;
nickname?: string;
deptId?: number;
isThisDeep?: boolean;
};
export type UserForm = {
id?: number;
username?: string;
pwd?: string;
phone: string;
nickname: string;
logo: string;
age: number;
sex: number;
idCard: string;
email: string;
accountStatusCd: string;
userTagCd: string;
birthday: string;
};
export type UserInfo = {
id?: number;
username: string;
phone: string;
nickname: string;
logo: string;
age: number;
sex: number;
idCard: string;
email: string;
accountStatusCd: string;
userTagCd: string;
lastLoginTime: string;
createTime: string;
updateTime: string;
delFlag: string;
birthday: string;
};
export type UserRoleForm = {
roleIds: number[];
userId: number;
};
export type UserRoleData = {
selectIds: number[];
roleInfoVOS: UserRoleInfo[];
};
export type UserRoleInfo = {
id: number;
roleName: string;
};
export type UserPasswordForm = {
oldPwd: string;
newPwd: string;
};
export type UserOptions = {
id: number;
username: string;
nickname: string;
};

@ -0,0 +1,50 @@
import type { IPageQuery } from '@/api/types';
// 查询条件
export type TeacherStatisticsQuery = IPageQuery & {
year?: string;
month?: string;
duringTime?: string;
teacherId?: string;
teacherCommonType?: number;
totalTeaching?: number;
totalClassCount?: number;
totalHours?: number;
checkStatus?: number;
checkTime?: string;
lastSyncTime?: string;
};
// 编辑form表单
export type TeacherStatisticsForm = {
id?: number;
year?: string;
month?: string;
duringTime?: string;
teacherId?: string;
teacherCommonType?: number;
totalTeaching?: number;
totalClassCount?: number;
totalHours?: number;
checkStatus?: number;
checkTime?: string;
lastSyncTime?: string;
remark?: string;
};
// list或detail返回结构
export type TeacherStatisticsRow = {
id?: number;
year?: string;
month?: string;
duringTime?: string;
teacherId?: string;
teacherCommonType?: number;
totalTeaching?: number;
totalClassCount?: number;
totalHours?: number;
checkStatus?: number;
checkTime?: string;
lastSyncTime?: string;
remark?: string;
};

@ -0,0 +1,103 @@
import type { IPageQuery } from '@/api/types';
export type GeneratorQuery = IPageQuery & {
tableName?: string;
tableComment?: string;
};
export type GeneratorInfo = {
tableId: number;
tableName: string;
tableComment: string;
className: string;
camelClassName: string;
tplCategory: string;
packageName: string;
moduleName: string;
businessName: string;
functionName: string;
functionAuthor: string;
type: string;
options: string;
path: string;
createId: number;
createTime: string;
updateId: number;
updateTime: string;
};
export type GeneratorForm = {
baseInfo: GeneratorBaseInfo;
columns: GeneratorColumnInfo[];
generatorInfo: GeneratorGeneratorInfo;
};
export type GeneratorBaseInfo = {
tableId?: number;
tableName: string;
tableComment: string;
className: string;
functionAuthor: string;
remark: string;
};
export type GeneratorColumnInfo = {
autofillType: string;
columnComment: string;
columnId: number;
columnName: string;
columnType: string;
dictType: string;
htmlType: string;
isAutofill: string;
isEdit: string;
isIncrement: string;
isInsert: string;
isList: string;
isLogicDel: string;
isPk: string;
isQuery: string;
isRequired: string;
isUniqueValid: string;
javaField: string;
javaType: string;
javaTypePackage: string;
options: string;
queryType: string;
specialPackages: string;
tableId: number;
upCamelField: string;
};
export type GeneratorGeneratorInfo = {
tplCategory?: string;
packageName: string;
moduleName: string;
businessName: string;
functionName: string;
options: string;
type: string;
parentMenuId: string;
hasImport: string;
hasExport: string;
pathApi: string;
pathWeb: string;
generateType: string;
menuInitType: string;
btnPermissionType: string;
isAutofill: string;
};
export type GeneratorPreviewInfo = {
name: string;
code: string;
language: string;
alias: string;
};
export type GeneratorCheckInfo = {
checkedApiPath: boolean;
checkedWebPath: boolean;
pathApi: string;
pathWeb: string;
};

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1711430282380" class="icon" viewBox="0 0 1042 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1749" xmlns:xlink="http://www.w3.org/1999/xlink" width="203.515625" height="200"><path d="M991.085714 658.285714h-36.571428v-129.828571c0-20.114286-16.457143-36.571429-36.571429-36.571429h-365.714286V365.714286h36.571429c20.114286 0 36.571429-16.457143 36.571429-36.571429v-146.285714c0-20.114286-16.457143-36.571429-36.571429-36.571429h-146.285714c-20.114286 0-36.571429 16.457143-36.571429 36.571429v146.285714c0 20.114286 16.457143 36.571429 36.571429 36.571429h36.571428v126.171428h-365.714285c-20.114286 0-36.571429 16.457143-36.571429 36.571429V658.285714h-36.571429c-20.114286 0-36.571429 16.457143-36.571428 36.571429v146.285714c0 20.114286 16.457143 36.571429 36.571428 36.571429h146.285715c20.114286 0 36.571429-16.457143 36.571428-36.571429v-146.285714c0-20.114286-16.457143-36.571429-36.571428-36.571429h-36.571429v-93.257143h329.142857V658.285714h-36.571428c-20.114286 0-36.571429 16.457143-36.571429 36.571429v146.285714c0 20.114286 16.457143 36.571429 36.571429 36.571429h146.285714c20.114286 0 36.571429-16.457143 36.571429-36.571429v-146.285714c0-20.114286-16.457143-36.571429-36.571429-36.571429h-36.571429v-93.257143h329.142858V658.285714h-36.571429c-20.114286 0-36.571429 16.457143-36.571429 36.571429v146.285714c0 20.114286 16.457143 36.571429 36.571429 36.571429h146.285714c20.114286 0 36.571429-16.457143 36.571429-36.571429v-146.285714c0-20.114286-16.457143-36.571429-36.571429-36.571429z" p-id="1750"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1,13 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1720242971107" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4270"
xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<path d="M858.925 913.968H613.599a77.328 77.328 0 0 1-77.952-77.328V663.476a77.952 77.952 0 0 1 77.952-75.624h245.326a78.52 78.52 0 0 1 77.952 77.952v173.164a77.952 77.952 0 0 1-77.952 75zM613.599 647.863a18.452 18.452 0 0 0-18.452 17.884v173.164a18.452 18.452 0 0 0 18.452 17.885h245.326a17.884 17.884 0 0 0 17.884-17.885V665.747a17.884 17.884 0 0 0-17.884-17.884z"
p-id="4271"></path>
<path d="M837.577 645.024a29.41 29.41 0 0 1-29.41-30.034v-69.833a73.808 73.808 0 1 0-147.217 0v69.833a30.034 30.034 0 1 1-57.74 0v-69.833a133.365 133.365 0 1 1 266.105 0v69.833a30.034 30.034 0 0 1-31.738 30.034zM694.447 733.31a41.56 41.56 0 1 0 41.56-41.56 41.56 41.56 0 0 0-41.56 41.56z"
p-id="4272"></path>
<path d="M737.142 838.4a30.034 30.034 0 0 1-30.034-30.034v-51.949a30.034 30.034 0 0 1 57.74 0v51.95a29.466 29.466 0 0 1-27.706 30.033zM491.816 292.905c-40.991 0-404.068 0-404.068-115.424S450.2 62 491.816 62s404.07 0 404.07 115.424-362.51 115.48-404.07 115.48zM150.654 177.48c24.811 21.347 147.616 54.277 341.162 54.277s316.352-32.93 341.162-54.277c-24.81-21.348-147.615-54.845-341.162-54.845s-316.35 35.2-341.162 56.775z m261.507 338.266c-98.732-5.677-323.619-26.57-323.619-115.424a30.034 30.034 0 1 1 57.74 0c7.495 9.822 86.583 43.887 264.97 52.517a28.842 28.842 0 0 1 28.388 31.17 29.466 29.466 0 0 1-27.138 31.737zM369.466 735.07c-129.334-9.822-281.15-36.961-281.15-109.122a30.034 30.034 0 0 1 57.74 0c6.358 8.062 71.593 38.096 223.41 49.621a30.034 30.034 0 0 1 0 57.74z"
p-id="4273"></path>
<path d="M430.045 961.886c-102.195-3.463-340.651-22.71-340.651-115.423V179.184a30.034 30.034 0 0 1 57.74 0v669.947c9.255 12.718 102.763 46.158 282.854 53.085a30.034 30.034 0 0 1 28.842 31.17 29.466 29.466 0 0 1-28.785 28.5z m435.238-590.461a29.466 29.466 0 0 1-29.466-28.842V179.184a30.034 30.034 0 1 1 57.74 0v163.342a30.034 30.034 0 0 1-28.274 28.842z"
p-id="4274"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1,7 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1705474620379" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2013"
xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<path d="M905.185809 178.844158C898.576738 172.685485 891.19337 165.824412 883.21687 158.436127 860.422682 137.322863 837.434925 116.207791 815.697647 96.487895 813.243072 94.261877 813.243072 94.261877 810.786411 92.037081 781.783552 65.781062 757.590948 44.376502 739.713617 29.293612 729.254178 20.469111 721.020606 13.860686 714.970549 9.501727 710.955023 6.608611 707.690543 4.524745 704.47155 2.998714 700.417679 1.07689 696.638044-0.094029 691.307277 0.005928 677.045677 0.273349 665.6 11.769337 665.6 26.182727L665.6 77.352844 665.6 128.522961 665.6 230.863194 665.6 256.448252 691.2 256.448252 896 256.448252 870.4 230.863194 870.4 998.414942 896 972.829884 230.381436 972.829884C187.90385 972.829884 153.6 938.623723 153.6 896.20663L153.6 26.182727 128 51.767786 588.8 51.767786C602.93849 51.767786 614.4 40.312965 614.4 26.182727 614.4 12.05249 602.93849 0.597669 588.8 0.597669L128 0.597669 102.4 0.597669 102.4 26.182727 102.4 896.20663C102.4 966.91021 159.652833 1024 230.381436 1024L896 1024 921.6 1024 921.6 998.414942 921.6 230.863194 921.6 205.278135 896 205.278135 691.2 205.278135 716.8 230.863194 716.8 128.522961 716.8 77.352844 716.8 26.182727C716.8 39.813762 705.748075 50.91427 692.267725 51.167041 687.705707 51.252584 685.069822 50.435995 682.52845 49.231204 682.259458 49.103682 683.344977 49.796618 685.029451 51.010252 689.779394 54.432502 697.145822 60.34494 706.686383 68.394196 724.009052 83.009121 747.816448 104.072869 776.413589 129.961594 778.850014 132.168064 778.850014 132.168064 781.285216 134.376514 802.876774 153.964212 825.739479 174.96442 848.413564 195.966437 856.350957 203.3185 863.697005 210.144893 870.269888 216.269843 874.209847 219.941299 877.019309 222.565641 878.499674 223.951409 888.81866 233.610931 905.019017 233.081212 914.684179 222.768247 924.349344 212.455283 923.819315 196.264383 913.500326 186.604861 911.981323 185.182945 909.155025 182.542876 905.185809 178.844158ZM102.4 461.128719 0 461.128719 0 896.074709 512 896.074709 1024 896.074709 1024 461.128719 153.6 461.128719 153.6 460.531049 102.4 460.531049 102.4 461.128719ZM282 778.8 437.6 778.8 437.6 819.2 220.8 819.2 220.8 786.4 371.4 568.8 221.8 568.8 221.8 528 432.8 528 432.8 560 282 778.8ZM534.4 819.2 484 819.2 484 528 534.4 528 534.4 819.2ZM647.2 711 647.2 819.2 596.6 819.2 596.6 528 708 528C740.533495 528 766.366571 536.466581 785.5 553.4 804.633429 570.333419 814.2 592.733195 814.2 620.6 814.2 649.133476 804.833427 671.333254 786.1 687.2 767.366573 703.066746 741.133502 711 707.4 711L647.2 711ZM647.2 670.4 708 670.4C726.00009 670.4 739.733286 666.166709 749.2 657.7 758.666714 649.233291 763.4 637.000079 763.4 621 763.4 605.266588 758.600047 592.700047 749 583.3 739.399951 573.899953 726.200083 569.066669 709.4 568.8L647.2 568.8 647.2 670.4Z"
p-id="2014"></path>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

@ -0,0 +1,33 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full" width="100%" height="100%" viewBox="0 0 1400 800">
<rect x="1300" y="400" rx="40" ry="40" width="150" height="150" stroke="rgb(129, 201, 149)" fill="rgb(129, 201, 149)">
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="35s" type="rotate" from="0 1450 550" to="360 1450 550" repeatCount="indefinite"/>
</rect>
<path d="M 100 350 A 150 150 0 1 1 400 350 Q400 370 380 370 L 250 370 L 120 370 Q100 370 100 350" fill="#a2b3ff">
<animateMotion path="M 800 -200 L 800 -300 L 800 -200" dur="20s" begin="0s" repeatCount="indefinite"/>
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="30s" type="rotate" values="0 210 530 ; -30 210 530 ; 0 210 530" keyTimes="0 ; 0.5 ; 1" repeatCount="indefinite"/>
</path>
<circle cx="150" cy="150" r="180" stroke="#85FFBD" fill="#85FFBD">
<animateMotion path="M 0 0 L 40 20 Z" dur="5s" repeatCount="indefinite"/>
</circle>
<!-- 三角形 -->
<path d="M 165 580 L 270 580 Q275 578 270 570 L 223 483 Q220 480 217 483 L 165 570 Q160 578 165 580" fill="#a2b3ff">
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="35s" type="rotate" from="0 210 530" to="360 210 530" repeatCount="indefinite"/>
</path>
<!-- <circle cx="1200" cy="600" r="30" stroke="rgb(241, 243, 244)" fill="rgb(241, 243, 244)">-->
<!-- <animateMotion path="M 0 0 L -20 40 Z" dur="9s" repeatCount="indefinite"/>-->
<!-- </circle>-->
<path d="M 100 350 A 40 40 0 1 1 180 350 L 180 430 A 40 40 0 1 1 100 430 Z" fill="#3054EB">
<animateMotion path="M 140 390 L 180 360 L 140 390" dur="20s" begin="0s" repeatCount="indefinite"/>
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="30s" type="rotate" values="0 140 390; -60 140 390; 0 140 390" keyTimes="0 ; 0.5 ; 1" repeatCount="indefinite"/>
</path>
<rect x="400" y="600" rx="40" ry="40" width="100" height="100" stroke="rgb(129, 201, 149)" fill="#3054EB">
<animateTransform attributeType="XML" attributeName="transform" begin="0s" dur="35s" type="rotate" from="-30 550 750" to="330 550 750" repeatCount="indefinite"/>
</rect>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

@ -0,0 +1,277 @@
<template>
<el-dialog
v-model="dialogVisible"
:style="{ width: slideData.bigWidth + 70 + 'px' }"
:close-on-click-modal="false"
@close="closeSlider"
@opened="onDialogOpened"
align-center
destroy-on-close
:close-on-press-escape="false"
>
<div class="sliderBox">
<div class="sliderBox_content">
<!-- 大图显示 -->
<img v-show="slideData.big" :src="'data:image/png;base64,' + slideData.big" class="bigImg" alt="验证码主图" />
<span class="sliderBox_refresh" @click="refreshSlider">
<el-icon class="el-input__icon"><refresh /></el-icon>
</span>
<!-- 小图显示 -->
<img
v-show="slideData.small"
:src="'data:image/png;base64,' + slideData.small"
class="smallImg"
ref="imgK"
alt="验证码拼图"
/>
<!-- 验证结果提示 -->
<div v-if="overlayVisible" class="overlay" :class="{ success: slideData.isSuccess, failure: !slideData.isSuccess }">
<span>{{ overlayMessage }}</span>
</div>
</div>
<div class="btnBox" ref="wrap">
<div class="sliderBox_text">
<span v-if="!isDragging && !slideData.isSuccess"></span>
<span v-if="isDragging"></span>
<span v-if="slideData.isSuccess"></span>
</div>
<!-- 滑块轨迹显示 -->
<div class="sliderBox_track" :style="{ width: trackWidth + 'px' }" />
<!-- 滑块按钮 -->
<div class="sliderBox_btn" ref="slider" @mousedown="onMouseDown" @touchstart="onTouchStart" tabindex="0">&gt;</div>
</div>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, onBeforeUnmount } from 'vue';
import { Refresh } from '@element-plus/icons-vue';
import { getImageCodeApi, verifyImageCodeApi } from '@/api/modules/system/captcha';
import { aesEncrypt } from '@/utils';
defineOptions({ name: 'SliderCaptcha' });
const dialogVisible = ref(false);
const slideData = reactive({
big: '',
small: '',
requestId: '',
posY: 0,
isSuccess: false,
bigWidth: 320,
secretKey: ''
});
const slider = ref<HTMLDivElement | null>(null);
const wrap = ref<HTMLDivElement | null>(null);
const imgK = ref<HTMLImageElement | null>(null);
const trackWidth = ref(0);
const overlayMessage = ref('');
const overlayVisible = ref(false);
const isVerifying = ref(false);
const emit = defineEmits(['success', 'close']);
let isDragging = false;
let startX = 0;
let startLeft = 0;
const acceptParams = () => {
fetchSlideData();
};
const fetchSlideData = async () => {
try {
const { data } = await getImageCodeApi();
Object.assign(slideData, {
big: data.bigImageBase64,
small: data.smallImageBase64,
posY: data.posY,
requestId: data.requestId,
bigWidth: data.bigWidth,
isSuccess: false,
secretKey: data.secretKey
});
overlayVisible.value = false;
overlayMessage.value = '';
trackWidth.value = 0;
dialogVisible.value = true;
setTimeout(resetSliderPosition, 50);
} catch (error) {
console.error('验证失败:', error);
overlayVisible.value = true;
overlayMessage.value = '获取验证码失败,请刷新重试';
}
};
const refreshSlider = async () => {
slideData.isSuccess = false;
overlayVisible.value = false;
overlayMessage.value = '';
await fetchSlideData();
};
const closeSlider = () => {
dialogVisible.value = false;
resetSlider();
emit('close');
};
const resetSlider = () => {
slideData.big = '';
slideData.small = '';
slideData.requestId = '';
slideData.posY = 0;
slideData.isSuccess = false;
slideData.secretKey = '';
slideData.bigWidth = 320;
resetSliderPosition();
};
const resetSliderPosition = () => {
if (slider.value && imgK.value) {
slider.value.style.left = '0px';
imgK.value.style.left = '0px';
ensureImageYPosition();
trackWidth.value = 0;
}
};
const onMouseDown = (e: MouseEvent) => {
if (!slider.value || !wrap.value || isVerifying.value || slideData.isSuccess) return;
e.preventDefault();
startDrag(e.clientX);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};
const onTouchStart = (e: TouchEvent) => {
if (!slider.value || !wrap.value || isVerifying.value || slideData.isSuccess) return;
e.preventDefault();
if (e.touches.length === 1) {
startDrag(e.touches[0].clientX);
document.addEventListener('touchmove', onTouchMove, { passive: false });
document.addEventListener('touchend', onTouchEnd, { passive: false });
}
};
let startTime = 0; //
const startDrag = (clientX: number) => {
isDragging = true;
startX = clientX;
startLeft = parseInt(slider.value!.style.left || '0');
startTime = Date.now(); //
};
const onMouseMove = (e: MouseEvent) => {
if (!isDragging) return;
e.preventDefault();
moveSlider(e.clientX);
};
const onTouchMove = (e: TouchEvent) => {
if (!isDragging) return;
e.preventDefault();
if (e.touches.length === 1) moveSlider(e.touches[0].clientX);
};
const moveSlider = (clientX: number) => {
if (!slider.value || !imgK.value || !wrap.value) return;
const moveX = clientX - startX;
const newLeft = Math.max(0, Math.min(startLeft + moveX, wrap.value.offsetWidth - slider.value.offsetWidth));
slider.value.style.left = `${newLeft}px`;
imgK.value.style.left = `${newLeft}px`;
ensureImageYPosition();
trackWidth.value = newLeft;
};
const onMouseUp = async () => {
if (!isDragging) return;
endDrag();
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
const onTouchEnd = async () => {
if (!isDragging) return;
endDrag();
document.removeEventListener('touchmove', onTouchMove);
document.removeEventListener('touchend', onTouchEnd);
};
const ensureImageYPosition = () => {
if (imgK.value && slideData.posY !== undefined && slideData.posY !== null) {
const yPos = parseInt(String(slideData.posY));
imgK.value.style.top = `${yPos}px`;
return yPos;
}
return 0;
};
const endDrag = async () => {
if (!slider.value || !imgK.value) return;
isDragging = false;
const currentLeft = parseInt(slider.value.style.left || '0');
ensureImageYPosition();
if (currentLeft > 0) {
try {
isVerifying.value = true;
const { iv, encryptedData } = await aesEncrypt(currentLeft + '', slideData.secretKey);
await verifyImageCode({
requestId: slideData.requestId,
startTime,
moveEncrypted: encryptedData,
iv: iv
});
} catch (error) {
console.error('endDrag err:', error);
handleVerificationFailure();
} finally {
isVerifying.value = false;
}
}
};
const verifyImageCode = async (params: { requestId: string; startTime: number; moveEncrypted: string; iv: string }) => {
try {
const { code } = await verifyImageCodeApi(params);
const duration = ((Date.now() - params.startTime) / 1000).toFixed(2);
overlayVisible.value = true;
if (code === '0000') {
slideData.isSuccess = true;
overlayMessage.value = `验证成功, 耗时${duration}s`;
setTimeout(() => {
emit('success');
closeSlider();
}, 1500);
} else {
handleVerificationFailure();
}
} catch {
handleVerificationFailure();
}
};
const handleVerificationFailure = () => {
slideData.isSuccess = false;
overlayMessage.value = '验证失败';
overlayVisible.value = true;
resetSliderPosition();
setTimeout(refreshSlider, 1000);
};
onBeforeUnmount(() => {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
document.removeEventListener('touchmove', onTouchMove);
document.removeEventListener('touchend', onTouchEnd);
});
const onDialogOpened = () => {
setTimeout(() => {
ensureImageYPosition();
}, 100);
};
defineExpose({ acceptParams, dialogVisible });
</script>
<style scoped lang="scss">
@use 'index.scss';
</style>

@ -0,0 +1,123 @@
.sliderBox {
user-select: none;
width: 320px;
padding: 20px;
background-color: #fff;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.2); //
border-radius: 4px;
}
.sliderBox_title {
position: relative;
font-size: 15px;
text-align: center; //
}
.sliderBox_refresh {
position: absolute;
top: 10px;
right: 10px;
width: 18px;
height: 18px;
cursor: pointer;
color: #ffffff;
transition: color 0.3s; //
&:hover {
color: #007bff; //
}
.el-icon {
width: 100%;
height: 100%;
font-size: 18px; //
}
}
.sliderBox_content {
position: relative;
width: 100%;
height: 160px;
border: 1px solid #c6dbf5; // 使
.bigImg {
width: 100%;
height: 100%;
object-fit: cover; //
}
.smallImg {
position: absolute;
width: 50px;
height: 50px;
cursor: pointer;
}
}
.btnBox {
position: relative;
width: 100%;
height: 30px;
margin-top: 22px;
background-color: var(--el-color-primary-light-7); // 使Element Plus
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
.sliderBox_text {
font-size: 14px;
color: #333;
}
.sliderBox_track {
position: absolute;
top: 0;
left: 0;
height: 100%;
background-color: var(--el-color-primary-light-2); // 使
border-radius: 8px;
}
.sliderBox_btn {
position: absolute;
top: -5px;
left: 0;
width: 50px;
height: 40px;
line-height: 40px;
text-align: center;
color: #fff;
background-color: var(--el-color-primary); // 使
border-radius: 8px;
box-shadow: 0 0 6px #ce9f7f; //
cursor: pointer;
}
}
.overlay {
position: absolute;
bottom: 0;
width: 100%;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-size: 14px;
background-color: rgba(0, 0, 0, 0.5); //
transition: background-color 0.3s; //
&.success {
background-color: rgba(92, 184, 92, 0.5); //
}
&.failure {
background-color: rgba(217, 83, 79, 0.5); //
}
}
.sliderBox_btn {
cursor: pointer;
user-select: none;
outline: none;
}
.sliderBox_btn:active {
background: #eee;
}

@ -0,0 +1,20 @@
<template>
<div class="not-container">
<img src="@/assets/images/403.png" class="not-img" alt="403" />
<div class="not-detail">
<h2>403</h2>
<h4>抱歉您无权访问该页面~🙅🙅</h4>
<el-button type="primary" @click="router.back"> </el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
const router = useRouter();
</script>
<style scoped lang="scss">
@use './index';
</style>

@ -0,0 +1,20 @@
<template>
<div class="not-container">
<img src="@/assets/images/404.png" class="not-img" alt="404" />
<div class="not-detail">
<h2>404</h2>
<h4>抱歉您访问的页面不存在~🤷🤷</h4>
<el-button type="primary" @click="router.back"> </el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
const router = useRouter();
</script>
<style scoped lang="scss">
@use './index';
</style>

@ -0,0 +1,20 @@
<template>
<div class="not-container">
<img src="@/assets/images/500.png" class="not-img" alt="500" />
<div class="not-detail">
<h2>500</h2>
<h4>抱歉您的网络不见了~🤦🤦</h4>
<el-button type="primary" @click="router.back"> </el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
const router = useRouter();
</script>
<style scoped lang="scss">
@use './index';
</style>

@ -0,0 +1,38 @@
.not-container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
.not-img {
margin-right: 120px;
}
.not-detail {
display: flex;
flex-direction: column;
h2,
h4 {
padding: 0;
margin: 0;
}
h2 {
font-size: 60px;
color: var(--el-text-color-primary);
}
h4 {
margin: 30px 0 20px;
font-size: 19px;
font-weight: normal;
color: var(--el-text-color-regular);
}
.el-button {
width: 100px;
}
}
}

@ -0,0 +1,72 @@
<template>
<div v-show="isShow" :style="style">
<slot />
</div>
</template>
<script setup lang="ts">
import { computed, inject, ref, useAttrs, watch } from 'vue';
import type { Ref } from 'vue';
import type { BreakPoint, Responsive } from '@/components/Grid/interface';
defineOptions({
name: 'GridItem'
});
type Props = {
offset?: number;
span?: number;
suffix?: boolean;
xs?: Responsive;
sm?: Responsive;
md?: Responsive;
lg?: Responsive;
xl?: Responsive;
};
const props = withDefaults(defineProps<Props>(), {
offset: 0,
span: 1,
suffix: false,
xs: undefined,
sm: undefined,
md: undefined,
lg: undefined,
xl: undefined
});
const attrs = useAttrs() as { index: string };
const isShow = ref(true);
//
const breakPoint = inject<Ref<BreakPoint>>('breakPoint', ref('xl'));
const shouldHiddenIndex = inject('shouldHiddenIndex', ref(-1));
watch(
() => [shouldHiddenIndex.value, breakPoint.value],
n => {
if (attrs.index) {
isShow.value = !(n[0] !== -1 && parseInt(attrs.index) >= Number(n[0]));
}
},
{ immediate: true }
);
const gap = inject('gap', 0);
const cols = inject('cols', ref(4));
const style = computed(() => {
let span = props[breakPoint.value]?.span ?? props.span;
let offset = props[breakPoint.value]?.offset ?? props.offset;
if (props.suffix) {
return {
gridColumnStart: cols.value - span - offset + 1,
gridColumnEnd: `span ${span + offset}`,
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : 'unset'
};
} else {
return {
gridColumn: `span ${span + offset > cols.value ? cols.value : span + offset}/span ${
span + offset > cols.value ? cols.value : span + offset
}`,
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : 'unset'
};
}
});
</script>

@ -0,0 +1,159 @@
<template>
<div :style="style">
<slot />
</div>
</template>
<script setup lang="ts">
import { ref, watch, useSlots, computed, provide, onBeforeMount, onMounted, onUnmounted, onDeactivated, onActivated } from 'vue';
import type { VNode, VNodeArrayChildren } from 'vue';
import type { BreakPoint } from '@/components/Grid/interface';
defineOptions({
name: 'Grid'
});
type Props = {
cols?: number | Record<BreakPoint, number>;
collapsed?: boolean;
collapsedRows?: number;
gap?: [number, number] | number;
};
const props = withDefaults(defineProps<Props>(), {
cols: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
collapsed: false,
collapsedRows: 1,
gap: 0
});
onBeforeMount(() => props.collapsed && findIndex());
onMounted(() => {
resize({ target: { innerWidth: window.innerWidth } } as unknown as Event);
window.addEventListener('resize', resize);
});
onActivated(() => {
resize({ target: { innerWidth: window.innerWidth } } as unknown as Event);
window.addEventListener('resize', resize);
});
onUnmounted(() => {
window.removeEventListener('resize', resize);
});
onDeactivated(() => {
window.removeEventListener('resize', resize);
});
//
const resize = (e: Event) => {
let width = (e.target as Window).innerWidth;
switch (true) {
case width < 768:
breakPoint.value = 'xs';
break;
case width >= 768 && width < 992:
breakPoint.value = 'sm';
break;
case width >= 992 && width < 1200:
breakPoint.value = 'md';
break;
case width >= 1200 && width < 1920:
breakPoint.value = 'lg';
break;
case width >= 1920:
breakPoint.value = 'xl';
break;
}
};
// gap
provide('gap', Array.isArray(props.gap) ? props.gap[0] : props.gap);
//
let breakPoint = ref<BreakPoint>('xl');
provide('breakPoint', breakPoint);
// index
const hiddenIndex = ref(-1);
provide('shouldHiddenIndex', hiddenIndex);
// cols
const gridCols = computed(() => {
if (typeof props.cols === 'object') return props.cols[breakPoint.value] ?? props.cols;
return props.cols;
});
provide('cols', gridCols);
// index
const slots = useSlots().default!();
const findIndex = () => {
let fields: VNodeArrayChildren = [];
let suffix: VNode | null = null;
slots.forEach((slot: any) => {
// suffix
if (typeof slot.type === 'object' && slot.type.name === 'GridItem' && slot.props?.suffix !== undefined) suffix = slot;
// slot children
if (typeof slot.type === 'symbol' && Array.isArray(slot.children)) fields.push(...slot.children);
});
// suffix
let suffixCols = 0;
if (suffix) {
suffixCols =
((suffix as VNode).props![breakPoint.value]?.span ?? (suffix as VNode).props?.span ?? 1) +
((suffix as VNode).props![breakPoint.value]?.offset ?? (suffix as VNode).props?.offset ?? 0);
}
try {
let find = false;
fields.reduce((prev = 0, current, index) => {
prev +=
((current as VNode).props![breakPoint.value]?.span ?? (current as VNode).props?.span ?? 1) +
((current as VNode).props![breakPoint.value]?.offset ?? (current as VNode).props?.offset ?? 0);
if (Number(prev) > props.collapsedRows * (gridCols.value as number) - suffixCols) {
hiddenIndex.value = index;
find = true;
throw new Error('find it');
}
return prev;
}, 0);
if (!find) hiddenIndex.value = -1;
} catch (error) {
console.warn(error);
}
};
// findIndex
watch(
() => breakPoint.value,
() => {
if (props.collapsed) findIndex();
}
);
// collapsed
watch(
() => props.collapsed,
value => {
if (value) return findIndex();
hiddenIndex.value = -1;
}
);
//
const gridGap = computed(() => {
if (typeof props.gap === 'number') return `${props.gap}px`;
if (Array.isArray(props.gap)) return `${props.gap[1]}px ${props.gap[0]}px`;
return 'unset';
});
// style
const style = computed(() => {
return {
display: 'grid',
gridGap: gridGap.value,
gridTemplateColumns: `repeat(${gridCols.value}, minmax(0, 1fr))`
};
});
defineExpose({ breakPoint });
</script>

@ -0,0 +1,6 @@
export type BreakPoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
export type Responsive = {
span?: number;
offset?: number;
};

@ -0,0 +1,116 @@
/* 语法高亮 */
.hljs-container {
position: relative;
display: flex;
flex-direction: column;
width: max-content;
overflow-x: hidden;
font-size: 14px;
line-height: 24px;
text-align: left;
background: #21252b;
.hljs-header {
width: 100%;
background-color: #21252b;
display: flex;
justify-content: flex-end;
align-items: center;
position: sticky;
top: 0;
left: 0;
z-index: 100;
}
.hljs-wrapper {
display: flex;
align-items: flex-start;
}
/** 行数样式 */
.hljs-code-number {
padding: 17px 10px 0;
color: #d1d8e6;
font-size: 12px;
list-style: none;
background: #21252b;
position: sticky;
left: 0;
}
pre {
flex: 1;
}
}
/** 3个点 */
.hljs-container .hljs-header::before {
position: absolute;
top: 10px;
left: 15px;
width: 12px;
height: 12px;
overflow: visible;
font-weight: 700;
font-size: 16px;
line-height: 12px;
white-space: nowrap;
text-indent: 75px;
background-color: #fc625d;
border-radius: 16px;
box-shadow:
20px 0 #fdbc40,
40px 0 #35cd4b;
content: attr(codetype);
}
/** 滚动条 */
:deep(.hljs) {
overflow-x: auto;
}
:deep(.hljs::-webkit-scrollbar) {
width: 12px !important;
height: 12px !important;
}
:deep(.hljs::-webkit-scrollbar-thumb) {
height: 30px !important;
background: #d1d8e6;
background-clip: content-box;
border: 2px solid transparent;
border-radius: 19px;
opacity: 0.8;
}
:deep(.hljs::-webkit-scrollbar-thumb:hover) {
background: #a5b3cf;
background-clip: content-box;
border: 2px solid transparent;
}
:deep(.hljs::-webkit-scrollbar-track-piece) {
width: 30px;
height: 30px;
background: #333;
}
::-webkit-scrollbar-button {
display: none;
}
/** 复制样式 */
.hljs-copy {
width: auto;
height: 30px;
color: #d7d7e1;
cursor: pointer;
user-select: none;
border-radius: 4px;
transition: background-color 0.3s;
margin: 0 2px;
.sql-box {
text-align: unset;
}
}

@ -0,0 +1,76 @@
.hljs-container {
position: relative;
margin: 20px 0;
background-color: #f3f3f3;
border-radius: 8px;
font-size: 14px;
line-height: 1.6;
overflow-x: auto;
pre {
margin: 0;
}
}
.hljs-container.sql-box {
text-align: unset;
}
.hljs-header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #343541;
padding: 4px;
position: relative; /* 添加 relative 定位 */
}
.hljs-separator {
height: 1px;
width: 100%;
background-color: #ccc;
margin: 10px 0;
}
.hljs-language {
color: #d7d7e1;
font-weight: bold;
margin-left: 10px;
}
.hljs-code-number {
position: absolute;
top: 0; /* 调整为 0即头部的底部 */
left: 0;
padding: 0 10px;
color: #757575;
user-select: none;
li {
list-style: none;
margin: 0;
padding: 0;
}
}
.hljs-code {
font-family: 'Courier New', monospace;
}
.hljs-copy {
margin-right: 10px;
color: #d7d7e1;
cursor: pointer;
user-select: none;
background-color: #333;
border-radius: 4px;
transition: background-color 0.3s;
&:hover {
background-color: #555;
}
.sql-box {
text-align: unset;
}
}

@ -0,0 +1,61 @@
<template>
<div class="hljs-container">
<div class="hljs-header" :codetype="title">
<div class="hljs-copy" @click="handleCopy">
<el-button type="primary" link @click="handleCopy" :icon="DocumentCopy" class="hljs-copy">
{{ copyButtonText }}
</el-button>
</div>
</div>
<div class="hljs-wrapper" v-code>
<highlightjs :language="language" :autodetect="false" :code="code" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { DocumentCopy } from '@element-plus/icons-vue';
import vCode from './line';
defineOptions({
name: 'HighCode'
});
const props = withDefaults(defineProps<HighCodeProps>(), {
language: 'java',
code: '',
title: ''
});
const copyButtonText = ref<string>('复制');
export interface HighCodeProps {
language: string; //
code: string; //
title: string; // title
}
//
const handleCopy = async () => {
try {
if (navigator.clipboard) {
//
await navigator.clipboard.writeText(props.code);
} else {
// 退 document.execCommand
const tempTextarea = document.createElement('textarea');
tempTextarea.value = props.code;
document.body.appendChild(tempTextarea);
tempTextarea.select();
document.execCommand('copy');
document.body.removeChild(tempTextarea);
}
copyButtonText.value = '复制成功!';
setTimeout(() => {
copyButtonText.value = '复制';
}, 2000); // 2000 ''
} catch (err) {
console.error('复制操作不被支持或失败: ', err);
}
};
</script>
<style lang="scss" scoped></style>

@ -0,0 +1,38 @@
import './code.scss';
const vCode = {
mounted(el: any) {
buildLineNumber(el);
},
updated(el: any) {
buildLineNumber(el);
}
};
const buildLineNumber = (el: any) => {
//获取代码片段
const code = el.querySelector('code.hljs');
const pre = el.querySelector('pre');
const html = code?.innerHTML;
const size = html.split('\n').length;
const codeNumber = el.querySelector('.hljs-code-number');
if (codeNumber) {
el.removeChild(codeNumber);
}
//插入行数
const ul = document.createElement('ul');
for (let i = 1; i <= size; i++) {
const li = document.createElement('li');
li.innerText = i + '';
ul.appendChild(li);
}
ul.classList.add('hljs-code-number');
// 确保 pre 是 el 的子元素后再进行插入
if (pre && pre.parentNode === el) {
el.insertBefore(ul, pre);
}
};
export default vCode;

@ -0,0 +1,197 @@
<template>
<div class="choose-container">
<el-input ref="refInput" v-model="internalValue" v-bind="$attrs" readonly @input="changeValue" @click="showPopover">
<template #prepend>
<el-icon size="30">
<SvgIcon v-if="internalValue.startsWith('svg-')" :name="internalValue.substring(4)" />
<component v-else-if="internalValue" :is="internalValue" />
</el-icon>
</template>
</el-input>
<div ref="chooseDialogRef" class="choose-wrap" :class="{ show: isShow }">
<el-input v-model="search" class="w-50 m-2" placeholder="搜索图标" :prefix-icon="Search" />
<el-tabs v-model="activeTab">
<el-tab-pane label="Element" name="element">
<el-scrollbar max-height="280">
<div class="choose-box">
<div class="choose-item" v-for="item in filteredElementIcons" :key="item">
<div @click="chooseIcon(item)">
<el-icon size="30">
<component :is="item" />
</el-icon>
{{ item }}
</div>
</div>
</div>
</el-scrollbar>
</el-tab-pane>
<el-tab-pane label="Svg" name="svg">
<el-scrollbar max-height="280">
<div class="choose-box">
<div class="choose-item" v-for="item in filteredSvgIcons" :key="item">
<div @click="chooseIcon(item)">
<el-icon size="30">
<SvgIcon :name="item.substring(4)" />
</el-icon>
{{ item }}
</div>
</div>
</div>
</el-scrollbar>
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
// element icons
import * as Icons from '@element-plus/icons-vue';
import { Search } from '@element-plus/icons-vue';
import SvgIcon from '@/components/SvgIcon/index.vue';
import { onClickOutside } from '@vueuse/core';
defineOptions({
name: 'IconChoose'
});
type Props = {
modelValue: string;
};
const props = defineProps<Props>();
const emits = defineEmits<{
'update:modelValue': [string];
}>();
const internalValue = ref(props.modelValue);
const changeValue = () => {
emits('update:modelValue', internalValue.value);
};
const activeTab = ref('element');
//
const search = ref('');
const elementIcons = Object.keys(Icons).map(key => {
return Icons[key as keyof typeof Icons].name;
});
const svgIcons: string[] = [];
const svgIconsFiles = import.meta.glob('@/assets/icons/**/*.svg');
for (const key in svgIconsFiles) {
const matchArray = key.match(/\/assets\/icons\/([^/]+)\.svg$/);
if (matchArray && matchArray.length >= 2) {
svgIcons.push(`svg-${matchArray[1]}`);
}
}
//
const filteredElementIcons = computed(() => {
return elementIcons.filter(item => item.toLowerCase().includes(search.value.toLowerCase()));
});
const filteredSvgIcons = computed(() => {
return svgIcons.filter(item => item.toLowerCase().includes(search.value.toLowerCase()));
});
/**
* 弹窗
*/
const isShow = ref(false);
/**
* 显示
*/
const showPopover = () => {
isShow.value = true;
};
/**
* 隐藏
*/
const hidePopover = () => {
isShow.value = false;
};
//
const chooseDialogRef = ref(null);
onClickOutside(chooseDialogRef, () => {
if (isShow.value) {
hidePopover();
}
});
const chooseIcon = (name: string) => {
hidePopover();
internalValue.value = name;
emits('update:modelValue', name);
};
</script>
<style scoped lang="scss">
.choose-container {
position: relative;
width: 100%;
.choose-wrap {
position: absolute;
margin-top: 6px;
padding: 10px;
z-index: 100;
border: 1px solid #e4e7ed;
border-radius: 6px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
background-color: #fff;
width: 100%;
height: 0;
opacity: 0;
overflow: hidden;
transition:
height 0.3s,
opacity 0.3s;
box-sizing: border-box;
&.show {
opacity: 1;
height: 400px;
}
}
}
.choose-box {
display: flex;
flex-wrap: wrap;
margin: 0 10px;
.choose-item {
width: 25%;
padding: 5px;
box-sizing: border-box;
> div {
display: flex;
align-items: center;
padding: 5px;
border: 1px solid rgba(204, 204, 204, 0.5);
border-radius: 4px;
overflow: hidden;
font-size: 12px;
}
&:hover {
> div {
border-color: rgba(0, 150, 136, 0.5);
}
}
}
}
.close-box {
position: absolute;
top: -15px;
right: -15px;
}
</style>

@ -0,0 +1,3 @@
.upload {
width: 80%;
}

@ -0,0 +1,177 @@
<template>
<el-dialog v-model="dialogVisible" :title="`导入${parameter.title}`" :destroy-on-close="true" width="580px" draggable>
<el-form class="drawer-multiColumn-form" label-width="100px">
<el-form-item label="模板下载 :">
<el-button type="primary" :icon="Download" @click="downloadTemp"> </el-button>
</el-form-item>
<el-form-item label="文件上传 :">
<el-upload
action="#"
class="upload"
:drag="true"
:limit="excelLimit"
:multiple="true"
:show-file-list="true"
:http-request="uploadExcel"
:before-upload="beforeExcelUpload"
:on-exceed="handleExceed"
:on-success="excelUploadSuccess"
:on-error="excelUploadError"
:accept="parameter.fileType!.join(',')"
>
<slot name="empty">
<el-icon class="el-icon--upload">
<upload-filled />
</el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
</slot>
<template #tip>
<slot name="tip">
<div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件文件最大为 {{ parameter.fileSize }}M</div>
</slot>
</template>
</el-upload>
</el-form-item>
<el-form-item label="数据覆盖 :">
<el-switch v-model="isCover" />
</el-form-item>
</el-form>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useDownload } from '@/hooks/useDownload';
import { Download } from '@element-plus/icons-vue';
import { type UploadRequestOptions, type UploadRawFile, ElNotification } from 'element-plus';
import type { AxiosProgressEvent, AxiosRequestConfig } from 'axios';
defineOptions({
name: 'ImportExcel'
});
export interface ExcelParameterProps {
title: string; //
templateName?: string; //
templateFileType?: File.ExcelFileType; //
fileSize?: number; //
fileType?: File.ExcelMimeType[]; //
tempApi?: (params: any) => Promise<any>; // Api
importApi?: (params: any, config?: AxiosRequestConfig<any> | undefined) => Promise<any>; // Api
getTableList?: () => void; // Api
}
//
const isCover = ref(false);
//
const excelLimit = ref(1);
// dialog
const dialogVisible = ref(false);
//
const parameter = ref<ExcelParameterProps>({
title: '',
fileSize: 5,
fileType: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
templateFileType: '.xlsx'
});
//
const acceptParams = (params: ExcelParameterProps) => {
parameter.value = { ...parameter.value, ...params };
dialogVisible.value = true;
};
// Excel
const downloadTemp = () => {
if (!parameter.value.tempApi) return;
const templateFileType = parameter.value.templateFileType;
const templateName = parameter.value.templateName ? parameter.value.templateName : parameter.value.title;
useDownload(parameter.value.tempApi, `${templateName}模板`, {
templateName: templateName + templateFileType
});
};
//
const uploadExcel = async (options: UploadRequestOptions) => {
let excelFormData = new FormData();
excelFormData.append('file', options.file);
excelFormData.append('isCover', isCover.value as unknown as Blob);
await parameter.value.importApi!(excelFormData, {
onUploadProgress(event: AxiosProgressEvent) {
const progressEvent = new ProgressEvent('upload', {
lengthComputable: event.total !== undefined,
loaded: event.loaded || 0,
total: event.total || 0
});
options.onProgress(new CustomUploadProgressEvent(progressEvent));
}
});
if (parameter.value.getTableList) parameter.value.getTableList();
dialogVisible.value = false;
};
/**
* @description 文件上传之前判断
* @param file 上传的文件
* */
const beforeExcelUpload = (file: UploadRawFile) => {
const isExcel = parameter.value.fileType!.includes(file.type as File.ExcelMimeType);
const fileSize = file.size / 1024 / 1024 < parameter.value.fileSize!;
if (!isExcel)
ElNotification({
title: '温馨提示',
message: '上传文件只能是 xls / xlsx 格式!',
type: 'warning'
});
if (!fileSize)
setTimeout(() => {
ElNotification({
title: '温馨提示',
message: `上传文件大小不能超过 ${parameter.value.fileSize}MB`,
type: 'warning'
});
}, 0);
return isExcel && fileSize;
};
//
const handleExceed = () => {
ElNotification({
title: '温馨提示',
message: '最多只能上传一个文件!',
type: 'warning'
});
};
//
const excelUploadError = () => {
ElNotification({
title: '温馨提示',
message: `导入${parameter.value.title}失败,请您重新上传!`,
type: 'error'
});
};
//
const excelUploadSuccess = () => {
ElNotification({
title: '温馨提示',
message: `导入${parameter.value.title}成功!`,
type: 'success'
});
};
class CustomUploadProgressEvent extends ProgressEvent {
percent: number;
constructor(event: ProgressEvent) {
super(event.type, event);
this.percent = event.lengthComputable ? Math.round((event.loaded / event.total) * 100) : 0;
}
}
defineExpose({
acceptParams
});
</script>
<style lang="scss" scoped>
@use './index';
</style>

@ -0,0 +1,74 @@
.loading-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
.loading-wrap {
display: flex;
align-items: center;
justify-content: center;
padding: 98px;
}
}
.dot {
position: relative;
box-sizing: border-box;
display: inline-block;
width: 32px;
height: 32px;
font-size: 32px;
transform: rotate(45deg);
animation: ant-rotate 1.2s infinite linear;
}
.dot i {
position: absolute;
display: block;
width: 14px;
height: 14px;
background-color: var(--el-color-primary);
border-radius: 100%;
opacity: 0.3;
transform: scale(0.75);
transform-origin: 50% 50%;
animation: ant-spin-move 1s infinite linear alternate;
}
.dot i:nth-child(1) {
top: 0;
left: 0;
}
.dot i:nth-child(2) {
top: 0;
right: 0;
animation-delay: 0.4s;
}
.dot i:nth-child(3) {
right: 0;
bottom: 0;
animation-delay: 0.8s;
}
.dot i:nth-child(4) {
bottom: 0;
left: 0;
animation-delay: 1.2s;
}
@keyframes ant-rotate {
to {
transform: rotate(405deg);
}
}
@keyframes ant-spin-move {
to {
opacity: 1;
}
}

@ -0,0 +1,17 @@
<template>
<div class="loading-box">
<div class="loading-wrap">
<span class="dot dot-spin"><i /><i /><i /><i /></span>
</div>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: 'Loading'
});
</script>
<style scoped lang="scss">
@use './index';
</style>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save